Compare commits

..

63 Commits

Author SHA1 Message Date
0135b6711e removed getting energy management data using communityUuid. 2025-06-03 16:01:45 +03:00
74ae9d7ce1 Merge pull request #225 from SyncrowIOT/SP-1175-FE-Identify-and-remove-all-instances-of-backend-error-toast-messages-in-the-frontend-code-web
change the validation from static code to backend
2025-06-03 15:04:03 +03:00
710f316f8d Merge pull request #223 from SyncrowIOT/SP-1600-FE-Single-Batch-Control-Migration
Sp 1600 fe single batch control migration
2025-06-03 12:27:10 +03:00
0c82a19a1d Merge pull request #218 from SyncrowIOT/SP-1593-FE-Create-Recommendation-Section-Based-on-AQI-Level-and-Ensure-Layout-Responsiveness
Sp 1593 fe create recommendation section based on aqi level and ensure layout responsiveness
2025-06-03 11:22:39 +03:00
d1df33b31e Refactor WallSensorBloc to integrate new service dependencies and utilize a factory for instantiation. Enhanced event handling methods for improved error management and real-time status updates from Firebase, including optimized parsing logic for device status values. 2025-06-03 11:15:06 +03:00
6a36405530 Refactor TwoGangSwitchBloc to integrate new service dependencies and utilize a factory for instantiation. Enhanced event handling methods for improved error management and real-time status updates from Firebase, including parsing logic for device status values. 2025-06-03 10:48:01 +03:00
3c98365338 change the validation from static code to backend 2025-06-03 10:44:34 +03:00
88a7607395 Refactor TwoGangGlassSwitchBloc to integrate new service dependencies and utilize a factory for instantiation. Enhanced event handling methods for improved error management and state updates, including real-time status listening from Firebase. 2025-06-03 10:33:33 +03:00
f58ddf76da Refactor LivingRoomBloc to integrate new service dependencies and utilize a factory for instantiation. Enhanced event handling methods for improved error management and state updates, including real-time status listening from Firebase. 2025-06-03 10:19:10 +03:00
a71a66034c Refactor ThreeGangGlassSwitchBloc to integrate new service dependencies and utilize a factory for instantiation. Enhanced event handling methods for improved error management and state updates. 2025-06-03 09:49:26 +03:00
b06a23cc60 Refactor WallLightSwitchBloc to integrate new service dependencies and utilize a factory for instantiation. Improved event handling methods for better error management and state updates. 2025-06-02 16:40:13 +03:00
5595bb7f25 Refactor OneGangGlassSwitchBloc to utilize new service dependencies and implement a factory for instantiation. Enhanced event handling methods for improved error management and state updates. 2025-06-02 16:35:55 +03:00
8e11749ed7 Prepared for aqi distribution API Integration. 2025-06-02 16:13:58 +03:00
7bc9079212 reverted a comment. 2025-06-02 14:30:07 +03:00
97801872e0 Implemented an initial remote implementation of RangeOfAqiService. 2025-06-02 14:29:04 +03:00
fa9210f387 added fromJson factory methods to RangeOfAqi, and to RangeOfAqiValue data models. 2025-06-02 14:28:50 +03:00
57b6f01177 SP-1593 Implemented the agreed upon api contract. 2025-06-02 14:26:47 +03:00
77d39bfc53 Refactor CurtainBloc to use new service dependencies and implement a factory for instantiation. Updated event handling methods for improved error management and state updates. 2025-06-02 11:26:30 +03:00
3bd2bd114b migrate CeilingSensorBloc to use the new services. 2025-06-02 11:13:56 +03:00
f98636a2e5 Migrated AcBloc single/batch controls the new services. 2025-06-02 10:44:43 +03:00
19548e99ab indentation and formatting of WaterHeaterBloc. 2025-06-02 10:20:05 +03:00
b60c674496 Created a factory for the WaterHeaterBloc, and injected the necessary dependenices. 2025-06-02 10:12:53 +03:00
6f3dfb607e Extracted single/batch control services creation into a factory for ease of reusablility for the sake of this migration. 2025-06-02 10:11:23 +03:00
62dabf1ce2 Made values in DeviceControlDialog selectable for a better UX. 2025-06-02 10:10:50 +03:00
f07dbad1ea Merge pull request #220 from SyncrowIOT/SP-1664-FE-Sider-bar-tree-behavior-issues-on-Analytics-page
Sp 1664 fe sider bar tree behavior issues on analytics page
2025-06-01 16:45:19 +03:00
87df8e4091 Merge pull request #222 from SyncrowIOT/SP-1389-FE-On-Login-page-Email-field-is-case-sensitive-it-should-not-be
Normalize email to lowercase when logging in
2025-06-01 16:38:53 +03:00
2d68fc23a3 Normalize email to lowercase when logging in 2025-06-01 16:21:22 +03:00
15ea1b4c5a Merge pull request #221 from SyncrowIOT/enable-hot-reload
enable hot reload on web.
2025-06-01 16:00:46 +03:00
17f6985dbf enable hot reload on web. 2025-06-01 15:59:29 +03:00
d1ddf75a42 Merge pull request #219 from SyncrowIOT/SP-1607-FE-Adjust-Padding-Between-Comparison-Signs-for-Visual-Consistency
Sp 1607 fe adjust padding between comparison signs for visual consistency
2025-06-01 15:50:53 +03:00
393a5361f0 Apply correct business logic in AirQualityDataLoadingStrategy. 2025-06-01 15:40:12 +03:00
a56e93d0d7 removed the interface method onSelectChildSpace, because all the clients dont use it and instead pass the onSpaceSelected, which isn't a good design. 2025-06-01 15:38:14 +03:00
94847fa936 SP-1664-Fe-Sider-bar-tree-behavior-issues-on-Analytics-page. 2025-06-01 15:36:52 +03:00
78f42dacf6 Adjust ConditionToggle widget dimensions and colors for improved UI consistency 2025-06-01 14:37:42 +03:00
066f967cd1 shows tooltip with data. 2025-06-01 14:28:40 +03:00
e28f3c3c03 reduced bar width size. 2025-06-01 14:28:40 +03:00
2be15e648a added loading widget to AqiDistributionChartTitle. 2025-06-01 14:28:40 +03:00
2e12d73151 randomize generated fake data in FakeAirQualityDistributionService. 2025-06-01 14:28:40 +03:00
c50ed693ae loads and clears aqi distribution in FetchAirQualityDataHelper. 2025-06-01 14:28:40 +03:00
8dc7d2b3d0 Connected AirQualityDistributionBloc into AqiDistributionChartBox. 2025-06-01 14:28:40 +03:00
accafb150e . 2025-06-01 14:24:07 +03:00
736e0c3d9c Injected AirQualityDistributionBloc into AnalyticsPage. 2025-06-01 14:23:14 +03:00
455d9c1f01 Created AirQualityDistributionBloc. 2025-06-01 14:22:25 +03:00
4479ed04b7 Created a AirQualityDistributionService along with its fake implementation. 2025-06-01 14:22:25 +03:00
286dea3f51 created a GetAirQualityDistributionParam. 2025-06-01 14:22:25 +03:00
44c4648941 made the first element of the bar rods to have only a top sides radius to match the design. 2025-06-01 14:22:25 +03:00
ca1feb9600 made charts based on states and not based on metrics. 2025-06-01 14:22:25 +03:00
7b31914e1c made progress towards aqi distribution chart. 2025-06-01 14:22:25 +03:00
10f35d3747 added more mock data to AqiDistributionChart. 2025-06-01 14:22:25 +03:00
1998a629b6 added some opacity to metric colors. 2025-06-01 14:22:25 +03:00
5940e52826 Implemented an initial version of AqiDistributionChart. 2025-06-01 14:22:25 +03:00
7c55e8bbf9 Prepared widgets for the aqi distribution chart. 2025-06-01 14:22:25 +03:00
fdabfe5d95 Merge pull request #217 from SyncrowIOT/SP-1584-FE-Block-Energy-Device-from-Being-Added-to-Then-Section-with-Validation-Message
Refactor energy clamp dialog to handle empty functions list gracefully
2025-06-01 14:13:53 +03:00
8916000696 Refactor visibility logic in Energy Clamp Dialog to handle empty functions list more elegantly 2025-06-01 14:11:21 +03:00
305d695358 Refactor energy clamp dialog to handle empty functions list gracefully 2025-06-01 13:12:58 +03:00
cde79fc168 Merge pull request #212 from SyncrowIOT/SP-1594-FE-Implement-Real-Time-AQI-Data-Panel-for-Selected-Sensor
Sp 1594 fe implement real time aqi data panel for selected sensor
2025-05-29 15:27:08 +03:00
d9448d9709 Merge pull request #209 from SyncrowIOT/SP-1546-FE-Garage-door-opener-Countdown-counter-is-throwing-Device-not-found-error
Refactor event handling in GarageDoorBloc to use local variable for d…
2025-05-29 14:39:40 +03:00
7bfd08238e Refactor event handling in GarageDoorBloc to use local variable for deviceId 2025-05-29 12:19:04 +03:00
0a9d53e5bd Refactor ConditionToggle widget to display icons with corresponding conditions 2025-05-29 10:48:12 +03:00
010960c89b Merge pull request #208 from SyncrowIOT/SP-1603-FE-Freeze-First-Row-in-All-Table-Views-Across-the-Platform
Refactor table layout to accommodate dynamic table size
2025-05-28 16:57:56 +03:00
fccf395c38 Update function names to follow consistent naming convention in name_filter.dart and users_page.dart 2025-05-28 16:56:51 +03:00
7c65b874eb Refactor table layout to accommodate dynamic table size 2025-05-28 16:40:44 +03:00
25db6ec687 Created pull_request_template.md . 2025-05-28 14:24:03 +03:00
102 changed files with 3440 additions and 2467 deletions

30
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,30 @@
<!--
Thanks for contributing!
Provide a description of your changes below and a general summary in the title
Please look at the following checklist to ensure that your PR can be accepted quickly:
-->
## Jira Ticket
<!-- Add your Jira ticket number as a link (e.g., [PROJ-123](https://jira.company.com/browse/PROJ-123)) -->
## Status
**READY/IN DEVELOPMENT/HOLD**
## Description
<!--- Describe your changes in detail -->
## Type of Change
<!--- Put an `x` in all the boxes that apply: -->
- [ ] ✨ New feature (non-breaking change which adds functionality)
- [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
- [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
- [ ] 🧹 Code refactor
- [ ] ✅ Build configuration change
- [ ] 📝 Documentation
- [ ] 🗑️ Chore

3
.vscode/launch.json vendored
View File

@ -16,6 +16,7 @@
"3000",
"-t",
"lib/main_dev.dart",
"--web-experimental-hot-reload",
],
"flutterMode": "debug"
@ -35,6 +36,7 @@
"3000",
"-t",
"lib/main_staging.dart",
"--web-experimental-hot-reload",
],
"flutterMode": "debug"
@ -54,6 +56,7 @@
"3000",
"-t",
"lib/main.dart",
"--web-experimental-hot-reload",
],
"flutterMode": "debug"

View 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];
}

View File

@ -1,18 +1,49 @@
import 'package:equatable/equatable.dart';
class RangeOfAqi extends Equatable {
final double min;
final double avg;
final double max;
final DateTime date;
final List<RangeOfAqiValue> data;
const RangeOfAqi({
required this.min,
required this.avg,
required this.max,
required this.data,
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
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];
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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];
}

View File

@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/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()) {
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
}
final RangeOfAqiService _rangeOfAqiService;
@ -20,19 +22,55 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
Emitter<RangeOfAqiState> emit,
) async {
emit(
RangeOfAqiState(
status: RangeOfAqiStatus.loading,
rangeOfAqi: state.rangeOfAqi,
),
state.copyWith(status: RangeOfAqiStatus.loading),
);
try {
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) {
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(
ClearRangeOfAqiEvent event,
Emitter<RangeOfAqiState> emit,

View File

@ -16,6 +16,15 @@ class LoadRangeOfAqiEvent extends RangeOfAqiEvent {
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 {
const ClearRangeOfAqiEvent();
}

View File

@ -5,14 +5,35 @@ enum RangeOfAqiStatus { initial, loading, loaded, failure }
final class RangeOfAqiState extends Equatable {
const RangeOfAqiState({
this.rangeOfAqi = const [],
this.filteredRangeOfAqi = const [],
this.status = RangeOfAqiStatus.initial,
this.errorMessage,
this.selectedAqiType = AqiType.aqi,
});
final RangeOfAqiStatus status;
final List<RangeOfAqi> rangeOfAqi;
final List<RangeOfAqi> filteredRangeOfAqi;
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
List<Object?> get props => [status, rangeOfAqi, errorMessage];
List<Object?> get props =>
[status, rangeOfAqi, filteredRangeOfAqi, errorMessage, selectedAqiType];
}

View File

@ -1,10 +1,11 @@
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/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_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/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_range_of_aqi_param.dart';
@ -13,8 +14,10 @@ abstract final class FetchAirQualityDataHelper {
static void loadAirQualityData(
BuildContext context, {
required DateTime date,
required String communityUuid,
required String spaceUuid,
bool shouldFetchAnalyticsDevices = true,
}) {
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
loadAnalyticsDevices(
@ -26,7 +29,11 @@ abstract final class FetchAirQualityDataHelper {
context,
spaceUuid: spaceUuid,
date: date,
aqiType: AqiType.aqi,
);
loadAirQualityDistribution(
context,
spaceUuid: spaceUuid,
date: date,
);
}
@ -37,7 +44,9 @@ abstract final class FetchAirQualityDataHelper {
context.read<RealtimeDeviceChangesBloc>().add(
const RealtimeDeviceChangesClosed(),
);
context.read<AirQualityDistributionBloc>().add(
const ClearAirQualityDistribution(),
);
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
}
@ -67,16 +76,26 @@ abstract final class FetchAirQualityDataHelper {
BuildContext context, {
required String spaceUuid,
required DateTime date,
required AqiType aqiType,
}) {
context.read<RangeOfAqiBloc>().add(
LoadRangeOfAqiEvent(
GetRangeOfAqiParam(
date: date,
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),
),
);
}
}

View File

@ -1,5 +1,6 @@
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/aqi_distribution_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
class AirQualityView extends StatelessWidget {
@ -23,8 +24,14 @@ class AirQualityView extends StatelessWidget {
height: height * 1.2,
child: const AirQualityEndSideWidget(),
),
SizedBox(height: height * 0.5, child: const RangeOfAqiChartBox()),
SizedBox(height: height * 0.5, child: const Placeholder()),
SizedBox(
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,
children: [
Expanded(child: RangeOfAqiChartBox()),
Expanded(child: Placeholder()),
Expanded(child: AqiDistributionChartBox()),
],
),
),

View File

@ -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,
);
}
}

View File

@ -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),
),
],
),
);
},
);
}
}

View File

@ -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));
}
},
),
),
],
);
}
}

View File

@ -3,17 +3,18 @@ import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
enum AqiType {
aqi('AQI', ''),
pm25('PM2.5', 'µg/m³'),
pm10('PM10', 'µg/m³'),
hcho('HCHO', 'mg/m³'),
tvoc('TVOC', 'µg/m³'),
co2('CO2', 'ppm');
aqi('AQI', '', 'aqi'),
pm25('PM2.5', 'µg/m³', 'pm25'),
pm10('PM10', 'µg/m³', 'pm10'),
hcho('HCHO', 'mg/m³', 'hcho'),
tvoc('TVOC', 'µg/m³', 'tvoc'),
co2('CO2', 'ppm', 'co2');
const AqiType(this.value, this.unit);
const AqiType(this.value, this.unit, this.code);
final String value;
final String unit;
final String code;
}
class AqiTypeDropdown extends StatefulWidget {

View File

@ -13,23 +13,37 @@ class RangeOfAqiChart extends StatelessWidget {
required this.chartData,
});
List<(List<double> values, Color color, Color? dotColor)> get _lines => [
(
chartData.map((e) => e.max).toList(),
ColorsManager.maxPurple,
ColorsManager.maxPurpleDot,
),
(
chartData.map((e) => e.avg).toList(),
Colors.white,
null,
),
(
chartData.map((e) => e.min).toList(),
ColorsManager.minBlue,
ColorsManager.minBlueDot,
),
];
List<(List<double> values, Color color, Color? dotColor)> get _lines {
final sortedData = List<RangeOfAqi>.from(chartData)
..sort((a, b) => a.date.compareTo(b.date));
return [
(
sortedData.map((e) {
final value = e.data.firstOrNull;
return value?.max ?? 0;
}).toList(),
ColorsManager.maxPurple,
ColorsManager.maxPurpleDot,
),
(
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
Widget build(BuildContext context) {

View File

@ -32,7 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
const SizedBox(height: 10),
const Divider(),
const SizedBox(height: 20),
Expanded(child: RangeOfAqiChart(chartData: state.rangeOfAqi)),
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
],
),
);

View File

@ -1,15 +1,18 @@
import 'package:flutter/material.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/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/energy_management/widgets/chart_title.dart';
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
class RangeOfAqiChartTitle extends StatelessWidget {
const RangeOfAqiChartTitle({required this.isLoading, super.key});
const RangeOfAqiChartTitle({
required this.isLoading,
super.key,
});
final bool isLoading;
static const List<(Color color, String title, bool hasBorder)> _colors = [
@ -66,12 +69,9 @@ class RangeOfAqiChartTitle extends StatelessWidget {
if (spaceUuid == null) return;
FetchAirQualityDataHelper.loadRangeOfAqi(
context,
spaceUuid: spaceUuid,
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
aqiType: value ?? AqiType.aqi,
);
if (value != null) {
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
}
},
),
),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.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/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_event.dart';
@ -26,10 +27,10 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
if (isSpaceSelected) {
clearData(context);
return;
}
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
if (hasSelectedSpaces) clearData(context);
if (isSpaceSelected) return;
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
@ -39,21 +40,15 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
context,
communityUuid: community.uuid,
spaceUuid: space.uuid ?? '',
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
);
}
@override
void onChildSpaceSelected(
BuildContext context,
CommunityModel community,
SpaceModel child,
) {
return onSpaceSelected(context, community, child);
}
@override
void clearData(BuildContext context) {
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
context.read<SpaceTreeBloc>().add(
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
);
FetchAirQualityDataHelper.clearAllData(context);
}
}

View File

@ -13,10 +13,5 @@ abstract class AnalyticsDataLoadingStrategy {
CommunityModel community,
SpaceModel space,
);
void onChildSpaceSelected(
BuildContext context,
CommunityModel community,
SpaceModel child,
);
void clearData(BuildContext context);
}

View File

@ -14,24 +14,14 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
List<SpaceModel> spaces,
) {
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
community.uuid,
spaces,
),
);
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isCommunitySelected =
spaceTreeBloc.state.selectedCommunities.contains(community.uuid);
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
if (isCommunitySelected) {
clearData(context);
return;
}
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
);
}
@override
@ -40,21 +30,31 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
SpaceModel space,
) {
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
community,
space.uuid ?? '',
space.children,
),
);
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
final spaceTreeState = context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
clearData(context);
if (isSpaceSelected) {
final firstSelectedSpace = spaceTreeBloc.state.selectedSpaces.first;
final isTheFirstSelectedSpace = firstSelectedSpace == space.uuid;
if (isTheFirstSelectedSpace) {
clearData(context);
}
return;
}
if (hasSelectedSpaces) {
clearData(context);
}
spaceTreeBloc.add(
OnSpaceSelected(
community,
space.uuid ?? '',
space.children,
),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
@ -62,18 +62,11 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
);
}
@override
void onChildSpaceSelected(
BuildContext context,
CommunityModel community,
SpaceModel child,
) {
return onSpaceSelected(context, community, child);
}
@override
void clearData(BuildContext context) {
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
context.read<SpaceTreeBloc>().add(
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
);
FetchEnergyManagementDataHelper.clearAllData(context);
}
}

View File

@ -26,10 +26,10 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
final spaceTreeBloc = context.read<SpaceTreeBloc>();
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
if (isSpaceSelected) {
clearData(context);
return;
}
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
if (hasSelectedSpaces) clearData(context);
if (isSpaceSelected) return;
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
@ -42,18 +42,11 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
);
}
@override
void onChildSpaceSelected(
BuildContext context,
CommunityModel community,
SpaceModel child,
) {
return onSpaceSelected(context, community, child);
}
@override
void clearData(BuildContext context) {
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
context.read<SpaceTreeBloc>().add(
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
);
FetchOccupancyDataHelper.clearAllData(context);
}
}

View File

@ -1,5 +1,6 @@
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/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_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/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/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/remote_energy_management_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(),
),
),
BlocProvider(
create: (context) => AirQualityDistributionBloc(
FakeAirQualityDistributionService(),
),
),
],
child: const AnalyticsPageForm(),
);

View File

@ -21,7 +21,7 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
strategy.onSpaceSelected(context, community, space);
},
onSelectChildSpace: (community, child) {
strategy.onChildSpaceSelected(context, community, child);
strategy.onSpaceSelected(context, community, child);
},
),
);

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.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_tab/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
@ -56,33 +57,16 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
const Spacer(),
Visibility(
key: ValueKey(selectedTab),
visible: selectedTab == AnalyticsPageTab.energyManagement,
visible: selectedTab == AnalyticsPageTab.energyManagement ||
selectedTab == AnalyticsPageTab.airQuality,
child: Expanded(
flex: 2,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDateFilterButton(
onDateSelected: (DateTime value) {
context.read<AnalyticsDatePickerBloc>().add(
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 ?? '',
);
}
onDateSelected: (value) {
_onDateChanged(context, value, selectedTab);
},
selectedDate: context
.watch<AnalyticsDatePickerBloc>()
@ -112,4 +96,73 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
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,
);
}
}

View File

@ -16,7 +16,6 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_
abstract final class FetchEnergyManagementDataHelper {
const FetchEnergyManagementDataHelper._();
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
}
@ -48,7 +47,6 @@ abstract final class FetchEnergyManagementDataHelper {
loadTotalEnergyConsumption(
context,
selectedDate: selectedDate0,
communityId: communityId,
spaceId: spaceId,
);
final selectedDevice = getSelectedDevice(context);
@ -61,7 +59,6 @@ abstract final class FetchEnergyManagementDataHelper {
}
loadEnergyConsumptionPerDevice(
context,
communityId: communityId,
spaceId: spaceId,
selectedDate: selectedDate0,
);
@ -84,12 +81,10 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadTotalEnergyConsumption(
BuildContext context, {
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final param = GetTotalEnergyConsumptionParam(
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<TotalEnergyConsumptionBloc>().add(
@ -100,12 +95,10 @@ abstract final class FetchEnergyManagementDataHelper {
static void loadEnergyConsumptionPerDevice(
BuildContext context, {
DateTime? selectedDate,
required String communityId,
required String spaceId,
}) {
final param = GetEnergyConsumptionPerDeviceParam(
spaceId: spaceId,
communityId: communityId,
monthDate: selectedDate,
);
context.read<EnergyConsumptionPerDeviceBloc>().add(

View File

@ -0,0 +1,9 @@
class GetAirQualityDistributionParam {
final DateTime date;
final String spaceUuid;
const GetAirQualityDistributionParam({
required this.date,
required this.spaceUuid,
});
}

View File

@ -2,18 +2,15 @@ class GetEnergyConsumptionPerDeviceParam {
const GetEnergyConsumptionPerDeviceParam({
this.monthDate,
this.spaceId,
this.communityId,
});
final DateTime? monthDate;
final String? spaceId;
final String? communityId;
Map<String, dynamic> toJson() => {
'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId,
'groupByDevice': true,
};
}

View File

@ -1,16 +1,12 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
class GetRangeOfAqiParam extends Equatable {
final DateTime date;
final String spaceUuid;
final AqiType aqiType;
const GetRangeOfAqiParam(
{
const GetRangeOfAqiParam({
required this.date,
required this.spaceUuid,
required this.aqiType,
});
@override

View File

@ -1,12 +1,10 @@
class GetTotalEnergyConsumptionParam {
final DateTime? monthDate;
final String? spaceId;
final String? communityId;
const GetTotalEnergyConsumptionParam({
this.monthDate,
this.spaceId,
this.communityId,
});
Map<String, dynamic> toJson() {
@ -14,7 +12,6 @@ class GetTotalEnergyConsumptionParam {
'monthDate':
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
'communityUuid': communityId,
'groupByDevice': false,
};
}

View File

@ -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,
);
}

View File

@ -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();
}
}

View File

@ -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');
}
}
}

View File

@ -1,4 +1,5 @@
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/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 max = (avg + maxDelta).clamp(0.0, 301.0);
return RangeOfAqi(
min: min,
avg: avg,
max: max,
return RangeOfAqi(
data: [
RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, 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,
);
});

View File

@ -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');
}
}
}

View File

@ -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/space_tree/bloc/space_tree_bloc.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/utils/constants/strings_manager.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));
}
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
Future<void> changePassword(
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState());
try {
var response = await AuthenticationAPI.verifyOtp(
@ -113,14 +115,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
emit(SuccessForgetState());
}
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
} on APIException catch (e) {
final errorMessage = e.message;
validate = errorMessage;
emit(AuthInitialState());
}
}
String? validateCode(String? value) {
if (value == null || value.isEmpty) {
return 'Code is required';
@ -149,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
static UserModel? user;
bool showValidationMessage = false;
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(AuthLoading());
if (isChecked) {
@ -161,25 +164,24 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
email: event.username,
email: event.username.toLowerCase(),
password: event.password,
),
);
} on DioException catch (e) {
final errorData = e.response!.data;
String errorMessage = errorData['error']['message'];
if (errorMessage == "Access denied for web platform") {
validate = errorMessage;
} else {
validate = 'Invalid Credentials!';
}
} on APIException catch (e) {
validate = e.message;
emit(LoginInitial());
return;
} catch (e) {
validate = 'Something went wrong';
emit(LoginInitial());
return;
}
if (token.accessTokenIsNotEmpty) {
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(
key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString());
@ -195,6 +197,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
}
}
checkBoxToggle(
CheckBoxEvent event,
Emitter<AuthState> emit,

View File

@ -21,6 +21,7 @@ class DynamicTable extends StatefulWidget {
final List<String>? initialSelectedIds;
final int uuidIndex;
final Function(dynamic selectedRows)? onSelectionChanged;
final Function(int rowIndex)? onSettingsPressed;
const DynamicTable({
super.key,
required this.headers,
@ -37,6 +38,7 @@ class DynamicTable extends StatefulWidget {
this.initialSelectedIds,
required this.uuidIndex,
this.onSelectionChanged,
this.onSettingsPressed,
});
@override
@ -48,11 +50,20 @@ class _DynamicTableState extends State<DynamicTable> {
bool _selectAll = false;
final ScrollController _verticalScrollController = ScrollController();
final ScrollController _horizontalScrollController = ScrollController();
late ScrollController _horizontalHeaderScrollController;
late ScrollController _horizontalBodyScrollController;
@override
void initState() {
super.initState();
_initializeSelection();
_horizontalHeaderScrollController = ScrollController();
_horizontalBodyScrollController = ScrollController();
// Synchronize horizontal scrolling
_horizontalBodyScrollController.addListener(() {
_horizontalHeaderScrollController
.jumpTo(_horizontalBodyScrollController.offset);
});
}
@override
@ -102,101 +113,87 @@ class _DynamicTableState extends State<DynamicTable> {
context.read<DeviceManagementBloc>().add(UpdateSelection(_selectedRows));
}
@override
void dispose() {
_horizontalHeaderScrollController.dispose();
_horizontalBodyScrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: widget.cellDecoration,
child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar(
controller: _horizontalScrollController,
thumbVisibility: true,
trackVisibility: true,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: Column(
children: [
Container(
decoration: widget.headerDecoration ??
const BoxDecoration(color: ColorsManager.boxColor),
child: SingleChildScrollView(
controller: _horizontalScrollController,
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
controller: _horizontalHeaderScrollController,
child: SizedBox(
width: widget.size.width,
child: Column(
child: Row(
children: [
Container(
decoration: widget.headerDecoration ??
const BoxDecoration(
color: ColorsManager.boxColor,
),
child: Row(
children: [
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(
widget.headers[index], index);
})
//...widget.headers.map((header) => _buildTableHeaderCell(header)),
],
),
),
widget.isEmpty
? SizedBox(
height: widget.size.height * 0.5,
width: widget.size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(
height: 15,
),
Text(
widget.tableName == 'AccessManagement'
? 'No Password '
: 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color:
ColorsManager.grayColor),
)
],
),
],
),
],
),
)
: Column(
children:
List.generate(widget.data.length, (index) {
final row = widget.data[index];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
index, widget.size.height * 0.08),
...row.map((cell) => _buildTableCell(
cell.toString(),
widget.size.height * 0.08)),
],
);
}),
),
if (widget.withCheckBox) _buildSelectAllCheckbox(),
...List.generate(widget.headers.length, (index) {
return _buildTableHeaderCell(
widget.headers[index], index);
}),
],
),
),
),
),
),
Expanded(
child: Scrollbar(
controller: _verticalScrollController,
thumbVisibility: true,
trackVisibility: true,
child: SingleChildScrollView(
controller: _verticalScrollController,
child: Scrollbar(
controller: _horizontalBodyScrollController,
thumbVisibility: false,
trackVisibility: false,
notificationPredicate: (notif) => notif.depth == 1,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _horizontalBodyScrollController,
child: SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children:
List.generate(widget.data.length, (rowIndex) {
final row = widget.data[rowIndex];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex, widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: entry.key,
);
}).toList(),
],
);
}),
),
),
),
),
),
),
),
],
),
);
}
@ -218,6 +215,32 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildEmptyState() => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
children: [
SvgPicture.asset(Assets.emptyTable),
const SizedBox(height: 15),
Text(
widget.tableName == 'AccessManagement'
? 'No Password '
: 'No Devices',
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: ColorsManager.grayColor),
)
],
),
],
),
],
);
Widget _buildRowCheckbox(int index, double size) {
return Container(
width: 50,
@ -272,13 +295,23 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget _buildTableCell(String content, double size) {
Widget _buildTableCell(
String content,
double size, {
required int rowIndex,
required int columnIndex,
}) {
bool isBatteryLevel = content.endsWith('%');
double? batteryLevel;
if (isBatteryLevel) {
batteryLevel = double.tryParse(content.replaceAll('%', '').trim());
}
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) {
return _buildSettingsIcon(rowIndex, size);
}
Color? statusColor;
switch (content) {
@ -330,4 +363,23 @@ class _DynamicTableState extends State<DynamicTable> {
),
);
}
Widget _buildSettingsIcon(int rowIndex, double size) {
return Container(
height: size,
width: 120,
padding: const EdgeInsets.all(5.0),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: ColorsManager.boxDivider, width: 1.0),
),
color: Colors.white,
),
alignment: Alignment.center,
child: IconButton(
icon: SvgPicture.asset(Assets.settings),
onPressed: () => widget.onSettingsPressed?.call(rowIndex),
),
);
}
}

View File

@ -1,21 +1,27 @@
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.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_state.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';
class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus;
final String deviceId;
Timer? _timer;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
AcBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
on<AcControlEvent>(_onAcControl);
@ -34,14 +40,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
int scheduledMinutes = 0;
FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
AcFetchDeviceStatusEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) {
// Convert API value to minutes
final totalMinutes = deviceStatus.countdown1 * 6;
scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
@ -62,30 +68,24 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
}
_listenToChanges(deviceId) {
void _listenToChanges(deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return;
if (_timer != null) {
await Future.delayed(const Duration(seconds: 1));
}
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']));
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(AcStatusUpdated(deviceStatus));
}
@ -93,146 +93,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} catch (_) {}
}
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
void _onAcStatusUpdated(
AcStatusUpdated event,
Emitter<AcsState> emit,
) {
deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(status: deviceStatus));
}
FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit);
AcControlEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: false,
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
);
}
try {
final success = await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
Future<void> _runDebounce({
required dynamic deviceId,
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);
if (!success) {
emit(const AcsFailedState(error: 'Failed to control device'));
}
});
}
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;
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
}
FutureOr<void> _onFetchAcBatchStatus(
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async {
AcFetchBatchStatusEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus));
} catch (e) {
emit(AcsFailedState(error: e.toString()));
@ -240,25 +138,32 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
FutureOr<void> _onAcBatchControl(
AcBatchControlEvent event, Emitter<AcsState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value, emit);
AcBatchControlEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: true,
deviceId: event.devicesIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
);
try {
final success = await batchControlDevicesService.batchControlDevices(
uuids: event.devicesIds,
code: event.code,
value: event.value,
);
if (!success) {
emit(const AcsFailedState(error: 'Failed to control devices'));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
}
FutureOr<void> _onFactoryReset(
AcFactoryResetEvent event, Emitter<AcsState> emit) async {
Future<void> _onFactoryReset(
AcFactoryResetEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState());
try {
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();
_timer?.cancel();
}
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;
final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
@ -315,7 +225,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}
Future<void> _handleToggleTimer(
ToggleScheduleEvent event, Emitter<AcsState> emit) async {
ToggleScheduleEvent event,
Emitter<AcsState> emit,
) async {
if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded;
@ -331,37 +243,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
try {
final scaledValue = totalMinutes ~/ 6;
await _runDebounce(
isBatch: false,
deviceId: deviceId,
code: 'countdown_time',
value: scaledValue,
oldValue: scaledValue,
emit: emit,
final success = await controlDeviceService.controlDevice(
deviceUuid: deviceId,
status: Status(code: 'countdown_time', value: scaledValue),
);
_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) {
timerActive = false;
emit(AcsFailedState(error: e.toString()));
}
} else {
await _runDebounce(
isBatch: false,
deviceId: deviceId,
code: 'countdown_time',
value: 0,
oldValue: 0,
emit: emit,
);
_countdownTimer?.cancel();
scheduledHours = 0;
scheduledMinutes = 0;
emit(currentState.copyWith(
isTimerActive: timerActive,
scheduledHours: 0,
scheduledMinutes: 0,
));
try {
final success = await controlDeviceService.controlDevice(
deviceUuid: deviceId,
status: Status(code: 'countdown_time', value: 0),
);
if (success) {
_countdownTimer?.cancel();
scheduledHours = 0;
scheduledMinutes = 0;
emit(currentState.copyWith(
isTimerActive: timerActive,
scheduledHours: 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) {
final currentState = state as ACStatusLoaded;
emit(currentState.copyWith(
@ -400,7 +322,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) {
final totalMinutes = event.apiValue * 6;
final scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60;
_startCountdownTimer(
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
Future<void> close() {
add(OnClose());

View 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(),
);
}
}

View File

@ -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_event.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_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/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/firmware_update.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/constants/assets.dart';
@ -26,8 +26,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) =>
AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
create: (context) => AcBlocFactory.create(
deviceId: devicesIds.first,
)..add(AcFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
if (state is ACStatusLoaded) {

View File

@ -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_event.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/current_temp.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);
return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!)
..add(AcFetchDeviceStatusEvent(device.uuid!)),
create: (context) => AcBlocFactory.create(
deviceId: device.uuid!,
)..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context);

View File

@ -1,5 +1,3 @@
import 'dart:async';
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';
@ -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/model/ceiling_sensor_model.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';
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
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<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
on<CeilingChangeValueEvent>(_changeValue);
@ -26,35 +31,34 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
on<StatusUpdated>(_onStatusUpdated);
}
void _fetchCeilingSensorStatus(
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
Future<void> _fetchCeilingSensorStatus(
CeilingInitialEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingInitialState());
try {
var response =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
final response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
_listenToChanges(event.deviceId);
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
return;
}
}
_listenToChanges(deviceId) {
void _listenToChanges(String deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
if (event.snapshot.value == null) return;
final usersMap = event.snapshot.value as Map<dynamic, dynamic>;
final statusList = <Status>[];
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = CeilingSensorModel.fromJson(statusList);
@ -65,149 +69,127 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<CeilingSensorState> emit) {
void _onStatusUpdated(
StatusUpdated event,
Emitter<CeilingSensorState> emit,
) {
deviceStatus = event.deviceStatus;
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
void _changeValue(
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
Future<void> _changeValue(
CeilingChangeValueEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') {
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);
}
_updateDeviceFunctionFromCode(event.code, event.value);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: deviceId,
code: event.code,
value: event.value,
emit: emit,
isBatch: false,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
}
}
Future<void> _onBatchControl(
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async {
CeilingBatchControlEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') {
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);
}
_updateDeviceFunctionFromCode(event.code, event.value);
emit(CeilingUpdateState(ceilingSensorModel: 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<CeilingSensorState> emit,
required bool isBatch,
}) {
late String id;
try {
final success = await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
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) {
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));
if (!success) {
emit(const CeilingFailedState(error: 'Failed to control devices'));
}
});
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
}
}
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
Emitter<CeilingSensorState> emit) async {
void _updateDeviceFunctionFromCode(String code, dynamic value) {
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) {
emit(ShowCeilingDescriptionState(description: reportString));
return;
} else {
emit(CeilingReportsLoadingState());
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
// final to = DateTime.now().millisecondsSinceEpoch;
}
try {
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString())
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(CeilingReportsState(deviceReport: value));
});
} catch (e) {
emit(CeilingReportsFailedState(error: e.toString()));
return;
}
emit(CeilingReportsLoadingState());
try {
final value = await DevicesManagementApi.getDeviceReports(
deviceId,
event.code,
);
emit(CeilingReportsState(deviceReport: value));
} catch (e) {
emit(CeilingReportsFailedState(error: e.toString()));
}
}
void _showDescription(
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
ShowCeilingDescriptionEvent event,
Emitter<CeilingSensorState> emit,
) {
emit(ShowCeilingDescriptionState(description: event.description));
}
void _backToGridView(
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
BackToCeilingGridViewEvent event,
Emitter<CeilingSensorState> emit,
) {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
}
FutureOr<void> _fetchCeilingSensorBatchControl(
CeilingFetchDeviceStatusEvent event,
Emitter<CeilingSensorState> emit) async {
Future<void> _fetchCeilingSensorBatchControl(
CeilingFetchDeviceStatusEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingInitialState());
try {
var response =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
return;
}
}
FutureOr<void> _onFactoryReset(
CeilingFactoryResetEvent event, Emitter<CeilingSensorState> emit) async {
Future<void> _onFactoryReset(
CeilingFactoryResetEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
try {
final response = await DevicesManagementApi().factoryReset(

View File

@ -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(),
);
}
}

View File

@ -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_event.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/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_update_data.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 isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first)
..add(CeilingFetchDeviceStatusEvent(devicesIds)),
create: (context) => CeilingSensorBlocFactory.create(
deviceId: devicesIds.first,
)..add(CeilingFetchDeviceStatusEvent(devicesIds)),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
@ -110,7 +111,6 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
),
),
),
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
FactoryResetWidget(
callFactoryReset: () {
context.read<CeilingSensorBloc>().add(

View File

@ -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_event.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/shared/sensors_widgets/presence_display_data.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 isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '')
..add(CeilingInitialEvent(device.uuid ?? '')),
create: (context) => CeilingSensorBlocFactory.create(
deviceId: device.uuid ?? '',
)..add(CeilingInitialEvent(device.uuid ?? '')),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) {
if (state is CeilingLoadingInitialState ||

View File

@ -1,17 +1,25 @@
import 'dart:async';
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/curtain/bloc/curtain_event.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';
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
late bool deviceStatus;
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<CurtainFetchBatchStatus>(_onFetchBatchStatus);
on<CurtainControl>(_onCurtainControl);
@ -20,32 +28,31 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
on<StatusUpdated>(_onStatusUpdated);
}
FutureOr<void> _onFetchDeviceStatus(
CurtainFetchDeviceStatus event, Emitter<CurtainState> emit) async {
Future<void> _onFetchDeviceStatus(
CurtainFetchDeviceStatus event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit);
deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus));
} catch (e) {
emit(CurtainError(e.toString()));
}
}
void _listenToChanges(String deviceId) {
void _listenToChanges(String deviceId, Emitter<CurtainState> emit) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
List<Status> statusList = [];
final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
@ -57,7 +64,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
}
}
if (statusList.isNotEmpty) {
bool newStatus = _checkStatus(statusList[0].value);
final newStatus = _checkStatus(statusList[0].value);
if (newStatus != deviceStatus) {
deviceStatus = newStatus;
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());
deviceStatus = event.deviceStatus;
emit(CurtainStatusLoaded(deviceStatus));
}
FutureOr<void> _onCurtainControl(
CurtainControl event, Emitter<CurtainState> emit) async {
final oldValue = deviceStatus;
Future<void> _onCurtainControl(
CurtainControl event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
_updateLocalValue(event.value, emit);
emit(CurtainStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
}
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;
try {
final controlValue = event.value ? 'open' : 'close';
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: controlValue),
);
} catch (e) {
_updateLocalValue(!event.value, emit);
emit(CurtainControlError(e.toString()));
}
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) {
@ -152,41 +115,44 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
return command.toLowerCase() == 'open';
}
FutureOr<void> _onFetchBatchStatus(
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async {
Future<void> _onFetchBatchStatus(
CurtainFetchBatchStatus event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus));
} catch (e) {
emit(CurtainError(e.toString()));
}
}
FutureOr<void> _onCurtainBatchControl(
CurtainBatchControl event, Emitter<CurtainState> emit) async {
final oldValue = deviceStatus;
Future<void> _onCurtainBatchControl(
CurtainBatchControl event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
_updateLocalValue(event.value, emit);
emit(CurtainStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.devicesIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
try {
final controlValue = event.value ? 'open' : 'stop';
await batchControlDevicesService.batchControlDevices(
uuids: event.devicesIds,
code: event.code,
value: controlValue,
);
} catch (e) {
_updateLocalValue(!event.value, emit);
emit(CurtainControlError(e.toString()));
}
}
FutureOr<void> _onFactoryReset(
CurtainFactoryReset event, Emitter<CurtainState> emit) async {
Future<void> _onFactoryReset(
CurtainFactoryReset event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
try {
final response = await DevicesManagementApi().factoryReset(

View File

@ -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(),
);
}
}

View File

@ -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_event.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/firmware_update.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) {
return BlocProvider(
create: (context) =>
CurtainBloc(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
CurtainBlocFactory.create(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) {
if (state is CurtainStatusLoading) {

View File

@ -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_event.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';
class CurtainStatusControlsView extends StatelessWidget
@ -15,7 +16,7 @@ class CurtainStatusControlsView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CurtainBloc(deviceId: deviceId)
create: (context) => CurtainBlocFactory.create(deviceId: deviceId)
..add(CurtainFetchDeviceStatus(deviceId)),
child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) {

View File

@ -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(),
);
}
}

View File

@ -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/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
abstract final class FlushMountedPresenceSensorBlocFactory {
const FlushMountedPresenceSensorBlocFactory._();
@ -10,12 +9,8 @@ abstract final class FlushMountedPresenceSensorBlocFactory {
}) {
return FlushMountedPresenceSensorBloc(
deviceId: deviceId,
controlDeviceService: DebouncedControlDeviceService(
decoratee: RemoteControlDeviceService(),
),
batchControlDevicesService: DebouncedBatchControlDevicesService(
decoratee: RemoteBatchControlDevicesService(),
),
controlDeviceService: DeviceBlocDependenciesFactory.createControlDeviceService(),
batchControlDevicesService: DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
);
}
}

View File

@ -360,7 +360,7 @@ class GarageDoorBloc extends Bloc<GarageDoorEvent, GarageDoorState> {
delay: deviceStatus.delay + Duration(minutes: 10));
emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent(
deviceId: event.deviceId,
deviceId: deviceId,
value: deviceStatus.delay.inSeconds,
code: 'countdown_1'));
} catch (e) {

View File

@ -1,11 +1,13 @@
import 'dart:async';
import 'package:bloc/bloc.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/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/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'one_gang_glass_switch_event.dart';
@ -13,13 +15,16 @@ part 'one_gang_glass_switch_state.dart';
class OneGangGlassSwitchBloc
extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
OneGangGlassStatusModel deviceStatus;
Timer? _timer;
late OneGangGlassStatusModel deviceStatus;
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
OneGangGlassSwitchBloc({required String deviceId})
: deviceStatus = OneGangGlassStatusModel(
uuid: deviceId, switch1: false, countDown: 0),
super(OneGangGlassSwitchInitial()) {
OneGangGlassSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(OneGangGlassSwitchInitial()) {
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<OneGangGlassSwitchControl>(_onControl);
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
@ -28,160 +33,140 @@ class OneGangGlassSwitchBloc
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event,
Emitter<OneGangGlassSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(
OneGangGlassSwitchFetchDeviceEvent event,
Emitter<OneGangGlassSwitchState> emit,
) async {
emit(OneGangGlassSwitchLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId);
deviceStatus =
OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit);
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
_listenToChanges(deviceId) {
void _listenToChanges(
String deviceId,
Emitter<OneGangGlassSwitchState> emit,
) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = OneGangGlassStatusModel.fromJson(
usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
Status(
code: element['code'].toString(),
value: element['value'].toString(),
),
);
}
}
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(
StatusUpdated event, Emitter<OneGangGlassSwitchState> emit) {
StatusUpdated event,
Emitter<OneGangGlassSwitchState> emit,
) {
emit(OneGangGlassSwitchLoading());
deviceStatus = event.deviceStatus;
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
}
Future<void> _onControl(OneGangGlassSwitchControl event,
Emitter<OneGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onControl(
OneGangGlassSwitchControl event,
Emitter<OneGangGlassSwitchState> emit,
) async {
emit(OneGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
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 {
final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
if (!response) {
emit(OneGangGlassSwitchError('Failed to reset device'));
} else {
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
}
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event,
Emitter<OneGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onBatchControl(
OneGangGlassSwitchBatchControl event,
Emitter<OneGangGlassSwitchState> emit,
) async {
emit(OneGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
OneGangGlassSwitchFetchBatchStatusEvent event,
Emitter<OneGangGlassSwitchState> emit) async {
OneGangGlassSwitchFetchBatchStatusEvent event,
Emitter<OneGangGlassSwitchState> emit,
) async {
emit(OneGangGlassSwitchLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = OneGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus =
OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<OneGangGlassSwitchState> 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);
Future<void> _onFactoryReset(
OneGangGlassFactoryResetEvent event,
Emitter<OneGangGlassSwitchState> emit,
) async {
emit(OneGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(OneGangGlassSwitchError('Failed to reset device'));
} else {
add(OneGangGlassSwitchFetchDeviceEvent(event.deviceId));
}
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
Emitter<OneGangGlassSwitchState> emit) {
_updateLocalValue(code, oldValue);
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(OneGangGlassSwitchError(e.toString()));
}
}
void _updateLocalValue(String code, bool value) {
@ -189,19 +174,4 @@ class OneGangGlassSwitchBloc
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();
}
}

View File

@ -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(),
);
}
}

View File

@ -2,9 +2,9 @@ import 'package:flutter/material.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/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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +16,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first)
create: (context) => OneGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
builder: (context, state) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.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/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/shared/toggle_widget.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 {
final String deviceId;
const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key);
const OneGangGlassSwitchControlView({required this.deviceId, super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
OneGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
builder: (context, state) {
if (state is OneGangGlassSwitchLoading) {

View File

@ -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_state.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';
class WallLightSwitchBloc
extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
WallLightSwitchBloc({required this.deviceId})
: super(WallLightSwitchInitial()) {
class WallLightSwitchBloc extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
late WallLightStatusModel deviceStatus;
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
WallLightSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(WallLightSwitchInitial()) {
on<WallLightSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<WallLightSwitchControl>(_onControl);
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
@ -20,143 +29,114 @@ class WallLightSwitchBloc
on<StatusUpdated>(_onStatusUpdated);
}
late WallLightStatusModel deviceStatus;
final String deviceId;
Timer? _timer;
FutureOr<void> _onFetchDeviceStatus(WallLightSwitchFetchDeviceEvent event,
Emitter<WallLightSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(
WallLightSwitchFetchDeviceEvent event,
Emitter<WallLightSwitchState> emit,
) async {
emit(WallLightSwitchLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
WallLightStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit);
deviceStatus = WallLightStatusModel.fromJson(event.deviceId, status.status);
emit(WallLightSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(WallLightSwitchError(e.toString()));
}
}
_listenToChanges(deviceId) {
void _listenToChanges(
String deviceId,
Emitter<WallLightSwitchState> emit,
) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
WallLightStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
Status(
code: element['code'].toString(),
value: element['value'].toString(),
),
);
}
}
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(
StatusUpdated event, Emitter<WallLightSwitchState> emit) {
StatusUpdated event,
Emitter<WallLightSwitchState> emit,
) {
emit(WallLightSwitchLoading());
deviceStatus = event.deviceStatus;
emit(WallLightSwitchStatusLoaded(deviceStatus));
}
FutureOr<void> _onControl(
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onControl(
WallLightSwitchControl event,
Emitter<WallLightSwitchState> emit,
) async {
emit(WallLightSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(WallLightSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(WallLightSwitchError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<WallLightSwitchState> 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<WallLightSwitchState> emit) {
_updateLocalValue(code, oldValue);
Future<void> _onBatchControl(
WallLightSwitchBatchControl event,
Emitter<WallLightSwitchState> emit,
) async {
emit(WallLightSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(WallLightSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
if (code == 'switch_1') {
deviceStatus = deviceStatus.copyWith(switch1: value);
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.devicesIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(WallLightSwitchError(e.toString()));
}
}
bool _getValueByCode(String code) {
switch (code) {
case 'switch_1':
return deviceStatus.switch1;
default:
return false;
}
}
Future<void> _onFetchBatchStatus(WallLightSwitchFetchBatchEvent event,
Emitter<WallLightSwitchState> emit) async {
Future<void> _onFetchBatchStatus(
WallLightSwitchFetchBatchEvent event,
Emitter<WallLightSwitchState> emit,
) async {
emit(WallLightSwitchLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
WallLightStatusModel.fromJson(event.devicesIds.first, status.status);
emit(WallLightSwitchStatusLoaded(deviceStatus));
@ -165,32 +145,10 @@ class WallLightSwitchBloc
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
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 {
Future<void> _onFactoryReset(
WallLightFactoryReset event,
Emitter<WallLightSwitchState> emit,
) async {
emit(WallLightSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
@ -198,12 +156,18 @@ class WallLightSwitchBloc
event.deviceId,
);
if (!response) {
emit(WallLightSwitchError('Failed'));
emit(WallLightSwitchError('Failed to reset device'));
} else {
emit(WallLightSwitchStatusLoaded(deviceStatus));
add(WallLightSwitchFetchDeviceEvent(event.deviceId));
}
} catch (e) {
emit(WallLightSwitchError(e.toString()));
}
}
void _updateLocalValue(String code, bool value) {
if (code == 'switch_1') {
deviceStatus = deviceStatus.copyWith(switch1: value);
}
}
}

View File

@ -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(),
);
}
}

View File

@ -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_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/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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -18,7 +18,7 @@ class WallLightBatchControlView extends StatelessWidget with HelperResponsiveLay
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first)
create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(WallLightSwitchFetchBatchEvent(deviceIds)),
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
builder: (context, state) {

View File

@ -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_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/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/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -15,7 +16,7 @@ class WallLightDeviceControl extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => WallLightSwitchBloc(deviceId: deviceId)
create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceId)
..add(WallLightSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
builder: (context, state) {

View File

@ -157,7 +157,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
),
),
const SizedBox(width: 10),
Text(
SelectableText(
value,
style: TextStyle(
fontSize: 16,

View File

@ -1,11 +1,14 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/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/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'three_gang_glass_switch_event.dart';
@ -13,19 +16,16 @@ part 'three_gang_glass_switch_state.dart';
class ThreeGangGlassSwitchBloc
extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
ThreeGangGlassStatusModel deviceStatus;
Timer? _timer;
late ThreeGangGlassStatusModel deviceStatus;
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
ThreeGangGlassSwitchBloc({required String deviceId})
: deviceStatus = ThreeGangGlassStatusModel(
uuid: deviceId,
switch1: false,
countDown1: 0,
switch2: false,
countDown2: 0,
switch3: false,
countDown3: 0),
super(ThreeGangGlassSwitchInitial()) {
ThreeGangGlassSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(ThreeGangGlassSwitchInitial()) {
on<ThreeGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<ThreeGangGlassSwitchControl>(_onControl);
on<ThreeGangGlassSwitchBatchControl>(_onBatchControl);
@ -34,188 +34,154 @@ class ThreeGangGlassSwitchBloc
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(ThreeGangGlassSwitchFetchDeviceEvent event,
Emitter<ThreeGangGlassSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(
ThreeGangGlassSwitchFetchDeviceEvent event,
Emitter<ThreeGangGlassSwitchState> emit,
) async {
emit(ThreeGangGlassSwitchLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(event.deviceId, emit);
deviceStatus =
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(ThreeGangGlassSwitchError(e.toString()));
}
}
_listenToChanges(deviceId) {
void _listenToChanges(
String deviceId,
Emitter<ThreeGangGlassSwitchState> emit,
) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = ThreeGangGlassStatusModel.fromJson(
usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
final statusList = <Status>[];
if (data['status'] != null) {
for (var element in data['status']) {
statusList.add(
Status(
code: element['code'].toString(),
value: element['value'].toString(),
),
);
}
}
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(
StatusUpdated event, Emitter<ThreeGangGlassSwitchState> emit) {
StatusUpdated event,
Emitter<ThreeGangGlassSwitchState> emit,
) {
emit(ThreeGangGlassSwitchLoading());
deviceStatus = event.deviceStatus;
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
}
Future<void> _onControl(ThreeGangGlassSwitchControl event,
Emitter<ThreeGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onControl(
ThreeGangGlassSwitchControl event,
Emitter<ThreeGangGlassSwitchState> emit,
) async {
emit(ThreeGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(ThreeGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(ThreeGangGlassSwitchBatchControl event,
Emitter<ThreeGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onBatchControl(
ThreeGangGlassSwitchBatchControl event,
Emitter<ThreeGangGlassSwitchState> emit,
) async {
emit(ThreeGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(ThreeGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
ThreeGangGlassSwitchFetchBatchStatusEvent event,
Emitter<ThreeGangGlassSwitchState> emit) async {
ThreeGangGlassSwitchFetchBatchStatusEvent event,
Emitter<ThreeGangGlassSwitchState> emit,
) async {
emit(ThreeGangGlassSwitchLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = ThreeGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus =
ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(ThreeGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event,
Emitter<ThreeGangGlassSwitchState> emit) async {
Future<void> _onFactoryReset(
ThreeGangGlassFactoryReset event,
Emitter<ThreeGangGlassSwitchState> emit,
) async {
emit(ThreeGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(ThreeGangGlassSwitchError('Failed'));
emit(ThreeGangGlassSwitchError('Failed to reset device'));
} else {
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
add(ThreeGangGlassSwitchFetchDeviceEvent(event.deviceId));
}
} catch (e) {
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) {
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) {
case 'switch_1':
return deviceStatus.switch1;
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
return deviceStatus.switch2;
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
case 'switch_3':
return deviceStatus.switch3;
default:
return false;
deviceStatus = deviceStatus.copyWith(switch3: value);
break;
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
}

View File

@ -1,7 +1,10 @@
part of 'three_gang_glass_switch_bloc.dart';
@immutable
abstract class ThreeGangGlassSwitchEvent {}
abstract class ThreeGangGlassSwitchEvent extends Equatable {
@override
List<Object?> get props => [];
}
class ThreeGangGlassSwitchFetchDeviceEvent extends ThreeGangGlassSwitchEvent {
final String deviceId;
@ -19,6 +22,9 @@ class ThreeGangGlassSwitchControl extends ThreeGangGlassSwitchEvent {
required this.code,
required this.value,
});
@override
List<Object?> get props => [deviceId, code, value];
}
class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
@ -31,6 +37,9 @@ class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
required this.code,
required this.value,
});
@override
List<Object?> get props => [deviceIds, code, value];
}
class ThreeGangGlassSwitchFetchBatchStatusEvent
@ -38,6 +47,9 @@ class ThreeGangGlassSwitchFetchBatchStatusEvent
final List<String> deviceIds;
ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
@override
List<Object?> get props => [deviceIds];
}
class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
@ -48,6 +60,9 @@ class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
required this.deviceId,
required this.factoryReset,
});
@override
List<Object?> get props => [deviceId, factoryReset];
}
class StatusUpdated extends ThreeGangGlassSwitchEvent {

View File

@ -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(),
);
}
}

View File

@ -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/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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ThreeGangGlassSwitchBloc(deviceId: deviceIds.first)
create: (context) => ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
builder: (context, state) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/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/factories/three_gang_glass_switch_bloc_factory.dart';
import 'package:syncrow_web/utils/constants/assets.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) {
return BlocProvider(
create: (context) =>
ThreeGangGlassSwitchBloc(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
builder: (context, state) {
if (state is ThreeGangGlassSwitchLoading) {

View File

@ -1,12 +1,14 @@
// ignore_for_file: invalid_use_of_visible_for_testing_member
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'dart:developer';
import 'package:equatable/equatable.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/factory_reset_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';
part 'living_room_event.dart';
@ -15,9 +17,14 @@ part 'living_room_state.dart';
class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
late LivingRoomStatusModel deviceStatus;
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<LivingRoomControl>(_livingRoomControl);
on<LivingRoomBatchControl>(_livingRoomBatchControl);
@ -26,156 +33,108 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
on<StatusUpdated>(_onStatusUpdated);
}
FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event,
Emitter<LivingRoomState> emit) async {
Future<void> _onFetchDeviceStatus(
LivingRoomFetchDeviceStatusEvent event,
Emitter<LivingRoomState> emit,
) async {
emit(LivingRoomDeviceStatusLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
LivingRoomStatusModel.fromJson(event.deviceId, status.status);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
_listenToChanges(deviceId);
deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status);
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) {
emit(LivingRoomDeviceManagementError(e.toString()));
}
}
FutureOr<void> _livingRoomControl(
LivingRoomControl event, Emitter<LivingRoomState> emit) async {
final oldValue = _getValueByCode(event.code);
void _listenToChanges(String deviceId) {
try {
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);
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(LivingRoomDeviceManagementError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required dynamic value,
required dynamic oldValue,
required Emitter<LivingRoomState> 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) {
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
Emitter<LivingRoomState> emit) {
_updateLocalValue(code, oldValue);
Future<void> _livingRoomBatchControl(
LivingRoomBatchControl event,
Emitter<LivingRoomState> emit,
) async {
emit(LivingRoomDeviceStatusLoading());
_updateLocalValue(event.code, event.value);
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, dynamic value) {
switch (code) {
case 'switch_1':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(switch1: value);
}
break;
case 'switch_2':
if (value is bool) {
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;
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.devicesIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(LivingRoomDeviceManagementError(e.toString()));
}
}
FutureOr<void> _livingRoomFetchBatchControl(
LivingRoomFetchBatchEvent event, Emitter<LivingRoomState> emit) async {
Future<void> _livingRoomFetchBatchControl(
LivingRoomFetchBatchEvent event,
Emitter<LivingRoomState> emit,
) async {
emit(LivingRoomDeviceStatusLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
// for (var deviceId in event.devicesIds) {
// _listenToChanges(deviceId);
// }
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
} catch (e) {
emit(LivingRoomDeviceManagementError(e.toString()));
}
}
FutureOr<void> _livingRoomBatchControl(
LivingRoomBatchControl event, Emitter<LivingRoomState> emit) async {
final oldValue = _getValueByCode(event.code);
_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 {
Future<void> _livingRoomFactoryReset(
LivingRoomFactoryResetEvent event,
Emitter<LivingRoomState> emit,
) async {
emit(LivingRoomDeviceStatusLoading());
try {
final response = await DevicesManagementApi().factoryReset(
@ -183,42 +142,28 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
event.uuid,
);
if (!response) {
emit(const LivingRoomDeviceManagementError('Failed'));
emit(const LivingRoomDeviceManagementError('Failed to reset device'));
} else {
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
add(LivingRoomFetchDeviceStatusEvent(event.uuid));
}
} catch (e) {
emit(LivingRoomDeviceManagementError(e.toString()));
}
}
_listenToChanges(deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
Stream<DatabaseEvent> stream = ref.onValue;
void _updateLocalValue(String code, dynamic value) {
if (value is! bool) return;
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 =
LivingRoomStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<LivingRoomState> emit) {
deviceStatus = event.deviceStatus;
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
switch (code) {
case 'switch_1':
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
case 'switch_3':
deviceStatus = deviceStatus.copyWith(switch3: value);
break;
}
}
}

View File

@ -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(),
);
}
}

View File

@ -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/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/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/shared/toggle_widget.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) {
return BlocProvider(
create: (context) =>
LivingRoomBloc(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
LivingRoomBlocFactory.create(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
builder: (context, state) {
if (state is LivingRoomDeviceStatusLoading) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.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/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/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -14,7 +15,7 @@ class LivingRoomDeviceControlsView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LivingRoomBloc(deviceId: deviceId)
create: (context) => LivingRoomBlocFactory.create(deviceId: deviceId)
..add(LivingRoomFetchDeviceStatusEvent(deviceId)),
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
builder: (context, state) {

View File

@ -1,26 +1,33 @@
import 'dart:async';
import 'dart:developer';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/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/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart';
part 'two_gang_glass_switch_event.dart';
part 'two_gang_glass_switch_state.dart';
class TwoGangGlassSwitchBloc
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
TwoGangGlassStatusModel deviceStatus;
Timer? _timer;
TwoGangGlassSwitchBloc({required String deviceId})
: deviceStatus = TwoGangGlassStatusModel(
uuid: deviceId,
switch1: false,
countDown1: 0,
switch2: false,
countDown2: 0),
super(TwoGangGlassSwitchInitial()) {
final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
late TwoGangGlassStatusModel deviceStatus;
TwoGangGlassSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(TwoGangGlassSwitchInitial()) {
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<TwoGangGlassSwitchControl>(_onControl);
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
@ -29,14 +36,14 @@ class TwoGangGlassSwitchBloc
on<StatusUpdated>(_onStatusUpdated);
}
Future<void> _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event,
Emitter<TwoGangGlassSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(
TwoGangGlassSwitchFetchDeviceEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
} catch (e) {
@ -46,200 +53,121 @@ class TwoGangGlassSwitchBloc
void _listenToChanges(String deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
ref.onValue.listen((DatabaseEvent event) {
if (event.snapshot.value == null) return;
final ref = FirebaseDatabase.instance.ref(
'device-status/$deviceId',
);
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 = [];
data['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
eventsMap['status'].forEach((element) {
statusList.add(Status(code: element['code'], value: element['value']));
});
// Parse the new status and add the event
final updatedStatus =
TwoGangGlassStatusModel.fromJson(data['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(updatedStatus));
}
deviceStatus = TwoGangGlassStatusModel.fromJson(deviceId, statusList);
add(StatusUpdated(deviceStatus));
});
} catch (e) {
// Handle errors and emit an error state if necessary
if (!isClosed) {
// add(TwoGangGlassSwitchError('Error listening to updates: $e'));
}
} catch (_) {
log(
'Error listening to changes',
name: 'TwoGangGlassSwitchBloc._listenToChanges',
);
}
}
Future<void> _onControl(TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onControl(
TwoGangGlassSwitchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onBatchControl(TwoGangGlassSwitchBatchControl event,
Emitter<TwoGangGlassSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
Future<void> _onBatchControl(
TwoGangGlassSwitchBatchControl event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceIds,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFetchBatchStatus(
TwoGangGlassSwitchFetchBatchStatusEvent event,
Emitter<TwoGangGlassSwitchState> emit) async {
TwoGangGlassSwitchFetchBatchStatusEvent event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.deviceIds);
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
deviceStatus = TwoGangGlassStatusModel.fromJson(
event.deviceIds.first, status.status);
event.deviceIds.first,
status.status,
);
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _onFactoryReset(TwoGangGlassFactoryReset event,
Emitter<TwoGangGlassSwitchState> emit) async {
Future<void> _onFactoryReset(
TwoGangGlassFactoryReset event,
Emitter<TwoGangGlassSwitchState> emit,
) async {
emit(TwoGangGlassSwitchLoading());
try {
final response = await DevicesManagementApi()
.factoryReset(event.factoryReset, event.deviceId);
final response = await DevicesManagementApi().factoryReset(
event.factoryReset,
event.deviceId,
);
if (!response) {
emit(TwoGangGlassSwitchError('Failed'));
emit(TwoGangGlassSwitchError('Failed to reset device'));
} else {
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
}
} catch (e) {
emit(TwoGangGlassSwitchError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
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);
void _onStatusUpdated(
StatusUpdated event,
Emitter<TwoGangGlassSwitchState> emit,
) {
deviceStatus = event.deviceStatus;
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
}
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) {
case 'switch_1':
return deviceStatus.switch1;
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
return deviceStatus.switch2;
default:
return false;
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
}
}
@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));
}
}

View File

@ -1,12 +1,17 @@
part of 'two_gang_glass_switch_bloc.dart';
@immutable
abstract class TwoGangGlassSwitchEvent {}
abstract class TwoGangGlassSwitchEvent extends Equatable {
const TwoGangGlassSwitchEvent();
}
class TwoGangGlassSwitchFetchDeviceEvent extends TwoGangGlassSwitchEvent {
final String deviceId;
TwoGangGlassSwitchFetchDeviceEvent(this.deviceId);
const TwoGangGlassSwitchFetchDeviceEvent(this.deviceId);
@override
List<Object> get props => [deviceId];
}
class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
@ -14,11 +19,14 @@ class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
final String code;
final bool value;
TwoGangGlassSwitchControl({
const TwoGangGlassSwitchControl({
required this.deviceId,
required this.code,
required this.value,
});
@override
List<Object> get props => [deviceId, code, value];
}
class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
@ -26,33 +34,43 @@ class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
final String code;
final bool value;
TwoGangGlassSwitchBatchControl({
const TwoGangGlassSwitchBatchControl({
required this.deviceIds,
required this.code,
required this.value,
});
@override
List<Object> get props => [deviceIds, code, value];
}
class TwoGangGlassSwitchFetchBatchStatusEvent extends TwoGangGlassSwitchEvent {
final List<String> deviceIds;
TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
const TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
@override
List<Object> get props => [deviceIds];
}
class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent {
final String deviceId;
final FactoryResetModel factoryReset;
TwoGangGlassFactoryReset({
const TwoGangGlassFactoryReset({
required this.deviceId,
required this.factoryReset,
});
@override
List<Object> get props => [deviceId, factoryReset];
}
class StatusUpdated extends TwoGangGlassSwitchEvent {
final TwoGangGlassStatusModel deviceStatus;
StatusUpdated(this.deviceStatus);
const StatusUpdated(this.deviceStatus);
@override
List<Object> get props => [deviceStatus];
}

View File

@ -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(),
);
}
}

View File

@ -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/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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +17,7 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceIds.first)
create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/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/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/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -15,7 +16,7 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceId)
create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceId)
..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) {

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:developer';
import 'package:firebase_database/firebase_database.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_state.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';
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<TwoGangSwitchControl>(_onControl);
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
@ -18,16 +31,13 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
on<StatusUpdated>(_onStatusUpdated);
}
late TwoGangStatusModel deviceStatus;
final String deviceId;
Timer? _timer;
FutureOr<void> _onFetchDeviceStatus(TwoGangSwitchFetchDeviceEvent event,
Emitter<TwoGangSwitchState> emit) async {
Future<void> _onFetchDeviceStatus(
TwoGangSwitchFetchDeviceEvent event,
Emitter<TwoGangSwitchState> emit,
) async {
emit(TwoGangSwitchLoading());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId);
emit(TwoGangSwitchStatusLoaded(deviceStatus));
@ -36,131 +46,91 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
}
}
FutureOr<void> _onControl(
TwoGangSwitchControl event, Emitter<TwoGangSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
void _listenToChanges(String deviceId) {
try {
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);
emit(TwoGangSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangSwitchError(e.toString()));
}
}
Future<void> _runDebounce({
required dynamic deviceId,
required String code,
required bool value,
required bool oldValue,
required Emitter<TwoGangSwitchState> 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<TwoGangSwitchState> emit) {
_updateLocalValue(code, oldValue);
Future<void> _onBatchControl(
TwoGangSwitchBatchControl event,
Emitter<TwoGangSwitchState> emit,
) async {
emit(TwoGangSwitchLoading());
_updateLocalValue(event.code, event.value);
emit(TwoGangSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) {
if (code == 'switch_1') {
deviceStatus = deviceStatus.copyWith(switch1: value);
}
if (code == 'switch_2') {
deviceStatus = deviceStatus.copyWith(switch2: value);
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceId,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangSwitchError(e.toString()));
}
}
bool _getValueByCode(String code) {
switch (code) {
case 'switch_1':
return deviceStatus.switch1;
case 'switch_2':
return deviceStatus.switch2;
default:
return false;
}
}
Future<void> _onFetchBatchStatus(TwoGangSwitchFetchBatchEvent event,
Emitter<TwoGangSwitchState> emit) async {
Future<void> _onFetchBatchStatus(
TwoGangSwitchFetchBatchEvent event,
Emitter<TwoGangSwitchState> emit,
) async {
emit(TwoGangSwitchLoading());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus =
TwoGangStatusModel.fromJson(event.devicesIds.first, status.status);
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = TwoGangStatusModel.fromJson(
event.devicesIds.first,
status.status,
);
emit(TwoGangSwitchStatusLoaded(deviceStatus));
} catch (e) {
emit(TwoGangSwitchError(e.toString()));
}
}
@override
Future<void> close() {
_timer?.cancel();
return super.close();
}
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 {
Future<void> _onFactoryReset(
TwoGangFactoryReset event,
Emitter<TwoGangSwitchState> emit,
) async {
emit(TwoGangSwitchLoading());
try {
final response = await DevicesManagementApi().factoryReset(
@ -168,42 +138,31 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
event.deviceId,
);
if (!response) {
emit(TwoGangSwitchError('Failed'));
emit(TwoGangSwitchError('Failed to reset device'));
} else {
emit(TwoGangSwitchStatusLoaded(deviceStatus));
add(TwoGangSwitchFetchDeviceEvent(event.deviceId));
}
} catch (e) {
emit(TwoGangSwitchError(e.toString()));
}
}
_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 =
TwoGangStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<TwoGangSwitchState> emit) {
void _onStatusUpdated(
StatusUpdated event,
Emitter<TwoGangSwitchState> emit,
) {
deviceStatus = event.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;
}
}
}

View File

@ -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(),
);
}
}

View File

@ -24,16 +24,16 @@ class TwoGangStatusModel {
for (var status in jsonList) {
switch (status.code) {
case 'switch_1':
switch1 = status.value ?? false;
switch1 = bool.tryParse(status.value.toString()) ?? false;
break;
case 'countdown_1':
countDown = status.value ?? 0;
countDown = int.tryParse(status.value.toString()) ?? 0;
break;
case 'switch_2':
switch2 = status.value ?? false;
switch2 = bool.tryParse(status.value.toString()) ?? false;
break;
case 'countdown_2':
countDown2 = status.value ?? 0;
countDown2 = int.tryParse(status.value.toString()) ?? 0;
break;
}
}

View File

@ -2,11 +2,11 @@ import 'package:flutter/material.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/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/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_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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -18,7 +18,7 @@ class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayou
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first)
create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(TwoGangSwitchFetchBatchEvent(deviceIds)),
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
builder: (context, state) {

View File

@ -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_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/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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -15,7 +16,7 @@ class TwoGangDeviceControlView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TwoGangSwitchBloc(deviceId: deviceId)
create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceId)
..add(TwoGangSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
builder: (context, state) {

View File

@ -1,18 +1,28 @@
import 'dart:async';
import 'dart:developer';
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/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/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';
class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
final String deviceId;
late WallSensorModel deviceStatus;
Timer? _timer;
final ControlDeviceService controlDeviceService;
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<WallSensorFetchBatchStatusEvent>(_fetchWallSensorBatchControl);
on<WallSensorChangeValueEvent>(_changeValue);
@ -24,28 +34,28 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
}
void _fetchWallSensorStatus(
WallSensorFetchStatusEvent event, Emitter<WallSensorState> emit) async {
Future<void> _fetchWallSensorStatus(
WallSensorFetchStatusEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingInitialState());
try {
var response = await DevicesManagementApi().getDeviceStatus(deviceId);
final response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
_listenToChanges(deviceId);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} catch (e) {
emit(WallSensorFailedState(error: e.toString()));
return;
}
}
// Fetch batch status
FutureOr<void> _fetchWallSensorBatchControl(
WallSensorFetchBatchStatusEvent event,
Emitter<WallSensorState> emit) async {
Future<void> _fetchWallSensorBatchControl(
WallSensorFetchBatchStatusEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingInitialState());
try {
var response =
await DevicesManagementApi().getBatchStatus(event.devicesIds);
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} catch (e) {
@ -54,132 +64,105 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
}
void _listenToChanges(String deviceId) {
DatabaseReference 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;
try {
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
final statusList = (data['status'] as List?)
?.map((e) => Status(code: e['code'], value: e['value']))
.toList();
ref.onValue.listen((event) {
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
if (statusList != null) {
final updatedDeviceStatus = WallSensorModel.fromJson(statusList);
List<Status> statusList = [];
eventsMap['status'].forEach((element) {
statusList.add(
Status(code: element['code'], value: element['value']),
);
});
deviceStatus = WallSensorModel.fromJson(statusList);
if (!isClosed) {
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus));
add(WallSensorRealtimeUpdateEvent(deviceStatus));
}
}
});
});
} catch (_) {
log(
'Error listening to changes',
name: 'WallSensorBloc._listenToChanges',
);
}
}
void _changeValue(
WallSensorChangeValueEvent event, Emitter<WallSensorState> emit) async {
Future<void> _changeValue(
WallSensorChangeValueEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
if (event.code == 'far_detection') {
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;
}
_updateLocalValue(event.code, event.value);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: deviceId,
code: event.code,
value: event.value,
isBatch: false,
emit: emit,
);
try {
await controlDeviceService.controlDevice(
deviceUuid: deviceId,
status: Status(code: event.code, value: event.value),
);
} catch (e) {
_updateLocalValue(event.code, event.value == 0 ? 1 : 0);
emit(WallSensorFailedState(error: e.toString()));
}
}
Future<void> _onBatchControl(
WallSensorBatchControlEvent event, Emitter<WallSensorState> emit) async {
WallSensorBatchControlEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
if (event.code == 'far_detection') {
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;
}
_updateLocalValue(event.code, event.value);
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 {
// await DevicesManagementApi.getDeviceReportsByDate(
// deviceId, event.code, from.toString(), to.toString())
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
.then((value) {
emit(DeviceReportsState(deviceReport: value, code: event.code));
});
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code,
value: event.value,
);
} 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) {
emit(DeviceReportsFailedState(error: e.toString()));
return;
}
}
void _showDescription(
ShowDescriptionEvent event, Emitter<WallSensorState> emit) {
ShowDescriptionEvent event,
Emitter<WallSensorState> emit,
) {
emit(WallSensorShowDescriptionState(description: event.description));
}
void _backToGridView(
BackToGridViewEvent event, Emitter<WallSensorState> emit) {
BackToGridViewEvent event,
Emitter<WallSensorState> emit,
) {
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
}
FutureOr<void> _onFactoryReset(
WallSensorFactoryResetEvent event, Emitter<WallSensorState> emit) async {
Future<void> _onFactoryReset(
WallSensorFactoryResetEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
try {
final response = await DevicesManagementApi().factoryReset(
@ -187,9 +170,9 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
event.deviceId,
);
if (!response) {
emit(const WallSensorFailedState(error: 'Failed'));
emit(const WallSensorFailedState(error: 'Failed to reset device'));
} else {
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
add(WallSensorFetchStatusEvent());
}
} catch (e) {
emit(WallSensorFailedState(error: e.toString()));
@ -200,7 +183,23 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
WallSensorRealtimeUpdateEvent event,
Emitter<WallSensorState> emit,
) {
deviceStatus = event.deviceStatus;
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
emit(WallSensorUpdateState(wallSensorModel: event.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;
}
}
}

View File

@ -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(),
);
}
}

View File

@ -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_event.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/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -21,7 +22,7 @@ class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLa
final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context);
return BlocProvider(
create: (context) => WallSensorBloc(deviceId: devicesIds.first)
create: (context) => WallSensorBlocFactory.create(deviceId: devicesIds.first)
..add(WallSensorFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<WallSensorBloc, WallSensorState>(
builder: (context, state) {

View File

@ -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_status.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/utils/constants/assets.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);
return BlocProvider(
create: (context) =>
WallSensorBloc(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()),
WallSensorBlocFactory.create(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()),
child: BlocBuilder<WallSensorBloc, WallSensorState>(
builder: (context, state) {
if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) {

View File

@ -1,5 +1,3 @@
// water_heater_bloc.dart
import 'dart:async';
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_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/utils/format_date_time.dart';
@ -17,7 +17,17 @@ part 'water_heater_event.dart';
part 'water_heater_state.dart';
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<ToggleWaterHeaterEvent>(_controlWaterHeater);
on<FetchWaterHeaterBatchStatusEvent>(_batchFetchWaterHeater);
@ -29,7 +39,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
on<UpdateSelectedTimeEvent>(_updateSelectedTime);
on<UpdateSelectedDayEvent>(_updateSelectedDay);
on<UpdateFunctionOnEvent>(_updateFunctionOn);
on<GetSchedulesEvent>(_getSchedule);
on<AddScheduleEvent>(_onAddSchedule);
on<EditWaterHeaterScheduleEvent>(_onEditSchedule);
@ -38,11 +47,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
on<StatusUpdated>(_onStatusUpdated);
}
late WaterHeaterStatusModel deviceStatus;
Timer? _countdownTimer;
// Timer? _inchingTimer;
FutureOr<void> _initializeAddSchedule(
void _initializeAddSchedule(
InitializeAddScheduleEvent event,
Emitter<WaterHeaterState> emit,
) {
@ -64,7 +69,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
FutureOr<void> _updateSelectedTime(
void _updateSelectedTime(
UpdateSelectedTimeEvent event,
Emitter<WaterHeaterState> emit,
) {
@ -73,7 +78,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
emit(currentState.copyWith(selectedTime: event.selectedTime));
}
FutureOr<void> _updateSelectedDay(
void _updateSelectedDay(
UpdateSelectedDayEvent event,
Emitter<WaterHeaterState> emit,
) {
@ -84,7 +89,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
selectedDays: updatedDays, selectedTime: currentState.selectedTime));
}
FutureOr<void> _updateFunctionOn(
void _updateFunctionOn(
UpdateFunctionOnEvent event,
Emitter<WaterHeaterState> emit,
) {
@ -93,16 +98,18 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
functionOn: event.isOn, selectedTime: currentState.selectedTime));
}
FutureOr<void> _updateScheduleEvent(
Future<void> _updateScheduleEvent(
UpdateScheduleEvent event,
Emitter<WaterHeaterState> emit,
) async {
final currentState = state;
if (currentState is WaterHeaterDeviceStatusLoaded) {
if (event.scheduleMode == ScheduleModes.schedule) {
emit(currentState.copyWith(
scheduleMode: ScheduleModes.schedule,
));
emit(
currentState.copyWith(
scheduleMode: ScheduleModes.schedule,
),
);
}
if (event.scheduleMode == ScheduleModes.countdown) {
final countdownRemaining =
@ -116,87 +123,88 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
countdownRemaining: countdownRemaining,
));
if (!currentState.isCountdownActive! &&
countdownRemaining > Duration.zero) {
if (!currentState.isCountdownActive! && countdownRemaining > Duration.zero) {
_startCountdownTimer(emit, countdownRemaining);
}
} else if (event.scheduleMode == ScheduleModes.inching) {
final inchingDuration =
Duration(hours: event.hours, minutes: event.minutes);
final inchingDuration = Duration(hours: event.hours, minutes: event.minutes);
emit(currentState.copyWith(
scheduleMode: ScheduleModes.inching,
inchingHours: inchingDuration.inHours,
inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: currentState.isInchingActive,
));
emit(
currentState.copyWith(
scheduleMode: ScheduleModes.inching,
inchingHours: inchingDuration.inHours,
inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: currentState.isInchingActive,
),
);
}
}
}
FutureOr<void> _controlWaterHeater(
Future<void> _controlWaterHeater(
ToggleWaterHeaterEvent event,
Emitter<WaterHeaterState> emit,
) async {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(currentState.copyWith(
status: deviceStatus,
));
emit(
currentState.copyWith(
status: deviceStatus,
),
);
final success = await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: false,
final success = await controlDeviceService.controlDevice(
deviceUuid: event.deviceId,
status: Status(
code: event.code,
value: event.value,
),
);
if (success) {
if (event.code == "countdown_1") {
final countdownDuration = Duration(seconds: event.value);
emit(currentState.copyWith(
countdownHours: countdownDuration.inHours,
countdownMinutes: countdownDuration.inMinutes % 60,
countdownRemaining: countdownDuration,
isCountdownActive: true,
));
emit(
currentState.copyWith(
countdownHours: countdownDuration.inHours,
countdownMinutes: countdownDuration.inMinutes % 60,
countdownRemaining: countdownDuration,
isCountdownActive: true,
),
);
if (countdownDuration.inSeconds > 0) {
_startCountdownTimer(emit, countdownDuration);
} else {
_countdownTimer?.cancel();
emit(currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
countdownRemaining: Duration.zero,
isCountdownActive: false,
));
emit(
currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
countdownRemaining: Duration.zero,
isCountdownActive: false,
),
);
}
} else if (event.code == "switch_inching") {
final inchingDuration = Duration(seconds: event.value);
//if (inchingDuration.inSeconds > 0) {
// _startInchingTimer(emit, inchingDuration);
// } else {
emit(currentState.copyWith(
inchingHours: inchingDuration.inHours,
inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: true,
));
// }
emit(
currentState.copyWith(
inchingHours: inchingDuration.inHours,
inchingMinutes: inchingDuration.inMinutes % 60,
isInchingActive: true,
),
);
}
}
}
}
FutureOr<void> _stopScheduleEvent(
Future<void> _stopScheduleEvent(
StopScheduleEvent event,
Emitter<WaterHeaterState> emit,
) async {
@ -207,25 +215,28 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
_countdownTimer?.cancel();
if (isCountDown) {
emit(currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
countdownRemaining: Duration.zero,
isCountdownActive: false,
));
emit(
currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
countdownRemaining: Duration.zero,
isCountdownActive: false,
),
);
} else if (currentState.scheduleMode == ScheduleModes.inching) {
emit(currentState.copyWith(
inchingHours: 0,
inchingMinutes: 0,
isInchingActive: false,
));
emit(
currentState.copyWith(
inchingHours: 0,
inchingMinutes: 0,
isInchingActive: false,
),
);
}
try {
final status = await DevicesManagementApi().deviceControl(
event.deviceId,
Status(
code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
Status(code: isCountDown ? 'countdown_1' : 'switch_inching', value: 0),
);
if (!status) {
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,
Emitter<WaterHeaterState> emit,
) async {
emit(WaterHeaterLoadingState());
try {
final status =
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus =
WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = WaterHeaterStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.scheduleMode == ScheduleModes.countdown) {
final countdownRemaining = Duration(
@ -288,7 +297,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
inchingMinutes: deviceStatus.inchingMinutes,
isInchingActive: true,
));
//_startInchingTimer(emit, inchingDuration);
} else {
emit(WaterHeaterDeviceStatusLoaded(
deviceStatus,
@ -316,7 +324,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
_listenToChanges(deviceId) {
void _listenToChanges(deviceId) {
try {
DatabaseReference ref =
FirebaseDatabase.instance.ref('device-status/$deviceId');
@ -328,12 +336,11 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
statusList.add(Status(code: element['code'], value: element['value']));
});
deviceStatus = WaterHeaterStatusModel.fromJson(
usersMap['productUuid'], statusList);
deviceStatus =
WaterHeaterStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
@ -341,7 +348,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<WaterHeaterState> emit) {
void _onStatusUpdated(
StatusUpdated event,
Emitter<WaterHeaterState> emit,
) {
deviceStatus = event.deviceStatus;
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
}
@ -352,23 +362,13 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
) {
_countdownTimer?.cancel();
_countdownTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
add(DecrementCountdownEvent());
});
_countdownTimer = Timer.periodic(
const Duration(minutes: 1),
(timer) => add(DecrementCountdownEvent()),
);
}
// void _startInchingTimer(
// Emitter<WaterHeaterState> emit,
// Duration inchingDuration,
// ) {
// _inchingTimer?.cancel();
// _inchingTimer = Timer.periodic(const Duration(minutes: 1), (timer) {
// add(DecrementInchingEvent());
// });
// }
_onDecrementCountdown(
void _onDecrementCountdown(
DecrementCountdownEvent event,
Emitter<WaterHeaterState> emit,
) {
@ -382,105 +382,29 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
if (newRemaining <= Duration.zero) {
_countdownTimer?.cancel();
emit(currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
isCountdownActive: false,
countdownRemaining: Duration.zero,
));
emit(
currentState.copyWith(
countdownHours: 0,
countdownMinutes: 0,
isCountdownActive: false,
countdownRemaining: Duration.zero,
),
);
return;
}
int totalSeconds = newRemaining.inSeconds;
final totalSeconds = newRemaining.inSeconds;
final newHours = totalSeconds ~/ 3600;
final newMinutes = (totalSeconds % 3600) ~/ 60;
int newHours = totalSeconds ~/ 3600;
int newMinutes = (totalSeconds % 3600) ~/ 60;
emit(currentState.copyWith(
countdownHours: newHours,
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),
emit(
currentState.copyWith(
countdownHours: newHours,
countdownMinutes: newMinutes,
countdownRemaining: newRemaining,
),
);
}
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) {
switch (code) {
case 'switch_1':
return deviceStatus.heaterSwitch;
case 'countdown_1':
return deviceStatus.countdownHours * 60 + deviceStatus.countdownMinutes;
default:
return null;
}
return switch (code) {
'switch_1' => deviceStatus.heaterSwitch,
'countdown_1' =>
(deviceStatus.countdownHours * 60) + deviceStatus.countdownMinutes,
_ => null,
};
}
@override
@ -521,13 +443,17 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
return super.close();
}
FutureOr<void> _getSchedule(
GetSchedulesEvent event, Emitter<WaterHeaterState> emit) async {
Future<void> _getSchedule(
GetSchedulesEvent event,
Emitter<WaterHeaterState> emit,
) async {
emit(ScheduleLoadingState());
try {
List<ScheduleModel> schedules = await DevicesManagementApi()
.getDeviceSchedules(deviceStatus.uuid, event.category);
final schedules = await DevicesManagementApi().getDeviceSchedules(
deviceStatus.uuid,
event.category,
);
emit(WaterHeaterDeviceStatusLoaded(
deviceStatus,
@ -535,7 +461,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
scheduleMode: ScheduleModes.schedule,
));
} catch (e) {
//(const WaterHeaterFailedState(error: 'Failed to fetch schedules.'));
emit(WaterHeaterDeviceStatusLoaded(
deviceStatus,
schedules: const [],
@ -543,7 +468,7 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
FutureOr<void> _onAddSchedule(
Future<void> _onAddSchedule(
AddScheduleEvent event,
Emitter<WaterHeaterState> emit,
) async {
@ -557,8 +482,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
// emit(ScheduleLoadingState());
bool success = await DevicesManagementApi()
.addScheduleRecord(newSchedule, currentState.status.uuid);
@ -566,13 +489,14 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
} else {
emit(currentState);
//emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
}
}
}
FutureOr<void> _onEditSchedule(EditWaterHeaterScheduleEvent event,
Emitter<WaterHeaterState> emit) async {
Future<void> _onEditSchedule(
EditWaterHeaterScheduleEvent event,
Emitter<WaterHeaterState> emit,
) async {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
@ -584,8 +508,6 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
);
// emit(ScheduleLoadingState());
bool success = await DevicesManagementApi().editScheduleRecord(
currentState.status.uuid,
newSchedule,
@ -595,12 +517,11 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
add(GetSchedulesEvent(category: 'switch_1', uuid: deviceStatus.uuid));
} else {
emit(currentState);
//emit(const WaterHeaterFailedState(error: 'Failed to add schedule.'));
}
}
}
FutureOr<void> _onUpdateSchedule(
Future<void> _onUpdateSchedule(
UpdateScheduleEntryEvent event,
Emitter<WaterHeaterState> emit,
) async {
@ -627,20 +548,17 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
emit(currentState.copyWith(schedules: updatedSchedules));
} else {
emit(currentState);
// emit(const WaterHeaterFailedState(error: 'Failed to update schedule.'));
}
}
}
FutureOr<void> _onDeleteSchedule(
Future<void> _onDeleteSchedule(
DeleteScheduleEvent event,
Emitter<WaterHeaterState> emit,
) async {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
// emit(ScheduleLoadingState());
bool success = await DevicesManagementApi()
.deleteScheduleRecord(currentState.status.uuid, event.scheduleId);
@ -652,20 +570,22 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
emit(currentState.copyWith(schedules: updatedSchedules));
} else {
emit(currentState);
// emit(const WaterHeaterFailedState(error: 'Failed to delete schedule.'));
}
}
}
FutureOr<void> _batchFetchWaterHeater(FetchWaterHeaterBatchStatusEvent event,
Emitter<WaterHeaterState> emit) async {
Future<void> _batchFetchWaterHeater(
FetchWaterHeaterBatchStatusEvent event,
Emitter<WaterHeaterState> emit,
) async {
emit(WaterHeaterLoadingState());
try {
final status =
await DevicesManagementApi().getBatchStatus(event.devicesUuid);
deviceStatus = WaterHeaterStatusModel.fromJson(
event.devicesUuid.first, status.status);
final status = await DevicesManagementApi().getBatchStatus(
event.devicesUuid,
);
deviceStatus =
WaterHeaterStatusModel.fromJson(event.devicesUuid.first, status.status);
emit(WaterHeaterDeviceStatusLoaded(deviceStatus));
} catch (e) {
@ -673,8 +593,8 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
}
}
FutureOr<void> _batchControlWaterHeater(ControlWaterHeaterBatchEvent event,
Emitter<WaterHeaterState> emit) async {
Future<void> _batchControlWaterHeater(
ControlWaterHeaterBatchEvent event, Emitter<WaterHeaterState> emit) async {
if (state is WaterHeaterDeviceStatusLoaded) {
final currentState = state as WaterHeaterDeviceStatusLoaded;
@ -686,13 +606,10 @@ class WaterHeaterBloc extends Bloc<WaterHeaterEvent, WaterHeaterState> {
status: deviceStatus,
));
final success = await _runDebounce(
deviceId: event.devicesUuid,
final success = await batchControlDevicesService.batchControlDevices(
uuids: event.devicesUuid,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
if (success) {

View File

@ -1,5 +1,3 @@
// water_heater_state.dart
part of 'water_heater_bloc.dart';
sealed class WaterHeaterState extends Equatable {

View File

@ -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(),
);
}
}

View File

@ -1,15 +1,16 @@
import 'package:flutter/material.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/firmware_update.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/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/utils/constants/assets.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});
final List<String> deviceIds;
@ -17,8 +18,9 @@ class WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveL
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
WaterHeaterBloc()..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
create: (context) => WaterHeaterBlocFactory.create(
deviceId: deviceIds.first,
)..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
builder: (context, state) {
if (state is WaterHeaterLoadingState) {

View File

@ -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/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/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/widgets/schedual_view.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@ -21,8 +22,9 @@ class WaterHeaterDeviceControlView extends StatelessWidget
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
WaterHeaterBloc()..add(WaterHeaterFetchStatusEvent(device.uuid!)),
create: (context) => WaterHeaterBlocFactory.create(
deviceId: device.uuid ?? '',
)..add(WaterHeaterFetchStatusEvent(device.uuid!)),
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
builder: (context, state) {
if (state is WaterHeaterLoadingState) {
@ -33,8 +35,7 @@ class WaterHeaterDeviceControlView extends StatelessWidget
state is WaterHeaterBatchFailedState) {
return const Center(child: Text('Error fetching status'));
} else {
return const SizedBox(
height: 200, child: Center(child: SizedBox()));
return const SizedBox(height: 200, child: Center(child: SizedBox()));
}
},
));

View File

@ -5,7 +5,7 @@ import 'package:syncrow_web/utils/constants/assets.dart';
Future<void> showNameMenu({
required BuildContext context,
Function()? aToZTap,
Function()? zToaTap,
Function()? zToATap,
String? isSelected,
}) async {
final RenderBox overlay =
@ -46,7 +46,7 @@ Future<void> showNameMenu({
),
),
PopupMenuItem(
onTap: zToaTap,
onTap: zToATap,
child: ListTile(
leading: Image.asset(
Assets.ZtoAIcon,

View File

@ -95,7 +95,7 @@ class _TableRow extends StatelessWidget {
],
),
if (!isLast)
Divider(
const Divider(
height: 1,
thickness: 1,
color: ColorsManager.boxDivider,
@ -110,12 +110,14 @@ class DynamicTableScreen extends StatefulWidget {
final List<String> titles;
final List<List<Widget>> rows;
final void Function(int columnIndex)? onFilter;
final double tableSize;
const DynamicTableScreen({
required this.titles,
required this.rows,
required this.onFilter,
Key? key,
required this.tableSize,
}) : super(key: key);
@override
@ -205,7 +207,8 @@ class _DynamicTableScreenState extends State<DynamicTableScreen> {
bottomRight: Radius.circular(15),
),
),
child: Column(
child: ListView(
shrinkWrap: true,
children: [
for (int rowIndex = 0; rowIndex < widget.rows.length; rowIndex++)
_TableRow(
@ -253,7 +256,7 @@ class _DynamicTableScreenState extends State<DynamicTableScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(),
_buildBody(),
Container(height: widget.tableSize - 37, child: _buildBody()),
],
),
),

View File

@ -27,7 +27,8 @@ class UsersPage extends StatelessWidget {
Widget build(BuildContext context) {
final TextEditingController searchController = TextEditingController();
Widget actionButton({bool isActive = false, required String title, Function()? onTap}) {
Widget actionButton(
{bool isActive = false, required String title, Function()? onTap}) {
return InkWell(
onTap: onTap,
child: Padding(
@ -60,7 +61,8 @@ class UsersPage extends StatelessWidget {
: ColorsManager.disabledPink.withOpacity(0.5),
),
child: Padding(
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5),
padding:
const EdgeInsets.only(left: 10, right: 10, bottom: 5, top: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
@ -84,12 +86,15 @@ class UsersPage extends StatelessWidget {
}
Widget changeIconStatus(
{required String userId, required String status, required Function()? onTap}) {
{required String userId,
required String status,
required Function()? onTap}) {
return Center(
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
padding:
const EdgeInsets.only(left: 5, right: 5, bottom: 5, top: 5),
child: SvgPicture.asset(
status == "invited"
? Assets.invitedIcon
@ -114,8 +119,7 @@ class UsersPage extends StatelessWidget {
padding: const EdgeInsets.all(20),
child: Align(
alignment: Alignment.topCenter,
child: ListView(
shrinkWrap: true,
child: Column(
children: [
Row(
children: [
@ -188,292 +192,325 @@ class UsersPage extends StatelessWidget {
),
],
),
const SizedBox(height: 25),
DynamicTableScreen(
onFilter: (columnIndex) {
if (columnIndex == 0) {
showNameMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context.read<UserTableBloc>().add(const SortUsersByNameAsc());
},
zToaTap: () {
context.read<UserTableBloc>().add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 2) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.jobTitle)
item: _blocRole.selectedJobTitles.contains(item),
};
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
const SizedBox(height: 20),
Container(
height: screenSize.height * 0.65,
child: DynamicTableScreen(
tableSize: screenSize.height * 0.65,
onFilter: (columnIndex) {
if (columnIndex == 0) {
showNameMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameAsc());
},
zToATap: () {
context
.read<UserTableBloc>()
.add(const SortUsersByNameDesc());
},
);
}
if (columnIndex == 2) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.jobTitle)
item: _blocRole.selectedJobTitles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 5.3,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.jobTitle,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortJopTitle,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems,
sortOrder: _blocRole.currentSortJopTitle,
));
},
onSortAtoZ: (v) {
_blocRole.currentSortJopTitle = v;
},
onSortZtoA: (v) {
_blocRole.currentSortJopTitle = v;
},
);
}
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 5.3,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.jobTitle,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortJopTitle,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByJobEvent(
selectedJob: selectedItems,
sortOrder: _blocRole.currentSortJopTitle,
));
},
onSortAtoZ: (v) {
_blocRole.currentSortJopTitle = v;
},
onSortZtoA: (v) {
_blocRole.currentSortJopTitle = v;
},
);
}
if (columnIndex == 3) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.roleTypes)
item: _blocRole.selectedRoles.contains(item),
};
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.roleTypes,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortRole,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
context.read<UserTableBloc>().add(FilterUsersByRoleEvent(
selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortRole));
},
onSortAtoZ: (v) {
_blocRole.currentSortRole = v;
},
onSortZtoA: (v) {
_blocRole.currentSortRole = v;
},
);
}
if (columnIndex == 4) {
showDateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context.read<UserTableBloc>().add(const DateNewestToOldestEvent());
},
zToaTap: () {
context.read<UserTableBloc>().add(const DateOldestToNewestEvent());
},
);
}
if (columnIndex == 6) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.createdBy)
item: _blocRole.selectedCreatedBy.contains(item),
};
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 1,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.createdBy,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortCreatedBy,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortCreatedBy));
},
onSortAtoZ: (v) {
_blocRole.currentSortCreatedBy = v;
},
onSortZtoA: (v) {
_blocRole.currentSortCreatedBy = v;
},
);
}
if (columnIndex == 7) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.status)
item: _blocRole.selectedStatuses.contains(item),
};
if (columnIndex == 3) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.roleTypes)
item: _blocRole.selectedRoles.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 4,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.roleTypes,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortRole,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
context.read<UserTableBloc>().add(
FilterUsersByRoleEvent(
selectedRoles: selectedItems,
sortOrder: _blocRole.currentSortRole));
},
onSortAtoZ: (v) {
_blocRole.currentSortRole = v;
},
onSortZtoA: (v) {
_blocRole.currentSortRole = v;
},
);
}
if (columnIndex == 4) {
showDateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrder,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
if (columnIndex == 6) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.createdBy)
item: _blocRole.selectedCreatedBy.contains(item),
};
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 1,
240,
overlay.size.width / 4,
0,
),
list: _blocRole.createdBy,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortCreatedBy,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByCreatedEvent(
selectedCreatedBy: selectedItems,
sortOrder: _blocRole.currentSortCreatedBy));
},
onSortAtoZ: (v) {
_blocRole.currentSortCreatedBy = v;
},
onSortZtoA: (v) {
_blocRole.currentSortCreatedBy = v;
},
);
}
if (columnIndex == 7) {
final Map<String, bool> checkboxStates = {
for (var item in _blocRole.status)
item: _blocRole.selectedStatuses.contains(item),
};
final RenderBox overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 0,
240,
overlay.size.width / 5,
0,
final RenderBox overlay = Overlay.of(context)
.context
.findRenderObject() as RenderBox;
showPopUpFilterMenu(
position: RelativeRect.fromLTRB(
overlay.size.width / 0,
240,
overlay.size.width / 5,
0,
),
list: _blocRole.status,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortStatus,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortStatus));
},
onSortAtoZ: (v) {
_blocRole.currentSortStatus = v;
},
onSortZtoA: (v) {
_blocRole.currentSortStatus = v;
},
);
}
if (columnIndex == 8) {
showDeActivateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context
.read<UserTableBloc>()
.add(const DateNewestToOldestEvent());
},
zToaTap: () {
context
.read<UserTableBloc>()
.add(const DateOldestToNewestEvent());
},
);
}
},
titles: const [
"Full Name",
"Email Address",
"Job Title",
"Role",
"Creation Date",
"Creation Time",
"Created By",
"Status",
"De/Activate",
"Action"
],
rows: state.users.map((user) {
return [
Text('${user.firstName} ${user.lastName}'),
Text(user.email),
Text(user.jobTitle),
Text(user.roleType ?? ''),
Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''),
Text(user.invitedBy),
status(
status: user.isEnabled == false
? 'disabled'
: user.status,
),
list: _blocRole.status,
context: context,
checkboxStates: checkboxStates,
isSelected: _blocRole.currentSortStatus,
onOkPressed: () {
searchController.clear();
_blocRole.add(FilterClearEvent());
final selectedItems = checkboxStates.entries
.where((entry) => entry.value)
.map((entry) => entry.key)
.toList();
Navigator.of(context).pop();
_blocRole.add(FilterUsersByDeActevateEvent(
selectedActivate: selectedItems,
sortOrder: _blocRole.currentSortStatus));
},
onSortAtoZ: (v) {
_blocRole.currentSortStatus = v;
},
onSortZtoA: (v) {
_blocRole.currentSortStatus = v;
},
);
}
if (columnIndex == 8) {
showDeActivateFilterMenu(
context: context,
isSelected: _blocRole.currentSortOrderDate,
aToZTap: () {
context.read<UserTableBloc>().add(const DateNewestToOldestEvent());
},
zToaTap: () {
context.read<UserTableBloc>().add(const DateOldestToNewestEvent());
},
);
}
},
titles: const [
"Full Name",
"Email Address",
"Job Title",
"Role",
"Creation Date",
"Creation Time",
"Created By",
"Status",
"De/Activate",
"Action"
],
rows: state.users.map((user) {
return [
Text('${user.firstName} ${user.lastName}'),
Text(user.email),
Text(user.jobTitle),
Text(user.roleType ?? ''),
Text(user.createdDate ?? ''),
Text(user.createdTime ?? ''),
Text(user.invitedBy),
status(
status: user.isEnabled == false ? 'disabled' : user.status,
),
changeIconStatus(
status: user.isEnabled == false ? 'disabled' : user.status,
userId: user.uuid,
onTap: user.status != "invited"
? () {
context.read<UserTableBloc>().add(ChangeUserStatus(
userId: user.uuid,
newStatus:
user.isEnabled == false ? 'disabled' : user.status));
}
: null,
),
Row(
children: [
user.isEnabled != false
? actionButton(
isActive: true,
title: "Edit",
onTap: () {
context.read<SpaceTreeBloc>().add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(userId: user.uuid);
},
).then((v) {
if (v != null) {
changeIconStatus(
status: user.isEnabled == false
? 'disabled'
: user.status,
userId: user.uuid,
onTap: user.status != "invited"
? () {
context.read<UserTableBloc>().add(
ChangeUserStatus(
userId: user.uuid,
newStatus: user.isEnabled == false
? 'disabled'
: user.status));
}
: null,
),
Row(
children: [
user.isEnabled != false
? actionButton(
isActive: true,
title: "Edit",
onTap: () {
context
.read<SpaceTreeBloc>()
.add(ClearCachedData());
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return EditUserDialog(
userId: user.uuid);
},
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
if (v != null) {
_blocRole.add(const GetUsers());
}
}
});
},
)
: actionButton(
title: "Edit",
),
actionButton(
title: "Delete",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return DeleteUserDialog(
onTapDelete: () async {
try {
_blocRole.add(DeleteUserEvent(
user.uuid, context));
await Future.delayed(
const Duration(seconds: 2));
return true;
} catch (e) {
return false;
}
});
},
)
: actionButton(
title: "Edit",
),
actionButton(
title: "Delete",
onTap: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return DeleteUserDialog(onTapDelete: () async {
try {
_blocRole.add(DeleteUserEvent(user.uuid, context));
await Future.delayed(const Duration(seconds: 2));
return true;
} catch (e) {
return false;
}
});
},
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
),
],
),
];
}).toList(),
).then((v) {
if (v != null) {
_blocRole.add(const GetUsers());
}
});
},
),
],
),
];
}).toList(),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
@ -486,14 +523,20 @@ class UsersPage extends StatelessWidget {
visiblePagesCount: 4,
buttonRadius: 10,
selectedButtonColor: ColorsManager.secondaryColor,
buttonUnSelectedBorderColor: ColorsManager.grayBorder,
lastPageIcon: const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon: const Icon(Icons.keyboard_double_arrow_left),
totalPages:
(_blocRole.totalUsersCount.length / _blocRole.itemsPerPage).ceil(),
buttonUnSelectedBorderColor:
ColorsManager.grayBorder,
lastPageIcon:
const Icon(Icons.keyboard_double_arrow_right),
firstPageIcon:
const Icon(Icons.keyboard_double_arrow_left),
totalPages: (_blocRole.totalUsersCount.length /
_blocRole.itemsPerPage)
.ceil(),
currentPage: _blocRole.currentPage,
onPageChanged: (int pageNumber) {
context.read<UserTableBloc>().add(ChangePage(pageNumber));
context
.read<UserTableBloc>()
.add(ChangePage(pageNumber));
},
),
),

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.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_model.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/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -64,7 +66,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
emit(state.copyWith(
routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState());
if (event.isRoutineTab) {
add(const LoadScenes());
@ -90,8 +93,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList
int index =
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index = updatedIfItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
updatedIfItems[index] = event.item;
@ -100,18 +103,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
if (event.isTabToRun) {
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
emit(state.copyWith(
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} 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);
// Find the index of the item in teh current itemsList
int index =
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
int index = currentItems.indexWhere(
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid
if (index != -1) {
currentItems[index] = event.item;
@ -122,7 +128,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems));
}
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
void _onAddFunctionsToRoutine(
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try {
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);
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
} 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));
List<ScenesModel> scenes = [];
try {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
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) {
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
scenes.addAll(
await SceneApi.getScenes(spaceId, communityId, projectUuid));
}
}
} else {
scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectUuid));
}
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));
List<ScenesModel> automations = [];
final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -207,17 +222,22 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>();
try {
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>();
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) {
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
automations.addAll(
await SceneApi.getAutomation(spaceId, communityId, projectId));
}
}
} else {
automations.addAll(await SceneApi.getAutomation(
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
createRoutineBloc.selectedSpaceId,
createRoutineBloc.selectedCommunityId,
projectId));
}
emit(state.copyWith(
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));
await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null));
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));
}
@ -254,7 +276,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay';
}
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateScene(
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@ -267,7 +290,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
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,
));
return;
@ -335,15 +359,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
errorMessage: 'Something went wrong',
));
}
} catch (e) {
} on APIException catch (e) {
final errorData = e.message;
String errorMessage = errorData;
emit(state.copyWith(
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 {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (state.routineName == null || state.routineName!.isEmpty) {
@ -365,7 +392,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
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,
));
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
@ -456,7 +484,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
final result =
await SceneApi.createAutomation(createAutomationModel, projectUuid);
if (result['success']) {
add(ResetRoutineState());
add(const LoadAutomation());
@ -468,26 +497,32 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
CustomSnackBar.redSnackBar('Something went wrong');
}
} catch (e) {
} on APIException catch (e) {
final errorData = e.message;
String errorMessage = errorData;
emit(state.copyWith(
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) {
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);
selectedFunctions.remove(event.key);
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(
thenItems: thenItems, selectedFunctions: selectedFunctions));
} else {
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);
selectedFunctions.remove(event.key);
@ -498,7 +533,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false,
isTabToRun: false));
} 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));
}
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(
routineName: event.name,
));
@ -641,7 +679,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// return (thenItems, ifItems, currentFunctions);
// }
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
Future<void> _onGetSceneDetails(
GetSceneDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
@ -689,10 +728,12 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
? action.entityId
: const Uuid().v4(),
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId':
action.type == 'automation' || action.actionExecutor == 'delay'
? action.entityId
: const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
: action.type == 'automation'
@ -732,7 +773,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
),
);
// 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 functionCode = action.executorProperty?.functionCode;
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(
ifItems: [],
thenItems: [],
@ -822,7 +865,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
createRoutineView: false));
}
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _deleteScene(
DeleteScene event, Emitter<RoutineState> emit) async {
try {
final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -831,7 +875,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var spaceBloc = context.read<SpaceTreeBloc>();
if (state.isTabToRun) {
await SceneApi.deleteScene(
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
unitUuid: spaceBloc.state.selectedSpaces[0],
sceneId: state.sceneId ?? '');
} else {
await SceneApi.deleteAutomation(
unitUuid: spaceBloc.state.selectedSpaces[0],
@ -854,11 +899,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
add(const LoadAutomation());
add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false));
} catch (e) {
} on APIException catch (e) {
final errorData = e.message;
String errorMessage = errorData;
emit(state.copyWith(
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));
try {
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
@ -885,17 +934,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var createRoutineBloc = context.read<CreateRoutineBloc>();
var spaceBloc = context.read<SpaceTreeBloc>();
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
if (createRoutineBloc.selectedSpaceId == '' &&
createRoutineBloc.selectedCommunityId == '') {
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) {
devices.addAll(
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
devices.addAll(await DevicesManagementApi()
.fetchDevices(communityId, spaceId, projectUuid));
}
}
} else {
devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
createRoutineBloc.selectedCommunityId,
createRoutineBloc.selectedSpaceId,
projectUuid));
}
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 {
// Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) {
@ -918,7 +972,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) {
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,
));
return;
@ -971,7 +1026,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions,
);
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
final result =
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) {
add(ResetRoutineState());
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 {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
@ -1114,10 +1171,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
errorMessage: result['message'],
));
}
} catch (e) {
} on APIException catch (e) {
final errorData = e.message;
emit(state.copyWith(
isLoading: false,
errorMessage: 'Something went wrong',
errorMessage: errorData,
));
}
}
@ -1214,7 +1272,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay'
? 'Delay'
@ -1249,7 +1308,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
updatedFunctions[uniqueCustomId] = [];
}
if (action.executorProperty != null && action.actionExecutor != 'delay') {
if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode;
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
.where((card) =>
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
card['type'] == 'action' ||
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList();
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));
try {
@ -1358,24 +1423,29 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (success) {
final updatedAutomations = await SceneApi.getAutomationByUnitId(
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
event.automationStatusUpdate.spaceUuid,
event.communityId,
projectId);
// Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds,
));
} else {
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update failed',
));
}
} catch (e) {
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
final updatedLoadingIds = {...state.loadingAutomationIds!}
..remove(event.automationId);
emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update error: ${e.toString()}',

View File

@ -12,22 +12,53 @@ class ConditionToggle extends StatelessWidget {
});
static const _conditions = ["<", "==", ">"];
static const _icons = [
Icons.chevron_left,
Icons.drag_handle,
Icons.chevron_right
];
@override
Widget build(BuildContext context) {
return ToggleButtons(
onPressed: (index) => onChanged(_conditions[index]),
borderRadius: const BorderRadius.all(Radius.circular(8)),
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
selectedColor: Colors.white,
fillColor: ColorsManager.primaryColorWithOpacity,
color: ColorsManager.primaryColorWithOpacity,
constraints: const BoxConstraints(
minHeight: 40.0,
minWidth: 40.0,
final selectedIndex = _conditions.indexOf(currentCondition ?? "==");
return Container(
height: 30,
width: MediaQuery.of(context).size.width * 0.1,
decoration: BoxDecoration(
color: ColorsManager.softGray.withOpacity(0.5),
borderRadius: BorderRadius.circular(50),
),
clipBehavior: Clip.antiAlias,
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(_conditions.length, (index) {
final isSelected = index == selectedIndex;
return Expanded(
child: InkWell(
onTap: () => onChanged(_conditions[index]),
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
curve: Curves.ease,
decoration: BoxDecoration(
color:
isSelected ? ColorsManager.vividBlue : Colors.transparent,
),
child: Center(
child: Icon(
_icons[index],
size: 20,
color: isSelected
? ColorsManager.whiteColors
: ColorsManager.blackColor,
weight: isSelected ? 700 : 500,
),
),
),
),
);
}),
),
isSelected: _conditions.map((c) => c == (currentCondition ?? "==")).toList(),
children: _conditions.map((c) => Text(c)).toList(),
);
}
}

View File

@ -99,7 +99,27 @@ class _EnergyClampDialogState extends State<EnergyClampDialog> {
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Energy Clamp Conditions'),
Expanded(child: _buildMainContent(context, state)),
Expanded(
child: Visibility(
visible: _functions.isNotEmpty,
replacement: SizedBox(
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text(
'You Cant add\n the Power Clamp to Then Section',
textAlign: TextAlign.center,
style: context.textTheme.bodyMedium!.copyWith(
color: ColorsManager.red,
fontWeight: FontWeight.w400),
)),
],
),
),
child: _buildMainContent(context, state),
)),
_buildDialogFooter(context, state),
],
),

View File

@ -24,6 +24,9 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
on<PaginationEvent>(_fetchPaginationSpaces);
on<DebouncedSearchEvent>(_onDebouncedSearch);
on<SpaceTreeClearSelectionEvent>(_onSpaceTreeClearSelectionEvent);
on<AnalyticsClearAllSpaceTreeSelectionsEvent>(
_onAnalyticsClearAllSpaceTreeSelectionsEvent,
);
}
Timer _timer = Timer(const Duration(microseconds: 0), () {});
@ -493,6 +496,20 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
);
}
void _onAnalyticsClearAllSpaceTreeSelectionsEvent(
AnalyticsClearAllSpaceTreeSelectionsEvent event,
Emitter<SpaceTreeState> emit,
) async {
emit(
state.copyWith(
selectedCommunities: [],
selectedCommunityAndSpaces: {},
selectedSpaces: [],
soldCheck: [],
),
);
}
@override
Future<void> close() async {
_timer.cancel();

View File

@ -112,3 +112,7 @@ class ClearCachedData extends SpaceTreeEvent {}
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
const SpaceTreeClearSelectionEvent();
}
final class AnalyticsClearAllSpaceTreeSelectionsEvent extends SpaceTreeEvent {
const AnalyticsClearAllSpaceTreeSelectionsEvent();
}

View File

@ -48,7 +48,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
builder: (context, state) {
final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity
: state.communityList;
@ -132,104 +133,118 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
)
else
CustomSearchBar(
onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
SearchQueryEvent(query),
),
onSearchChanged: (query) =>
context.read<SpaceTreeBloc>().add(
SearchQueryEvent(query),
),
),
const SizedBox(height: 16),
Expanded(
child: state.isSearching
? const Center(child: CircularProgressIndicator())
: SidebarCommunitiesList(
onScrollToEnd: () {
if (!state.paginationIsLoading) {
context.read<SpaceTreeBloc>().add(
PaginationEvent(
state.paginationModel,
state.communityList,
),
);
}
},
scrollController: _scrollController,
communities: communities,
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: communities[index].name,
isSelected: state.selectedCommunities
.contains(communities[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(communities[index].uuid),
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnCommunityExpanded(
communities[index].uuid,
),
),
isExpanded: state.expandedCommunities.contains(
communities[index].uuid,
: communities.isEmpty
? Center(
child: Text(
'No communities found',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.textGray,
),
),
onItemSelected: () {
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
communities[index].uuid,
communities[index].spaces,
),
);
},
children: communities[index].spaces.map(
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded:
state.expandedSpaces.contains(space.uuid),
onItemSelected: () {
final isParentSelected = _isParentSelected(
state,
communities[index],
space,
)
: SidebarCommunitiesList(
onScrollToEnd: () {
if (!state.paginationIsLoading) {
context.read<SpaceTreeBloc>().add(
PaginationEvent(
state.paginationModel,
state.communityList,
),
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
widget.onSelect();
}
},
scrollController: _scrollController,
communities: communities,
itemBuilder: (context, index) {
return CustomExpansionTileSpaceTree(
title: communities[index].name,
isSelected: state.selectedCommunities
.contains(communities[index].uuid),
isSoldCheck: state.selectedCommunities
.contains(communities[index].uuid),
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
communities[index],
space.uuid ?? '',
space.children,
OnCommunityExpanded(
communities[index].uuid,
),
),
isExpanded:
state.expandedCommunities.contains(
communities[index].uuid,
),
onItemSelected: () {
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnCommunitySelected(
communities[index].uuid,
communities[index].spaces,
),
);
},
children: communities[index].spaces.map(
(space) {
return CustomExpansionTileSpaceTree(
title: space.name,
isExpanded: state.expandedSpaces
.contains(space.uuid),
onItemSelected: () {
final isParentSelected =
_isParentSelected(
state,
communities[index],
space,
);
if (widget
.shouldDisableDeselectingChildrenOfSelectedParent &&
isParentSelected) {
return;
}
widget.onSelect();
context.read<SpaceTreeBloc>().add(
OnSpaceSelected(
communities[index],
space.uuid ?? '',
space.children,
),
);
},
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck
.contains(space.uuid),
isSoldCheck: state.soldCheck
.contains(space.uuid),
children: _buildNestedSpaces(
context,
state,
space,
communities[index],
),
);
},
onExpansionChanged: () =>
context.read<SpaceTreeBloc>().add(
OnSpaceExpanded(
communities[index].uuid,
space.uuid ?? '',
),
),
isSelected: state.selectedSpaces
.contains(space.uuid) ||
state.soldCheck.contains(space.uuid),
isSoldCheck:
state.soldCheck.contains(space.uuid),
children: _buildNestedSpaces(
context,
state,
space,
communities[index],
),
);
},
).toList(),
);
},
),
).toList(),
);
},
),
),
if (state.paginationIsLoading) const CircularProgressIndicator(),
if (state.paginationIsLoading)
const CircularProgressIndicator(),
],
),
);

View File

@ -0,0 +1,10 @@
class APIException implements Exception {
final String message;
APIException(this.message);
@override
String toString() {
return message;
}
}

Some files were not shown because too many files have changed in this diff Show More