Compare commits

..

40 Commits

Author SHA1 Message Date
46feb0ea28 SP-1509 attatch space uuid to analytics device dropdown on energy management tab. 2025-06-03 15:20:30 +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
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
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
88 changed files with 2689 additions and 2140 deletions

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

@ -23,17 +23,18 @@ class AnalyticsDevice {
return AnalyticsDevice( return AnalyticsDevice(
uuid: json['uuid'] as String, uuid: json['uuid'] as String,
name: json['name'] as String, name: json['name'] as String,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, createdAt: json['createdAt'] != null
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, ? DateTime.parse(json['createdAt'] as String)
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
deviceTuyaUuid: json['deviceTuyaUuid'] as String?, deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
isActive: json['isActive'] as bool?, isActive: json['isActive'] as bool?,
productDevice: json['productDevice'] != null productDevice: json['productDevice'] != null
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>) ? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null, : null,
spaceUuid: (json['spaces'] as List<dynamic>?) spaceUuid: json['spaceUuid'] as String?,
?.map((e) => e['uuid'])
.firstOrNull
?.toString(),
); );
} }
} }
@ -60,8 +61,12 @@ class ProductDevice {
factory ProductDevice.fromJson(Map<String, dynamic> json) { factory ProductDevice.fromJson(Map<String, dynamic> json) {
return ProductDevice( return ProductDevice(
uuid: json['uuid'] as String?, uuid: json['uuid'] as String?,
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null, createdAt: json['createdAt'] != null
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null, ? DateTime.parse(json['createdAt'] as String)
: null,
updatedAt: json['updatedAt'] != null
? DateTime.parse(json['updatedAt'] as String)
: null,
catName: json['catName'] as String?, catName: json['catName'] as String?,
prodId: json['prodId'] as String?, prodId: json['prodId'] as String?,
name: json['name'] as String?, name: json['name'] as String?,

View File

@ -1,18 +1,49 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
class RangeOfAqi extends Equatable { class RangeOfAqi extends Equatable {
final double min;
final double avg;
final double max;
final DateTime date; final DateTime date;
final List<RangeOfAqiValue> data;
const RangeOfAqi({ const RangeOfAqi({
required this.min, required this.data,
required this.avg,
required this.max,
required this.date, required this.date,
}); });
@override factory RangeOfAqi.fromJson(Map<String, dynamic> json) {
List<Object?> get props => [min, avg, max, date]; 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 => [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:bloc/bloc.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart'; import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart'; import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
@ -11,6 +12,7 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) { RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) {
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent); on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent); on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
} }
final RangeOfAqiService _rangeOfAqiService; final RangeOfAqiService _rangeOfAqiService;
@ -20,19 +22,55 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
Emitter<RangeOfAqiState> emit, Emitter<RangeOfAqiState> emit,
) async { ) async {
emit( emit(
RangeOfAqiState( state.copyWith(status: RangeOfAqiStatus.loading),
status: RangeOfAqiStatus.loading,
rangeOfAqi: state.rangeOfAqi,
),
); );
try { try {
final rangeOfAqi = await _rangeOfAqiService.load(event.param); final rangeOfAqi = await _rangeOfAqiService.load(event.param);
emit(RangeOfAqiState(status: RangeOfAqiStatus.loaded, rangeOfAqi: rangeOfAqi)); emit(
state.copyWith(
status: RangeOfAqiStatus.loaded,
rangeOfAqi: rangeOfAqi,
filteredRangeOfAqi: _arrangeChartDataByType(
rangeOfAqi,
state.selectedAqiType,
),
),
);
} catch (e) { } catch (e) {
emit(RangeOfAqiState(status: RangeOfAqiStatus.failure, errorMessage: '$e')); emit(
state.copyWith(
status: RangeOfAqiStatus.failure,
errorMessage: '$e',
),
);
} }
} }
void _onUpdateAqiTypeEvent(
UpdateAqiTypeEvent event,
Emitter<RangeOfAqiState> emit,
) {
emit(
state.copyWith(
selectedAqiType: event.aqiType,
filteredRangeOfAqi: _arrangeChartDataByType(state.rangeOfAqi, event.aqiType),
),
);
}
List<RangeOfAqi> _arrangeChartDataByType(
List<RangeOfAqi> rangeOfAqi,
AqiType aqiType,
) {
final filteredRangeOfAqi = rangeOfAqi.map(
(data) => RangeOfAqi(
date: data.date,
data: data.data.where((value) => value.type == aqiType.code).toList(),
),
);
return filteredRangeOfAqi.toList();
}
void _onClearRangeOfAqiEvent( void _onClearRangeOfAqiEvent(
ClearRangeOfAqiEvent event, ClearRangeOfAqiEvent event,
Emitter<RangeOfAqiState> emit, Emitter<RangeOfAqiState> emit,

View File

@ -16,6 +16,15 @@ class LoadRangeOfAqiEvent extends RangeOfAqiEvent {
List<Object> get props => [param]; List<Object> get props => [param];
} }
class UpdateAqiTypeEvent extends RangeOfAqiEvent {
const UpdateAqiTypeEvent(this.aqiType);
final AqiType aqiType;
@override
List<Object> get props => [aqiType];
}
class ClearRangeOfAqiEvent extends RangeOfAqiEvent { class ClearRangeOfAqiEvent extends RangeOfAqiEvent {
const ClearRangeOfAqiEvent(); const ClearRangeOfAqiEvent();
} }

View File

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

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
@ -13,8 +14,10 @@ abstract final class FetchAirQualityDataHelper {
static void loadAirQualityData( static void loadAirQualityData(
BuildContext context, { BuildContext context, {
required DateTime date,
required String communityUuid, required String communityUuid,
required String spaceUuid, required String spaceUuid,
bool shouldFetchAnalyticsDevices = true,
}) { }) {
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate; final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
loadAnalyticsDevices( loadAnalyticsDevices(
@ -26,7 +29,11 @@ abstract final class FetchAirQualityDataHelper {
context, context,
spaceUuid: spaceUuid, spaceUuid: spaceUuid,
date: date, date: date,
aqiType: AqiType.aqi, );
loadAirQualityDistribution(
context,
spaceUuid: spaceUuid,
date: date,
); );
} }
@ -37,7 +44,9 @@ abstract final class FetchAirQualityDataHelper {
context.read<RealtimeDeviceChangesBloc>().add( context.read<RealtimeDeviceChangesBloc>().add(
const RealtimeDeviceChangesClosed(), const RealtimeDeviceChangesClosed(),
); );
context.read<AirQualityDistributionBloc>().add(
const ClearAirQualityDistribution(),
);
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent()); context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
} }
@ -67,16 +76,26 @@ abstract final class FetchAirQualityDataHelper {
BuildContext context, { BuildContext context, {
required String spaceUuid, required String spaceUuid,
required DateTime date, required DateTime date,
required AqiType aqiType,
}) { }) {
context.read<RangeOfAqiBloc>().add( context.read<RangeOfAqiBloc>().add(
LoadRangeOfAqiEvent( LoadRangeOfAqiEvent(
GetRangeOfAqiParam( GetRangeOfAqiParam(
date: date, date: date,
spaceUuid: spaceUuid, spaceUuid: spaceUuid,
aqiType: aqiType,
), ),
), ),
); );
} }
static void loadAirQualityDistribution(
BuildContext context, {
required String spaceUuid,
required DateTime date,
}) {
context.read<AirQualityDistributionBloc>().add(
LoadAirQualityDistribution(
GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date),
),
);
}
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
class AirQualityView extends StatelessWidget { class AirQualityView extends StatelessWidget {
@ -23,8 +24,14 @@ class AirQualityView extends StatelessWidget {
height: height * 1.2, height: height * 1.2,
child: const AirQualityEndSideWidget(), child: const AirQualityEndSideWidget(),
), ),
SizedBox(height: height * 0.5, child: const RangeOfAqiChartBox()), SizedBox(
SizedBox(height: height * 0.5, child: const Placeholder()), height: height * 0.5,
child: const RangeOfAqiChartBox(),
),
SizedBox(
height: height * 0.5,
child: const AqiDistributionChartBox(),
),
], ],
), ),
); );
@ -46,7 +53,7 @@ class AirQualityView extends StatelessWidget {
spacing: 20, spacing: 20,
children: [ children: [
Expanded(child: RangeOfAqiChartBox()), Expanded(child: RangeOfAqiChartBox()),
Expanded(child: Placeholder()), Expanded(child: AqiDistributionChartBox()),
], ],
), ),
), ),

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'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
enum AqiType { enum AqiType {
aqi('AQI', ''), aqi('AQI', '', 'aqi'),
pm25('PM2.5', 'µg/m³'), pm25('PM2.5', 'µg/m³', 'pm25'),
pm10('PM10', 'µg/m³'), pm10('PM10', 'µg/m³', 'pm10'),
hcho('HCHO', 'mg/m³'), hcho('HCHO', 'mg/m³', 'hcho'),
tvoc('TVOC', 'µg/m³'), tvoc('TVOC', 'µg/m³', 'tvoc'),
co2('CO2', 'ppm'); co2('CO2', 'ppm', 'co2');
const AqiType(this.value, this.unit); const AqiType(this.value, this.unit, this.code);
final String value; final String value;
final String unit; final String unit;
final String code;
} }
class AqiTypeDropdown extends StatefulWidget { class AqiTypeDropdown extends StatefulWidget {

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
@ -39,6 +40,7 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
context, context,
communityUuid: community.uuid, communityUuid: community.uuid,
spaceUuid: space.uuid ?? '', spaceUuid: space.uuid ?? '',
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
); );
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
@ -13,6 +14,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart'; import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
@ -101,6 +103,11 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
FakeRangeOfAqiService(), FakeRangeOfAqiService(),
), ),
), ),
BlocProvider(
create: (context) => AirQualityDistributionBloc(
FakeAirQualityDistributionService(),
),
),
], ],
child: const AnalyticsPageForm(), child: const AnalyticsPageForm(),
); );

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
@ -56,33 +57,16 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
const Spacer(), const Spacer(),
Visibility( Visibility(
key: ValueKey(selectedTab), key: ValueKey(selectedTab),
visible: selectedTab == AnalyticsPageTab.energyManagement, visible: selectedTab == AnalyticsPageTab.energyManagement ||
selectedTab == AnalyticsPageTab.airQuality,
child: Expanded( child: Expanded(
flex: 2, flex: 2,
child: FittedBox( child: FittedBox(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
child: AnalyticsDateFilterButton( child: AnalyticsDateFilterButton(
onDateSelected: (DateTime value) { onDateSelected: (value) {
context.read<AnalyticsDatePickerBloc>().add( _onDateChanged(context, value, selectedTab);
UpdateAnalyticsDatePickerEvent(montlyDate: value),
);
final spaceTreeState =
context.read<SpaceTreeBloc>().state;
if (spaceTreeState.selectedSpaces.isNotEmpty) {
FetchEnergyManagementDataHelper
.loadEnergyManagementData(
context,
shouldFetchAnalyticsDevices: false,
selectedDate: value,
communityId:
spaceTreeState.selectedCommunities.firstOrNull ??
'',
spaceId:
spaceTreeState.selectedSpaces.firstOrNull ?? '',
);
}
}, },
selectedDate: context selectedDate: context
.watch<AnalyticsDatePickerBloc>() .watch<AnalyticsDatePickerBloc>()
@ -112,4 +96,73 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
child: child, child: child,
); );
} }
void _onDateChanged(
BuildContext context,
DateTime date,
AnalyticsPageTab selectedTab,
) {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: date),
);
final spaceTreeState = context.read<SpaceTreeBloc>().state;
final communities = spaceTreeState.selectedCommunities;
final spaces = spaceTreeState.selectedSpaces;
if (spaceTreeState.selectedSpaces.isNotEmpty) {
switch (selectedTab) {
case AnalyticsPageTab.energyManagement:
_onEnergyManagementDateChanged(
context,
date: date,
communityUuid: communities.firstOrNull ?? '',
spaceUuid: spaces.firstOrNull ?? '',
);
break;
case AnalyticsPageTab.airQuality:
_onAirQualityDateChanged(
context,
date: date,
communityUuid: communities.firstOrNull ?? '',
spaceUuid: spaces.firstOrNull ?? '',
);
default:
break;
}
}
}
void _onEnergyManagementDateChanged(
BuildContext context, {
required DateTime date,
required String communityUuid,
required String spaceUuid,
}) {
context.read<AnalyticsDatePickerBloc>().add(
UpdateAnalyticsDatePickerEvent(montlyDate: date),
);
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
shouldFetchAnalyticsDevices: false,
selectedDate: date,
communityId: communityUuid,
spaceId: spaceUuid,
);
}
void _onAirQualityDateChanged(
BuildContext context, {
required DateTime date,
required String communityUuid,
required String spaceUuid,
}) {
FetchAirQualityDataHelper.loadAirQualityData(
context,
date: date,
communityUuid: communityUuid,
spaceUuid: spaceUuid,
shouldFetchAnalyticsDevices: false,
);
}
} }

View File

@ -41,7 +41,7 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
.color; .color;
return Tooltip( return Tooltip(
message: '${device.name}\n${device.productDevice?.uuid ?? ''}', message: '${device.name}\n${device.spaceUuid ?? ''}',
child: ChartInformativeCell(title: Text(device.name), color: deviceColor), child: ChartInformativeCell(title: Text(device.name), color: deviceColor),
); );
} }

View File

@ -41,7 +41,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
AnalyticsErrorWidget(state.errorMessage), AnalyticsErrorWidget(state.errorMessage),
AnalyticsSidebarHeader( AnalyticsSidebarHeader(
title: 'Smart Power Clamp', title: 'Smart Power Clamp',
showSpaceUuid: true, showSpaceUuidInDevicesDropdown: true,
onChanged: (device) { onChanged: (device) {
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases( FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
context, context,

View File

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

View File

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

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/models/range_of_aqi.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart'; import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart'; import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
@ -19,9 +20,14 @@ class FakeRangeOfAqiService implements RangeOfAqiService {
final max = (avg + maxDelta).clamp(0.0, 301.0); final max = (avg + maxDelta).clamp(0.0, 301.0);
return RangeOfAqi( return RangeOfAqi(
min: min, data: [
avg: avg, RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
max: max, RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max),
RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max),
RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max),
RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max),
],
date: date, date: date,
); );
}); });

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

@ -10,13 +10,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
class AnalyticsSidebarHeader extends StatelessWidget { class AnalyticsSidebarHeader extends StatelessWidget {
const AnalyticsSidebarHeader({ const AnalyticsSidebarHeader({
required this.title, required this.title,
this.showSpaceUuid = false, this.showSpaceUuidInDevicesDropdown = false,
this.onChanged, this.onChanged,
super.key, super.key,
}); });
final String title; final String title;
final bool showSpaceUuid; final bool showSpaceUuidInDevicesDropdown;
final void Function(AnalyticsDevice device)? onChanged; final void Function(AnalyticsDevice device)? onChanged;
@override @override
@ -49,6 +49,7 @@ class AnalyticsSidebarHeader extends StatelessWidget {
alignment: AlignmentDirectional.centerEnd, alignment: AlignmentDirectional.centerEnd,
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
child: AnalyticsDeviceDropdown( child: AnalyticsDeviceDropdown(
showSpaceUuid: showSpaceUuidInDevicesDropdown,
onChanged: (value) { onChanged: (value) {
context.read<AnalyticsDevicesBloc>().add( context.read<AnalyticsDevicesBloc>().add(
SelectAnalyticsDeviceEvent(value), SelectAnalyticsDeviceEvent(value),

View File

@ -13,7 +13,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart'; import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/auth_api.dart'; import 'package:syncrow_web/services/auth_api.dart';
import 'package:syncrow_web/utils/constants/strings_manager.dart'; import 'package:syncrow_web/utils/constants/strings_manager.dart';
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart'; import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
@ -100,8 +99,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
} }
Future<void> changePassword( Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
ChangePasswordEvent event, Emitter<AuthState> emit) async {
emit(LoadingForgetState()); emit(LoadingForgetState());
try { try {
var response = await AuthenticationAPI.verifyOtp( var response = await AuthenticationAPI.verifyOtp(
@ -115,14 +113,14 @@ Future<void> changePassword(
emit(const TimerState(isButtonEnabled: true, remainingTime: 0)); emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
emit(SuccessForgetState()); emit(SuccessForgetState());
} }
} on APIException catch (e) { } on DioException catch (e) {
final errorMessage = e.message; final errorData = e.response!.data;
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
validate = errorMessage; validate = errorMessage;
emit(AuthInitialState()); emit(AuthInitialState());
} }
} }
String? validateCode(String? value) { String? validateCode(String? value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Code is required'; return 'Code is required';
@ -151,7 +149,6 @@ Future<void> changePassword(
static UserModel? user; static UserModel? user;
bool showValidationMessage = false; bool showValidationMessage = false;
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async { void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
emit(AuthLoading()); emit(AuthLoading());
if (isChecked) { if (isChecked) {
@ -168,20 +165,21 @@ Future<void> changePassword(
password: event.password, password: event.password,
), ),
); );
} on APIException catch (e) { } on DioException catch (e) {
validate = e.message; final errorData = e.response!.data;
emit(LoginInitial()); String errorMessage = errorData['error']['message'];
return; if (errorMessage == "Access denied for web platform") {
} catch (e) { validate = errorMessage;
validate = 'Something went wrong'; } else {
validate = 'Invalid Credentials!';
}
emit(LoginInitial()); emit(LoginInitial());
return; return;
} }
if (token.accessTokenIsNotEmpty) { if (token.accessTokenIsNotEmpty) {
FlutterSecureStorage storage = const FlutterSecureStorage(); FlutterSecureStorage storage = const FlutterSecureStorage();
await storage.write( await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
key: Token.loginAccessTokenKey, value: token.accessToken);
const FlutterSecureStorage().write( const FlutterSecureStorage().write(
key: UserModel.userUuidKey, key: UserModel.userUuidKey,
value: Token.decodeToken(token.accessToken)['uuid'].toString()); value: Token.decodeToken(token.accessToken)['uuid'].toString());
@ -197,7 +195,6 @@ Future<void> changePassword(
} }
} }
checkBoxToggle( checkBoxToggle(
CheckBoxEvent event, CheckBoxEvent event,
Emitter<AuthState> emit, Emitter<AuthState> emit,

View File

@ -1,21 +1,27 @@
import 'dart:async'; import 'dart:async';
import 'package:dio/dio.dart';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart'; import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
class AcBloc extends Bloc<AcsEvent, AcsState> { class AcBloc extends Bloc<AcsEvent, AcsState> {
late AcStatusModel deviceStatus; late AcStatusModel deviceStatus;
final String deviceId; final String deviceId;
Timer? _timer; final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
Timer? _countdownTimer; Timer? _countdownTimer;
AcBloc({required this.deviceId}) : super(AcsInitialState()) { AcBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(AcsInitialState()) {
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus); on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus); on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
on<AcControlEvent>(_onAcControl); on<AcControlEvent>(_onAcControl);
@ -34,14 +40,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
int scheduledMinutes = 0; int scheduledMinutes = 0;
FutureOr<void> _onFetchAcStatus( FutureOr<void> _onFetchAcStatus(
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async { AcFetchDeviceStatusEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status); deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
if (deviceStatus.countdown1 != 0) { if (deviceStatus.countdown1 != 0) {
// Convert API value to minutes
final totalMinutes = deviceStatus.countdown1 * 6; final totalMinutes = deviceStatus.countdown1 * 6;
scheduledHours = totalMinutes ~/ 60; scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60; scheduledMinutes = totalMinutes % 60;
@ -62,30 +68,24 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
_listenToChanges(deviceId) { void _listenToChanges(deviceId) {
try { try {
DatabaseReference ref = final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
FirebaseDatabase.instance.ref('device-status/$deviceId'); final stream = ref.onValue;
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) async { stream.listen((DatabaseEvent event) async {
if (event.snapshot.value == null) return; if (event.snapshot.value == null) return;
if (_timer != null) {
await Future.delayed(const Duration(seconds: 1));
}
Map<dynamic, dynamic> usersMap = Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>; event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = []; List<Status> statusList = [];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList statusList.add(Status(code: element['code'], value: element['value']));
.add(Status(code: element['code'], value: element['value']));
}); });
deviceStatus = deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) { if (!isClosed) {
add(AcStatusUpdated(deviceStatus)); add(AcStatusUpdated(deviceStatus));
} }
@ -93,146 +93,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} catch (_) {} } catch (_) {}
} }
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) { void _onAcStatusUpdated(
AcStatusUpdated event,
Emitter<AcsState> emit,
) {
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} }
FutureOr<void> _onAcControl( FutureOr<void> _onAcControl(
AcControlEvent event, Emitter<AcsState> emit) async { AcControlEvent event,
final oldValue = _getValueByCode(event.code); Emitter<AcsState> emit,
) async {
_updateLocalValue(event.code, event.value, emit); emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce(
isBatch: false,
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
);
}
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 { try {
late bool response; final success = await controlDeviceService.controlDevice(
if (isBatch) { deviceUuid: event.deviceId,
response = await DevicesManagementApi() status: Status(code: event.code, value: event.value),
.deviceBatchControl(deviceId, code, value); );
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) { if (!success) {
_revertValueAndEmit(id, code, oldValue, emit); emit(const AcsFailedState(error: 'Failed to control device'));
} }
} catch (e) { } catch (e) {
if (e is DioException && e.response != null) { emit(AcsFailedState(error: e.toString()));
debugPrint('Error response: ${e.response?.data}');
}
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
_updateLocalValue(code, oldValue, emit);
emit(ACStatusLoaded(status: deviceStatus));
}
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
switch (code) {
case 'switch':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(acSwitch: value);
}
break;
case 'temp_set':
if (value is int) {
deviceStatus = deviceStatus.copyWith(tempSet: value);
}
break;
case 'mode':
if (value is String) {
deviceStatus = deviceStatus.copyWith(
modeString: value,
);
}
break;
case 'level':
if (value is String) {
deviceStatus = deviceStatus.copyWith(
fanSpeedsString: value,
);
}
break;
case 'child_lock':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(childLock: value);
}
case 'countdown_time':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value);
}
break;
default:
break;
}
emit(ACStatusLoaded(status: deviceStatus));
}
dynamic _getValueByCode(String code) {
switch (code) {
case 'switch':
return deviceStatus.acSwitch;
case 'temp_set':
return deviceStatus.tempSet;
case 'mode':
return deviceStatus.modeString;
case 'level':
return deviceStatus.fanSpeedsString;
case 'child_lock':
return deviceStatus.childLock;
case 'countdown_time':
return deviceStatus.countdown1;
default:
return null;
} }
} }
FutureOr<void> _onFetchAcBatchStatus( FutureOr<void> _onFetchAcBatchStatus(
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async { AcFetchBatchStatusEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final status = final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
deviceStatus =
AcStatusModel.fromJson(event.devicesIds.first, status.status);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
} catch (e) { } catch (e) {
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
@ -240,25 +138,32 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
FutureOr<void> _onAcBatchControl( FutureOr<void> _onAcBatchControl(
AcBatchControlEvent event, Emitter<AcsState> emit) async { AcBatchControlEvent event,
final oldValue = _getValueByCode(event.code); Emitter<AcsState> emit,
) async {
_updateLocalValue(event.code, event.value, emit); emit(AcsLoadingState());
_updateDeviceFunctionFromCode(event.code, event.value);
emit(ACStatusLoaded(status: deviceStatus)); emit(ACStatusLoaded(status: deviceStatus));
await _runDebounce( try {
isBatch: true, final success = await batchControlDevicesService.batchControlDevices(
deviceId: event.devicesIds, uuids: event.devicesIds,
code: event.code, code: event.code,
value: event.value, value: event.value,
oldValue: oldValue,
emit: emit,
); );
if (!success) {
emit(const AcsFailedState(error: 'Failed to control devices'));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
} }
FutureOr<void> _onFactoryReset( Future<void> _onFactoryReset(
AcFactoryResetEvent event, Emitter<AcsState> emit) async { AcFactoryResetEvent event,
Emitter<AcsState> emit,
) async {
emit(AcsLoadingState()); emit(AcsLoadingState());
try { try {
final response = await DevicesManagementApi().factoryReset( final response = await DevicesManagementApi().factoryReset(
@ -275,9 +180,11 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
void _onClose(OnClose event, Emitter<AcsState> emit) { void _onClose(
OnClose event,
Emitter<AcsState> emit,
) {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
_timer?.cancel();
} }
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) { void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
@ -300,7 +207,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
)); ));
} }
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) { void _handleDecreaseTime(
DecreaseTimeEvent event,
Emitter<AcsState> emit,
) {
if (state is! ACStatusLoaded) return; if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
int totalMinutes = (scheduledHours * 60) + scheduledMinutes; int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
@ -315,7 +225,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
Future<void> _handleToggleTimer( Future<void> _handleToggleTimer(
ToggleScheduleEvent event, Emitter<AcsState> emit) async { ToggleScheduleEvent event,
Emitter<AcsState> emit,
) async {
if (state is! ACStatusLoaded) return; if (state is! ACStatusLoaded) return;
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
@ -331,29 +243,30 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
try { try {
final scaledValue = totalMinutes ~/ 6; final scaledValue = totalMinutes ~/ 6;
await _runDebounce( final success = await controlDeviceService.controlDevice(
isBatch: false, deviceUuid: deviceId,
deviceId: deviceId, status: Status(code: 'countdown_time', value: scaledValue),
code: 'countdown_time',
value: scaledValue,
oldValue: scaledValue,
emit: emit,
); );
if (success) {
_startCountdownTimer(emit); _startCountdownTimer(emit);
emit(currentState.copyWith(isTimerActive: timerActive)); emit(currentState.copyWith(isTimerActive: timerActive));
} else {
timerActive = false;
emit(const AcsFailedState(error: 'Failed to set timer'));
}
} catch (e) { } catch (e) {
timerActive = false; timerActive = false;
emit(AcsFailedState(error: e.toString())); emit(AcsFailedState(error: e.toString()));
} }
} else { } else {
await _runDebounce( try {
isBatch: false, final success = await controlDeviceService.controlDevice(
deviceId: deviceId, deviceUuid: deviceId,
code: 'countdown_time', status: Status(code: 'countdown_time', value: 0),
value: 0,
oldValue: 0,
emit: emit,
); );
if (success) {
_countdownTimer?.cancel(); _countdownTimer?.cancel();
scheduledHours = 0; scheduledHours = 0;
scheduledMinutes = 0; scheduledMinutes = 0;
@ -362,6 +275,12 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
scheduledHours: 0, scheduledHours: 0,
scheduledMinutes: 0, scheduledMinutes: 0,
)); ));
} else {
emit(const AcsFailedState(error: 'Failed to stop timer'));
}
} catch (e) {
emit(AcsFailedState(error: e.toString()));
}
} }
} }
@ -385,7 +304,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
}); });
} }
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) { void _handleUpdateTimer(
UpdateTimerEvent event,
Emitter<AcsState> emit,
) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
final currentState = state as ACStatusLoaded; final currentState = state as ACStatusLoaded;
emit(currentState.copyWith( emit(currentState.copyWith(
@ -400,7 +322,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
ApiCountdownValueEvent event, Emitter<AcsState> emit) { ApiCountdownValueEvent event, Emitter<AcsState> emit) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {
final totalMinutes = event.apiValue * 6; final totalMinutes = event.apiValue * 6;
final scheduledHours = totalMinutes ~/ 60;
scheduledMinutes = totalMinutes % 60; scheduledMinutes = totalMinutes % 60;
_startCountdownTimer( _startCountdownTimer(
emit, emit,
@ -409,6 +330,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
} }
} }
void _updateDeviceFunctionFromCode(String code, dynamic value) {
switch (code) {
case 'switch':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(acSwitch: value);
}
break;
case 'temp_set':
if (value is int) {
deviceStatus = deviceStatus.copyWith(tempSet: value);
}
break;
case 'mode':
if (value is String) {
deviceStatus = deviceStatus.copyWith(modeString: value);
}
break;
case 'level':
if (value is String) {
deviceStatus = deviceStatus.copyWith(fanSpeedsString: value);
}
break;
case 'child_lock':
if (value is bool) {
deviceStatus = deviceStatus.copyWith(childLock: value);
}
break;
case 'countdown_time':
if (value is int) {
deviceStatus = deviceStatus.copyWith(countdown1: value);
}
break;
default:
break;
}
}
@override @override
Future<void> close() { Future<void> close() {
add(OnClose()); add(OnClose());

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
import 'package:syncrow_web/pages/device_managment/ac/factories/ac_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -26,8 +26,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => create: (context) => AcBlocFactory.create(
AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)), deviceId: devicesIds.first,
)..add(AcFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<AcBloc, AcsState>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
if (state is ACStatusLoaded) { if (state is ACStatusLoaded) {

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart'; import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
import 'package:syncrow_web/pages/device_managment/ac/factories/ac_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart';
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart'; import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart';
@ -24,8 +25,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => AcBloc(deviceId: device.uuid!) create: (context) => AcBlocFactory.create(
..add(AcFetchDeviceStatusEvent(device.uuid!)), deviceId: device.uuid!,
)..add(AcFetchDeviceStatusEvent(device.uuid!)),
child: BlocBuilder<AcBloc, AcsState>( child: BlocBuilder<AcBloc, AcsState>(
builder: (context, state) { builder: (context, state) {
final acBloc = BlocProvider.of<AcBloc>(context); final acBloc = BlocProvider.of<AcBloc>(context);

View File

@ -1,5 +1,3 @@
import 'dart:async';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
@ -7,14 +5,21 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_e
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> { class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
final String deviceId; final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
late CeilingSensorModel deviceStatus; late CeilingSensorModel deviceStatus;
Timer? _timer;
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) { CeilingSensorBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(CeilingInitialState()) {
on<CeilingInitialEvent>(_fetchCeilingSensorStatus); on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl); on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
on<CeilingChangeValueEvent>(_changeValue); on<CeilingChangeValueEvent>(_changeValue);
@ -26,35 +31,34 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
on<StatusUpdated>(_onStatusUpdated); on<StatusUpdated>(_onStatusUpdated);
} }
void _fetchCeilingSensorStatus( Future<void> _fetchCeilingSensorStatus(
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async { CeilingInitialEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingInitialState()); emit(CeilingLoadingInitialState());
try { try {
var response = final response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = CeilingSensorModel.fromJson(response.status); deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
_listenToChanges(event.deviceId); _listenToChanges(event.deviceId);
} catch (e) { } catch (e) {
emit(CeilingFailedState(error: e.toString())); emit(CeilingFailedState(error: e.toString()));
return;
} }
} }
_listenToChanges(deviceId) { void _listenToChanges(String deviceId) {
try { try {
DatabaseReference ref = final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
FirebaseDatabase.instance.ref('device-status/$deviceId'); final stream = ref.onValue;
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) { stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap = if (event.snapshot.value == null) return;
event.snapshot.value as Map<dynamic, dynamic>;
final usersMap = event.snapshot.value as Map<dynamic, dynamic>;
final statusList = <Status>[];
List<Status> statusList = [];
usersMap['status'].forEach((element) { usersMap['status'].forEach((element) {
statusList statusList.add(Status(code: element['code'], value: element['value']));
.add(Status(code: element['code'], value: element['value']));
}); });
deviceStatus = CeilingSensorModel.fromJson(statusList); deviceStatus = CeilingSensorModel.fromJson(statusList);
@ -65,149 +69,127 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
} catch (_) {} } catch (_) {}
} }
void _onStatusUpdated(StatusUpdated event, Emitter<CeilingSensorState> emit) { void _onStatusUpdated(
StatusUpdated event,
Emitter<CeilingSensorState> emit,
) {
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} }
void _changeValue( Future<void> _changeValue(
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async { CeilingChangeValueEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') { _updateDeviceFunctionFromCode(event.code, event.value);
deviceStatus.sensitivity = event.value;
} else if (event.code == 'none_body_time') {
deviceStatus.noBodyTime = event.value;
} else if (event.code == 'moving_max_dis') {
deviceStatus.maxDistance = event.value;
} else if (event.code == 'scene') {
deviceStatus.spaceType = getSpaceType(event.value);
}
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: deviceId, try {
code: event.code, await controlDeviceService.controlDevice(
value: event.value, deviceUuid: deviceId,
emit: emit, status: Status(code: event.code, value: event.value),
isBatch: false,
); );
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
}
} }
Future<void> _onBatchControl( Future<void> _onBatchControl(
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async { CeilingBatchControlEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
if (event.code == 'sensitivity') { _updateDeviceFunctionFromCode(event.code, event.value);
deviceStatus.sensitivity = event.value;
} else if (event.code == 'none_body_time') {
deviceStatus.noBodyTime = event.value;
} else if (event.code == 'moving_max_dis') {
deviceStatus.maxDistance = event.value;
} else if (event.code == 'scene') {
deviceStatus.spaceType = getSpaceType(event.value);
}
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: event.deviceIds, try {
final success = await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code, code: event.code,
value: event.value, value: event.value,
emit: emit,
isBatch: true,
); );
if (!success) {
emit(const CeilingFailedState(error: 'Failed to control devices'));
}
} catch (e) {
emit(CeilingFailedState(error: e.toString()));
}
} }
_runDeBouncer({ void _updateDeviceFunctionFromCode(String code, dynamic value) {
required dynamic deviceId, switch (code) {
required String code, case 'sensitivity':
required dynamic value, deviceStatus.sensitivity = value;
required Emitter<CeilingSensorState> emit, break;
required bool isBatch, case 'none_body_time':
}) { deviceStatus.noBodyTime = value;
late String id; break;
case 'moving_max_dis':
if (deviceId is List) { deviceStatus.maxDistance = value;
id = deviceId.first; break;
} else { case 'scene':
id = deviceId; deviceStatus.spaceType = getSpaceType(value);
break;
default:
break;
}
} }
if (_timer != null) { Future<void> _getDeviceReports(
_timer!.cancel(); GetCeilingDeviceReportsEvent event,
} Emitter<CeilingSensorState> emit,
_timer = Timer(const Duration(seconds: 1), () async { ) 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));
}
});
}
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
Emitter<CeilingSensorState> emit) async {
if (event.code.isEmpty) { if (event.code.isEmpty) {
emit(ShowCeilingDescriptionState(description: reportString)); emit(ShowCeilingDescriptionState(description: reportString));
return; return;
} else { }
emit(CeilingReportsLoadingState());
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
// final to = DateTime.now().millisecondsSinceEpoch;
emit(CeilingReportsLoadingState());
try { try {
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString()) final value = await DevicesManagementApi.getDeviceReports(
await DevicesManagementApi.getDeviceReports(deviceId, event.code) deviceId,
.then((value) { event.code,
);
emit(CeilingReportsState(deviceReport: value)); emit(CeilingReportsState(deviceReport: value));
});
} catch (e) { } catch (e) {
emit(CeilingReportsFailedState(error: e.toString())); emit(CeilingReportsFailedState(error: e.toString()));
return;
}
} }
} }
void _showDescription( void _showDescription(
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) { ShowCeilingDescriptionEvent event,
Emitter<CeilingSensorState> emit,
) {
emit(ShowCeilingDescriptionState(description: event.description)); emit(ShowCeilingDescriptionState(description: event.description));
} }
void _backToGridView( void _backToGridView(
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) { BackToCeilingGridViewEvent event,
Emitter<CeilingSensorState> emit,
) {
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} }
FutureOr<void> _fetchCeilingSensorBatchControl( Future<void> _fetchCeilingSensorBatchControl(
CeilingFetchDeviceStatusEvent event, CeilingFetchDeviceStatusEvent event,
Emitter<CeilingSensorState> emit) async { Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingInitialState()); emit(CeilingLoadingInitialState());
try { try {
var response = final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = CeilingSensorModel.fromJson(response.status); deviceStatus = CeilingSensorModel.fromJson(response.status);
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus)); emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
} catch (e) { } catch (e) {
emit(CeilingFailedState(error: e.toString())); emit(CeilingFailedState(error: e.toString()));
return;
} }
} }
FutureOr<void> _onFactoryReset( Future<void> _onFactoryReset(
CeilingFactoryResetEvent event, Emitter<CeilingSensorState> emit) async { CeilingFactoryResetEvent event,
Emitter<CeilingSensorState> emit,
) async {
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus)); emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
try { try {
final response = await DevicesManagementApi().factoryReset( final response = await DevicesManagementApi().factoryReset(

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/factories/ceiling_sensor_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
@ -23,8 +23,9 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first) create: (context) => CeilingSensorBlocFactory.create(
..add(CeilingFetchDeviceStatusEvent(devicesIds)), deviceId: devicesIds.first,
)..add(CeilingFetchDeviceStatusEvent(devicesIds)),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>( child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) { builder: (context, state) {
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) { if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
@ -110,7 +111,6 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
), ),
), ),
), ),
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
FactoryResetWidget( FactoryResetWidget(
callFactoryReset: () { callFactoryReset: () {
context.read<CeilingSensorBloc>().add( context.read<CeilingSensorBloc>().add(

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/factories/ceiling_sensor_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
@ -28,8 +29,9 @@ class CeilingSensorControlsView extends StatelessWidget
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '') create: (context) => CeilingSensorBlocFactory.create(
..add(CeilingInitialEvent(device.uuid ?? '')), deviceId: device.uuid ?? '',
)..add(CeilingInitialEvent(device.uuid ?? '')),
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>( child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
builder: (context, state) { builder: (context, state) {
if (state is CeilingLoadingInitialState || if (state is CeilingLoadingInitialState ||

View File

@ -1,17 +1,25 @@
import 'dart:async'; import 'dart:async';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> { class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
late bool deviceStatus; late bool deviceStatus;
final String deviceId; final String deviceId;
Timer? _timer; final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
CurtainBloc({required this.deviceId}) : super(CurtainInitial()) { CurtainBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(CurtainInitial()) {
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus); on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
on<CurtainFetchBatchStatus>(_onFetchBatchStatus); on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
on<CurtainControl>(_onCurtainControl); on<CurtainControl>(_onCurtainControl);
@ -20,32 +28,31 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
on<StatusUpdated>(_onStatusUpdated); on<StatusUpdated>(_onStatusUpdated);
} }
FutureOr<void> _onFetchDeviceStatus( Future<void> _onFetchDeviceStatus(
CurtainFetchDeviceStatus event, Emitter<CurtainState> emit) async { CurtainFetchDeviceStatus event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading()); emit(CurtainStatusLoading());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId); _listenToChanges(event.deviceId, emit);
_listenToChanges(event.deviceId);
deviceStatus = _checkStatus(status.status[0].value); deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus)); emit(CurtainStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(CurtainError(e.toString())); emit(CurtainError(e.toString()));
} }
} }
void _listenToChanges(String deviceId) { void _listenToChanges(String deviceId, Emitter<CurtainState> emit) {
try { try {
DatabaseReference ref = final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
FirebaseDatabase.instance.ref('device-status/$deviceId'); final stream = ref.onValue;
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) { stream.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?; final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return; if (data == null) return;
List<Status> statusList = []; final statusList = <Status>[];
if (data['status'] != null) { if (data['status'] != null) {
for (var element in data['status']) { for (var element in data['status']) {
statusList.add( statusList.add(
@ -57,7 +64,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
} }
} }
if (statusList.isNotEmpty) { if (statusList.isNotEmpty) {
bool newStatus = _checkStatus(statusList[0].value); final newStatus = _checkStatus(statusList[0].value);
if (newStatus != deviceStatus) { if (newStatus != deviceStatus) {
deviceStatus = newStatus; deviceStatus = newStatus;
if (!isClosed) { if (!isClosed) {
@ -71,76 +78,32 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
} }
} }
void _onStatusUpdated(StatusUpdated event, Emitter<CurtainState> emit) { void _onStatusUpdated(
StatusUpdated event,
Emitter<CurtainState> emit,
) {
emit(CurtainStatusLoading()); emit(CurtainStatusLoading());
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(CurtainStatusLoaded(deviceStatus)); emit(CurtainStatusLoaded(deviceStatus));
} }
FutureOr<void> _onCurtainControl( Future<void> _onCurtainControl(
CurtainControl event, Emitter<CurtainState> emit) async { CurtainControl event,
final oldValue = deviceStatus; Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
_updateLocalValue(event.value, emit); _updateLocalValue(event.value, emit);
emit(CurtainStatusLoaded(deviceStatus));
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;
}
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(const Duration(seconds: 1), () async {
try { try {
final controlValue = value ? 'open' : 'close'; final controlValue = event.value ? 'open' : 'close';
await controlDeviceService.controlDevice(
late bool response; deviceUuid: event.deviceId,
if (isBatch) { status: Status(code: event.code, value: controlValue),
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) { } catch (e) {
_revertValueAndEmit(id, oldValue, emit); _updateLocalValue(!event.value, emit);
emit(CurtainControlError(e.toString()));
} }
});
}
void _revertValueAndEmit(
String deviceId, bool oldValue, Emitter<CurtainState> emit) {
_updateLocalValue(oldValue, emit);
emit(CurtainStatusLoaded(deviceStatus));
emit(const CurtainControlError('Failed to control the device.'));
} }
void _updateLocalValue(bool value, Emitter<CurtainState> emit) { void _updateLocalValue(bool value, Emitter<CurtainState> emit) {
@ -152,41 +115,44 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
return command.toLowerCase() == 'open'; return command.toLowerCase() == 'open';
} }
FutureOr<void> _onFetchBatchStatus( Future<void> _onFetchBatchStatus(
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async { CurtainFetchBatchStatus event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading()); emit(CurtainStatusLoading());
try { try {
final status = final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = _checkStatus(status.status[0].value); deviceStatus = _checkStatus(status.status[0].value);
emit(CurtainStatusLoaded(deviceStatus)); emit(CurtainStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(CurtainError(e.toString())); emit(CurtainError(e.toString()));
} }
} }
FutureOr<void> _onCurtainBatchControl( Future<void> _onCurtainBatchControl(
CurtainBatchControl event, Emitter<CurtainState> emit) async { CurtainBatchControl event,
final oldValue = deviceStatus; Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading());
_updateLocalValue(event.value, emit); _updateLocalValue(event.value, emit);
emit(CurtainStatusLoaded(deviceStatus)); try {
final controlValue = event.value ? 'open' : 'stop';
await _runDebounce( await batchControlDevicesService.batchControlDevices(
deviceId: event.devicesIds, uuids: event.devicesIds,
code: event.code, code: event.code,
value: event.value, value: controlValue,
oldValue: oldValue,
emit: emit,
isBatch: true,
); );
} catch (e) {
_updateLocalValue(!event.value, emit);
emit(CurtainControlError(e.toString()));
}
} }
FutureOr<void> _onFactoryReset( Future<void> _onFactoryReset(
CurtainFactoryReset event, Emitter<CurtainState> emit) async { CurtainFactoryReset event,
Emitter<CurtainState> emit,
) async {
emit(CurtainStatusLoading()); emit(CurtainStatusLoading());
try { try {
final response = await DevicesManagementApi().factoryReset( final response = await DevicesManagementApi().factoryReset(

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/pages/device_managment/curtain/factories/curtain_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; // import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -18,7 +19,7 @@ class CurtainBatchStatusView extends StatelessWidget with HelperResponsiveLayout
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) =>
CurtainBloc(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)), CurtainBlocFactory.create(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
child: BlocBuilder<CurtainBloc, CurtainState>( child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) { builder: (context, state) {
if (state is CurtainStatusLoading) { if (state is CurtainStatusLoading) {

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart'; import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
import 'package:syncrow_web/pages/device_managment/curtain/factories/curtain_bloc_factory.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class CurtainStatusControlsView extends StatelessWidget class CurtainStatusControlsView extends StatelessWidget
@ -15,7 +16,7 @@ class CurtainStatusControlsView extends StatelessWidget
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => CurtainBloc(deviceId: deviceId) create: (context) => CurtainBlocFactory.create(deviceId: deviceId)
..add(CurtainFetchDeviceStatus(deviceId)), ..add(CurtainFetchDeviceStatus(deviceId)),
child: BlocBuilder<CurtainBloc, CurtainState>( child: BlocBuilder<CurtainBloc, CurtainState>(
builder: (context, state) { builder: (context, state) {

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

View File

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

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:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +16,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first) create: (context) => OneGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)), ..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>( child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
builder: (context, state) { builder: (context, state) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -9,13 +10,13 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout { class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
final String deviceId; final String deviceId;
const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key); const OneGangGlassSwitchControlView({required this.deviceId, super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) =>
OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)), OneGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>( child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
builder: (context, state) { builder: (context, state) {
if (state is OneGangGlassSwitchLoading) { if (state is OneGangGlassSwitchLoading) {

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

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/factories/wall_light_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart'; import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -18,7 +18,7 @@ class WallLightBatchControlView extends StatelessWidget with HelperResponsiveLay
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first) create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(WallLightSwitchFetchBatchEvent(deviceIds)), ..add(WallLightSwitchFetchBatchEvent(deviceIds)),
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>( child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
builder: (context, state) { builder: (context, state) {

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

View File

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

View File

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

View File

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

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/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => ThreeGangGlassSwitchBloc(deviceId: deviceIds.first) create: (context) => ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)), ..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>( child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
builder: (context, state) { builder: (context, state) {

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) =>
ThreeGangGlassSwitchBloc(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)), ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>( child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
builder: (context, state) { builder: (context, state) {
if (state is ThreeGangGlassSwitchLoading) { if (state is ThreeGangGlassSwitchLoading) {

View File

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

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/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; // import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -17,7 +18,7 @@ class LivingRoomBatchControlsView extends StatelessWidget with HelperResponsiveL
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) =>
LivingRoomBloc(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)), LivingRoomBlocFactory.create(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
child: BlocBuilder<LivingRoomBloc, LivingRoomState>( child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
builder: (context, state) { builder: (context, state) {
if (state is LivingRoomDeviceStatusLoading) { if (state is LivingRoomDeviceStatusLoading) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart'; import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -14,7 +15,7 @@ class LivingRoomDeviceControlsView extends StatelessWidget
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => LivingRoomBloc(deviceId: deviceId) create: (context) => LivingRoomBlocFactory.create(deviceId: deviceId)
..add(LivingRoomFetchDeviceStatusEvent(deviceId)), ..add(LivingRoomFetchDeviceStatusEvent(deviceId)),
child: BlocBuilder<LivingRoomBloc, LivingRoomState>( child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
builder: (context, state) { builder: (context, state) {

View File

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

View File

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

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/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/factories/two_gang_glass_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart'; import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -16,7 +17,7 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceIds.first) create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)), ..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>( child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
builder: (context, state) { builder: (context, state) {

View File

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

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -6,10 +7,22 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> { class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
TwoGangSwitchBloc({required this.deviceId}) : super(TwoGangSwitchInitial()) { final String deviceId;
final ControlDeviceService controlDeviceService;
final BatchControlDevicesService batchControlDevicesService;
late TwoGangStatusModel deviceStatus;
TwoGangSwitchBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(TwoGangSwitchInitial()) {
on<TwoGangSwitchFetchDeviceEvent>(_onFetchDeviceStatus); on<TwoGangSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
on<TwoGangSwitchControl>(_onControl); on<TwoGangSwitchControl>(_onControl);
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus); on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
@ -18,16 +31,13 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
on<StatusUpdated>(_onStatusUpdated); on<StatusUpdated>(_onStatusUpdated);
} }
late TwoGangStatusModel deviceStatus; Future<void> _onFetchDeviceStatus(
final String deviceId; TwoGangSwitchFetchDeviceEvent event,
Timer? _timer; Emitter<TwoGangSwitchState> emit,
) async {
FutureOr<void> _onFetchDeviceStatus(TwoGangSwitchFetchDeviceEvent event,
Emitter<TwoGangSwitchState> emit) async {
emit(TwoGangSwitchLoading()); emit(TwoGangSwitchLoading());
try { try {
final status = final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
await DevicesManagementApi().getDeviceStatus(event.deviceId);
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status); deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
_listenToChanges(event.deviceId); _listenToChanges(event.deviceId);
emit(TwoGangSwitchStatusLoaded(deviceStatus)); emit(TwoGangSwitchStatusLoaded(deviceStatus));
@ -36,131 +46,91 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
} }
} }
FutureOr<void> _onControl( void _listenToChanges(String deviceId) {
TwoGangSwitchControl event, Emitter<TwoGangSwitchState> emit) async { try {
final oldValue = _getValueByCode(event.code); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
_updateLocalValue(event.code, event.value); ref.onValue.listen((event) {
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
emit(TwoGangSwitchStatusLoaded(deviceStatus)); List<Status> statusList = [];
eventsMap['status'].forEach((element) {
statusList.add(
Status(code: element['code'], value: element['value']),
);
});
await _runDebounce( deviceStatus = TwoGangStatusModel.fromJson(deviceId, statusList);
deviceId: event.deviceId, add(StatusUpdated(deviceStatus));
code: event.code, });
value: event.value, } catch (_) {
oldValue: oldValue, log(
emit: emit, 'Error listening to changes',
isBatch: false, name: 'TwoGangSwitchBloc._listenToChanges',
); );
} }
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) { Future<void> _onControl(
_timer!.cancel(); TwoGangSwitchControl event,
} Emitter<TwoGangSwitchState> emit,
) async {
_timer = Timer(const Duration(milliseconds: 500), () async { emit(TwoGangSwitchLoading());
try { _updateLocalValue(event.code, event.value);
late bool response;
if (isBatch) {
response = await DevicesManagementApi()
.deviceBatchControl(deviceId, code, value);
} else {
response = await DevicesManagementApi()
.deviceControl(deviceId, Status(code: code, value: value));
}
if (!response) {
_revertValueAndEmit(id, code, oldValue, emit);
}
} catch (e) {
_revertValueAndEmit(id, code, oldValue, emit);
}
});
}
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
Emitter<TwoGangSwitchState> emit) {
_updateLocalValue(code, oldValue);
emit(TwoGangSwitchStatusLoaded(deviceStatus)); emit(TwoGangSwitchStatusLoaded(deviceStatus));
}
void _updateLocalValue(String code, bool value) { try {
if (code == 'switch_1') { await controlDeviceService.controlDevice(
deviceStatus = deviceStatus.copyWith(switch1: value); deviceUuid: event.deviceId,
} status: Status(code: event.code, value: event.value),
);
if (code == 'switch_2') { } catch (e) {
deviceStatus = deviceStatus.copyWith(switch2: value); _updateLocalValue(event.code, !event.value);
emit(TwoGangSwitchError(e.toString()));
} }
} }
bool _getValueByCode(String code) { Future<void> _onBatchControl(
switch (code) { TwoGangSwitchBatchControl event,
case 'switch_1': Emitter<TwoGangSwitchState> emit,
return deviceStatus.switch1; ) async {
case 'switch_2': emit(TwoGangSwitchLoading());
return deviceStatus.switch2; _updateLocalValue(event.code, event.value);
default: emit(TwoGangSwitchStatusLoaded(deviceStatus));
return false;
try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceId,
code: event.code,
value: event.value,
);
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(TwoGangSwitchError(e.toString()));
} }
} }
Future<void> _onFetchBatchStatus(TwoGangSwitchFetchBatchEvent event, Future<void> _onFetchBatchStatus(
Emitter<TwoGangSwitchState> emit) async { TwoGangSwitchFetchBatchEvent event,
Emitter<TwoGangSwitchState> emit,
) async {
emit(TwoGangSwitchLoading()); emit(TwoGangSwitchLoading());
try { try {
final status = final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds); deviceStatus = TwoGangStatusModel.fromJson(
deviceStatus = event.devicesIds.first,
TwoGangStatusModel.fromJson(event.devicesIds.first, status.status); status.status,
);
emit(TwoGangSwitchStatusLoaded(deviceStatus)); emit(TwoGangSwitchStatusLoaded(deviceStatus));
} catch (e) { } catch (e) {
emit(TwoGangSwitchError(e.toString())); emit(TwoGangSwitchError(e.toString()));
} }
} }
@override Future<void> _onFactoryReset(
Future<void> close() { TwoGangFactoryReset event,
_timer?.cancel(); Emitter<TwoGangSwitchState> emit,
return super.close(); ) async {
}
FutureOr<void> _onBatchControl(
TwoGangSwitchBatchControl event, Emitter<TwoGangSwitchState> emit) async {
final oldValue = _getValueByCode(event.code);
_updateLocalValue(event.code, event.value);
emit(TwoGangSwitchStatusLoaded(deviceStatus));
await _runDebounce(
deviceId: event.deviceId,
code: event.code,
value: event.value,
oldValue: oldValue,
emit: emit,
isBatch: true,
);
}
FutureOr<void> _onFactoryReset(
TwoGangFactoryReset event, Emitter<TwoGangSwitchState> emit) async {
emit(TwoGangSwitchLoading()); emit(TwoGangSwitchLoading());
try { try {
final response = await DevicesManagementApi().factoryReset( final response = await DevicesManagementApi().factoryReset(
@ -168,42 +138,31 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
event.deviceId, event.deviceId,
); );
if (!response) { if (!response) {
emit(TwoGangSwitchError('Failed')); emit(TwoGangSwitchError('Failed to reset device'));
} else { } else {
emit(TwoGangSwitchStatusLoaded(deviceStatus)); add(TwoGangSwitchFetchDeviceEvent(event.deviceId));
} }
} catch (e) { } catch (e) {
emit(TwoGangSwitchError(e.toString())); emit(TwoGangSwitchError(e.toString()));
} }
} }
_listenToChanges(deviceId) { void _onStatusUpdated(
try { StatusUpdated event,
DatabaseReference ref = Emitter<TwoGangSwitchState> emit,
FirebaseDatabase.instance.ref('device-status/$deviceId'); ) {
Stream<DatabaseEvent> stream = ref.onValue;
stream.listen((DatabaseEvent event) {
Map<dynamic, dynamic> usersMap =
event.snapshot.value as Map<dynamic, dynamic>;
List<Status> statusList = [];
usersMap['status'].forEach((element) {
statusList
.add(Status(code: element['code'], value: element['value']));
});
deviceStatus =
TwoGangStatusModel.fromJson(usersMap['productUuid'], statusList);
if (!isClosed) {
add(StatusUpdated(deviceStatus));
}
});
} catch (_) {}
}
void _onStatusUpdated(StatusUpdated event, Emitter<TwoGangSwitchState> emit) {
deviceStatus = event.deviceStatus; deviceStatus = event.deviceStatus;
emit(TwoGangSwitchStatusLoaded(deviceStatus)); emit(TwoGangSwitchStatusLoaded(deviceStatus));
} }
void _updateLocalValue(String code, bool value) {
switch (code) {
case 'switch_1':
deviceStatus = deviceStatus.copyWith(switch1: value);
break;
case 'switch_2':
deviceStatus = deviceStatus.copyWith(switch2: value);
break;
}
}
} }

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) { for (var status in jsonList) {
switch (status.code) { switch (status.code) {
case 'switch_1': case 'switch_1':
switch1 = status.value ?? false; switch1 = bool.tryParse(status.value.toString()) ?? false;
break; break;
case 'countdown_1': case 'countdown_1':
countDown = status.value ?? 0; countDown = int.tryParse(status.value.toString()) ?? 0;
break; break;
case 'switch_2': case 'switch_2':
switch2 = status.value ?? false; switch2 = bool.tryParse(status.value.toString()) ?? false;
break; break;
case 'countdown_2': case 'countdown_2':
countDown2 = status.value ?? 0; countDown2 = int.tryParse(status.value.toString()) ?? 0;
break; break;
} }
} }

View File

@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -18,7 +18,7 @@ class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayou
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first) create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceIds.first)
..add(TwoGangSwitchFetchBatchEvent(deviceIds)), ..add(TwoGangSwitchFetchBatchEvent(deviceIds)),
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>( child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
builder: (context, state) { builder: (context, state) {

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart'; import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -15,7 +16,7 @@ class TwoGangDeviceControlView extends StatelessWidget
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => TwoGangSwitchBloc(deviceId: deviceId) create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceId)
..add(TwoGangSwitchFetchDeviceEvent(deviceId)), ..add(TwoGangSwitchFetchDeviceEvent(deviceId)),
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>( child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
builder: (context, state) { builder: (context, state) {

View File

@ -1,18 +1,28 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:firebase_database/firebase_database.dart'; import 'package:firebase_database/firebase_database.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
import 'package:syncrow_web/services/batch_control_devices_service.dart';
import 'package:syncrow_web/services/control_device_service.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> { class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
final String deviceId; final String deviceId;
late WallSensorModel deviceStatus; final ControlDeviceService controlDeviceService;
Timer? _timer; final BatchControlDevicesService batchControlDevicesService;
WallSensorBloc({required this.deviceId}) : super(WallSensorInitialState()) { late WallSensorModel deviceStatus;
WallSensorBloc({
required this.deviceId,
required this.controlDeviceService,
required this.batchControlDevicesService,
}) : super(WallSensorInitialState()) {
on<WallSensorFetchStatusEvent>(_fetchWallSensorStatus); on<WallSensorFetchStatusEvent>(_fetchWallSensorStatus);
on<WallSensorFetchBatchStatusEvent>(_fetchWallSensorBatchControl); on<WallSensorFetchBatchStatusEvent>(_fetchWallSensorBatchControl);
on<WallSensorChangeValueEvent>(_changeValue); on<WallSensorChangeValueEvent>(_changeValue);
@ -24,28 +34,28 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate); on<WallSensorRealtimeUpdateEvent>(_onRealtimeUpdate);
} }
void _fetchWallSensorStatus( Future<void> _fetchWallSensorStatus(
WallSensorFetchStatusEvent event, Emitter<WallSensorState> emit) async { WallSensorFetchStatusEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingInitialState()); emit(WallSensorLoadingInitialState());
try { try {
var response = await DevicesManagementApi().getDeviceStatus(deviceId); final response = await DevicesManagementApi().getDeviceStatus(deviceId);
deviceStatus = WallSensorModel.fromJson(response.status); deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
_listenToChanges(deviceId); _listenToChanges(deviceId);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} catch (e) { } catch (e) {
emit(WallSensorFailedState(error: e.toString())); emit(WallSensorFailedState(error: e.toString()));
return;
} }
} }
// Fetch batch status Future<void> _fetchWallSensorBatchControl(
FutureOr<void> _fetchWallSensorBatchControl(
WallSensorFetchBatchStatusEvent event, WallSensorFetchBatchStatusEvent event,
Emitter<WallSensorState> emit) async { Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingInitialState()); emit(WallSensorLoadingInitialState());
try { try {
var response = final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
await DevicesManagementApi().getBatchStatus(event.devicesIds);
deviceStatus = WallSensorModel.fromJson(response.status); deviceStatus = WallSensorModel.fromJson(response.status);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} catch (e) { } catch (e) {
@ -54,132 +64,105 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
} }
void _listenToChanges(String deviceId) { void _listenToChanges(String deviceId) {
DatabaseReference ref = try {
FirebaseDatabase.instance.ref('device-status/$deviceId'); final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
ref.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value as Map<dynamic, dynamic>?;
if (data == null) return;
final statusList = (data['status'] as List?) ref.onValue.listen((event) {
?.map((e) => Status(code: e['code'], value: e['value'])) final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
.toList();
if (statusList != null) { List<Status> statusList = [];
final updatedDeviceStatus = WallSensorModel.fromJson(statusList); eventsMap['status'].forEach((element) {
statusList.add(
Status(code: element['code'], value: element['value']),
);
});
deviceStatus = WallSensorModel.fromJson(statusList);
if (!isClosed) { if (!isClosed) {
add(WallSensorRealtimeUpdateEvent(updatedDeviceStatus)); add(WallSensorRealtimeUpdateEvent(deviceStatus));
}
} }
}); });
} } catch (_) {
log(
'Error listening to changes',
void _changeValue( name: 'WallSensorBloc._listenToChanges',
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;
}
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: deviceId,
code: event.code,
value: event.value,
isBatch: false,
emit: emit,
); );
} }
}
Future<void> _changeValue(
WallSensorChangeValueEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
_updateLocalValue(event.code, event.value);
emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
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( Future<void> _onBatchControl(
WallSensorBatchControlEvent event, Emitter<WallSensorState> emit) async { WallSensorBatchControlEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
if (event.code == 'far_detection') { _updateLocalValue(event.code, event.value);
deviceStatus.farDetection = event.value;
} else if (event.code == 'motionless_sensitivity') {
deviceStatus.motionlessSensitivity = event.value;
} else if (event.code == 'motion_sensitivity_value') {
deviceStatus.motionSensitivity = event.value;
} else if (event.code == 'no_one_time') {
deviceStatus.noBodyTime = event.value;
}
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
await _runDeBouncer(
deviceId: event.deviceIds, try {
await batchControlDevicesService.batchControlDevices(
uuids: event.deviceIds,
code: event.code, code: event.code,
value: event.value, value: event.value,
emit: emit,
isBatch: true,
); );
} catch (e) {
_updateLocalValue(event.code, !event.value);
emit(WallSensorFailedState(error: e.toString()));
}
} }
_runDeBouncer({ Future<void> _getDeviceReports(
required dynamic deviceId, GetDeviceReportsEvent event,
required String code, Emitter<WallSensorState> emit,
required dynamic value, ) async {
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()); emit(DeviceReportsLoadingState());
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
// final to = DateTime.now().millisecondsSinceEpoch;
try { try {
// await DevicesManagementApi.getDeviceReportsByDate( final reports = await DevicesManagementApi.getDeviceReports(
// deviceId, event.code, from.toString(), to.toString()) deviceId,
await DevicesManagementApi.getDeviceReports(deviceId, event.code) event.code,
.then((value) { );
emit(DeviceReportsState(deviceReport: value, code: event.code)); emit(DeviceReportsState(deviceReport: reports, code: event.code));
});
} catch (e) { } catch (e) {
emit(DeviceReportsFailedState(error: e.toString())); emit(DeviceReportsFailedState(error: e.toString()));
return;
} }
} }
void _showDescription( void _showDescription(
ShowDescriptionEvent event, Emitter<WallSensorState> emit) { ShowDescriptionEvent event,
Emitter<WallSensorState> emit,
) {
emit(WallSensorShowDescriptionState(description: event.description)); emit(WallSensorShowDescriptionState(description: event.description));
} }
void _backToGridView( void _backToGridView(
BackToGridViewEvent event, Emitter<WallSensorState> emit) { BackToGridViewEvent event,
Emitter<WallSensorState> emit,
) {
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); emit(WallSensorUpdateState(wallSensorModel: deviceStatus));
} }
FutureOr<void> _onFactoryReset( Future<void> _onFactoryReset(
WallSensorFactoryResetEvent event, Emitter<WallSensorState> emit) async { WallSensorFactoryResetEvent event,
Emitter<WallSensorState> emit,
) async {
emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus)); emit(WallSensorLoadingNewSate(wallSensorModel: deviceStatus));
try { try {
final response = await DevicesManagementApi().factoryReset( final response = await DevicesManagementApi().factoryReset(
@ -187,9 +170,9 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
event.deviceId, event.deviceId,
); );
if (!response) { if (!response) {
emit(const WallSensorFailedState(error: 'Failed')); emit(const WallSensorFailedState(error: 'Failed to reset device'));
} else { } else {
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); add(WallSensorFetchStatusEvent());
} }
} catch (e) { } catch (e) {
emit(WallSensorFailedState(error: e.toString())); emit(WallSensorFailedState(error: e.toString()));
@ -200,7 +183,23 @@ class WallSensorBloc extends Bloc<WallSensorEvent, WallSensorState> {
WallSensorRealtimeUpdateEvent event, WallSensorRealtimeUpdateEvent event,
Emitter<WallSensorState> emit, Emitter<WallSensorState> emit,
) { ) {
deviceStatus = event.deviceStatus; emit(WallSensorUpdateState(wallSensorModel: event.deviceStatus));
emit(WallSensorUpdateState(wallSensorModel: deviceStatus)); }
void _updateLocalValue(String code, dynamic value) {
switch (code) {
case 'far_detection':
deviceStatus.farDetection = value;
break;
case 'motionless_sensitivity':
deviceStatus.motionlessSensitivity = value;
break;
case 'motion_sensitivity_value':
deviceStatus.motionSensitivity = value;
break;
case 'no_one_time':
deviceStatus.noBodyTime = value;
break;
}
} }
} }

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_bloc.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_event.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/bloc/wall_state.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/factories/wall_sensor_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -21,7 +22,7 @@ class WallSensorBatchControlView extends StatelessWidget with HelperResponsiveLa
final isLarge = isLargeScreenSize(context); final isLarge = isLargeScreenSize(context);
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => WallSensorBloc(deviceId: devicesIds.first) create: (context) => WallSensorBlocFactory.create(deviceId: devicesIds.first)
..add(WallSensorFetchBatchStatusEvent(devicesIds)), ..add(WallSensorFetchBatchStatusEvent(devicesIds)),
child: BlocBuilder<WallSensorBloc, WallSensorState>( child: BlocBuilder<WallSensorBloc, WallSensorState>(
builder: (context, state) { builder: (context, state) {

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_static_widget.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart';
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/factories/wall_sensor_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart'; import 'package:syncrow_web/pages/device_managment/wall_sensor/model/wall_sensor_model.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
@ -26,7 +27,7 @@ class WallSensorControlsView extends StatelessWidget with HelperResponsiveLayout
final isMedium = isMediumScreenSize(context); final isMedium = isMediumScreenSize(context);
return BlocProvider( return BlocProvider(
create: (context) => create: (context) =>
WallSensorBloc(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()), WallSensorBlocFactory.create(deviceId: device.uuid!)..add(WallSensorFetchStatusEvent()),
child: BlocBuilder<WallSensorBloc, WallSensorState>( child: BlocBuilder<WallSensorBloc, WallSensorState>(
builder: (context, state) { builder: (context, state) {
if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) { if (state is WallSensorLoadingInitialState || state is DeviceReportsLoadingState) {

View File

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

View File

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

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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart'; // import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart'; import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/bloc/water_heater_bloc.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/factories/water_heater_bloc_factory.dart';
import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
class WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveLayout { class WaterHEaterBatchControlView extends StatelessWidget
with HelperResponsiveLayout {
const WaterHEaterBatchControlView({super.key, required this.deviceIds}); const WaterHEaterBatchControlView({super.key, required this.deviceIds});
final List<String> deviceIds; final List<String> deviceIds;
@ -17,8 +18,9 @@ class WaterHEaterBatchControlView extends StatelessWidget with HelperResponsiveL
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (context) => create: (context) => WaterHeaterBlocFactory.create(
WaterHeaterBloc()..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)), deviceId: deviceIds.first,
)..add(FetchWaterHeaterBatchStatusEvent(devicesUuid: deviceIds)),
child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>( child: BlocBuilder<WaterHeaterBloc, WaterHeaterState>(
builder: (context, state) { builder: (context, state) {
if (state is WaterHeaterLoadingState) { if (state is WaterHeaterLoadingState) {

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

View File

@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
@ -16,7 +15,6 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routines/models/routine_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_model.dart';
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/devices_mang_api.dart'; import 'package:syncrow_web/services/devices_mang_api.dart';
import 'package:syncrow_web/services/routines_api.dart'; import 'package:syncrow_web/services/routines_api.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -66,8 +64,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
TriggerSwitchTabsEvent event, TriggerSwitchTabsEvent event,
Emitter<RoutineState> emit, Emitter<RoutineState> emit,
) { ) {
emit(state.copyWith( emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
routineTab: event.isRoutineTab, createRoutineView: false));
add(ResetRoutineState()); add(ResetRoutineState());
if (event.isRoutineTab) { if (event.isRoutineTab) {
add(const LoadScenes()); add(const LoadScenes());
@ -93,8 +90,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems); final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
// Find the index of the item in teh current itemsList // Find the index of the item in teh current itemsList
int index = updatedIfItems.indexWhere( int index =
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid // Replace the map if the index is valid
if (index != -1) { if (index != -1) {
updatedIfItems[index] = event.item; updatedIfItems[index] = event.item;
@ -103,21 +100,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
if (event.isTabToRun) { if (event.isTabToRun) {
emit(state.copyWith( emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
} else { } else {
emit(state.copyWith( emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
} }
} }
void _onAddToThenContainer( void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.from(state.thenItems); final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
// Find the index of the item in teh current itemsList // Find the index of the item in teh current itemsList
int index = currentItems.indexWhere( int index =
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']); currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
// Replace the map if the index is valid // Replace the map if the index is valid
if (index != -1) { if (index != -1) {
currentItems[index] = event.item; currentItems[index] = event.item;
@ -128,8 +122,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems)); emit(state.copyWith(thenItems: currentItems));
} }
void _onAddFunctionsToRoutine( void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try { try {
if (event.functions.isEmpty) return; if (event.functions.isEmpty) return;
@ -164,8 +157,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions); // currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
// } // }
currentSelectedFunctions[event.uniqueCustomId] = currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
List.from(event.functions);
emit(state.copyWith(selectedFunctions: currentSelectedFunctions)); emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
} catch (e) { } catch (e) {
@ -173,30 +165,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onLoadScenes( Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
LoadScenes event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> scenes = []; List<ScenesModel> scenes = [];
try { try {
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (createRoutineBloc.selectedSpaceId == '' && if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) { for (var spaceId in spacesList) {
scenes.addAll( scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
await SceneApi.getScenes(spaceId, communityId, projectUuid));
} }
} }
} else { } else {
scenes.addAll(await SceneApi.getScenes( scenes.addAll(await SceneApi.getScenes(
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
createRoutineBloc.selectedCommunityId,
projectUuid));
} }
emit(state.copyWith( emit(state.copyWith(
@ -213,8 +199,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onLoadAutomation( Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
List<ScenesModel> automations = []; List<ScenesModel> automations = [];
final projectId = await ProjectManager.getProjectUUID() ?? ''; final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -222,22 +207,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
BuildContext context = NavigationService.navigatorKey.currentContext!; BuildContext context = NavigationService.navigatorKey.currentContext!;
var createRoutineBloc = context.read<CreateRoutineBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
try { try {
if (createRoutineBloc.selectedSpaceId == '' && if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
createRoutineBloc.selectedCommunityId == '') {
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) { for (var spaceId in spacesList) {
automations.addAll( automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
await SceneApi.getAutomation(spaceId, communityId, projectId));
} }
} }
} else { } else {
automations.addAll(await SceneApi.getAutomation( automations.addAll(await SceneApi.getAutomation(
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
createRoutineBloc.selectedCommunityId,
projectId));
} }
emit(state.copyWith( emit(state.copyWith(
automations: automations, automations: automations,
@ -253,16 +233,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onSearchRoutines( FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
SearchRoutines event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null)); emit(state.copyWith(isLoading: true, errorMessage: null));
await Future.delayed(const Duration(seconds: 1)); await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(isLoading: false, errorMessage: null)); emit(state.copyWith(isLoading: false, errorMessage: null));
emit(state.copyWith(searchText: event.query)); emit(state.copyWith(searchText: event.query));
} }
FutureOr<void> _onAddSelectedIcon( FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon)); emit(state.copyWith(selectedIcon: event.icon));
} }
@ -276,8 +254,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return actions.last['deviceId'] == 'delay'; return actions.last['deviceId'] == 'delay';
} }
Future<void> _onCreateScene( Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
@ -290,8 +267,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@ -359,18 +335,15 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
errorMessage: 'Something went wrong', errorMessage: 'Something went wrong',
)); ));
} }
} on APIException catch (e) { } catch (e) {
final errorData = e.message;
String errorMessage = errorData;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: errorMessage, errorMessage: 'Something went wrong',
)); ));
} }
} }
Future<void> _onCreateAutomation( Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
@ -392,8 +365,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
CustomSnackBar.redSnackBar('Cannot have delay as the last action'); CustomSnackBar.redSnackBar('Cannot have delay as the last action');
@ -484,8 +456,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
await SceneApi.createAutomation(createAutomationModel, projectUuid);
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadAutomation()); add(const LoadAutomation());
@ -497,32 +468,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
CustomSnackBar.redSnackBar('Something went wrong'); CustomSnackBar.redSnackBar('Something went wrong');
} }
} on APIException catch (e) { } catch (e) {
final errorData = e.message;
String errorMessage = errorData;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: errorMessage, errorMessage: 'Something went wrong',
)); ));
CustomSnackBar.redSnackBar(errorMessage); CustomSnackBar.redSnackBar('Something went wrong');
} }
} }
FutureOr<void> _onRemoveDragCard( FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) { if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems); final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions = final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
thenItems.removeAt(event.index); thenItems.removeAt(event.index);
selectedFunctions.remove(event.key); selectedFunctions.remove(event.key);
emit(state.copyWith( emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
thenItems: thenItems, selectedFunctions: selectedFunctions));
} else { } else {
final ifItems = List<Map<String, dynamic>>.from(state.ifItems); final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions = final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index); ifItems.removeAt(event.index);
selectedFunctions.remove(event.key); selectedFunctions.remove(event.key);
@ -533,8 +498,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false, isAutomation: false,
isTabToRun: false)); isTabToRun: false));
} else { } else {
emit(state.copyWith( emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
ifItems: ifItems, selectedFunctions: selectedFunctions));
} }
} }
} }
@ -546,13 +510,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
)); ));
} }
FutureOr<void> _onEffectiveTimeEvent( FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime)); emit(state.copyWith(effectiveTime: event.effectiveTime));
} }
FutureOr<void> _onSetRoutineName( FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
routineName: event.name, routineName: event.name,
)); ));
@ -679,8 +641,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// return (thenItems, ifItems, currentFunctions); // return (thenItems, ifItems, currentFunctions);
// } // }
Future<void> _onGetSceneDetails( Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
GetSceneDetails event, Emitter<RoutineState> emit) async {
try { try {
emit(state.copyWith( emit(state.copyWith(
isLoading: true, isLoading: true,
@ -728,10 +689,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// if (!deviceCards.containsKey(deviceId)) { // if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = { deviceCards[deviceId] = {
'entityId': action.entityId, 'entityId': action.entityId,
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId, 'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
'uniqueCustomId':
action.type == 'automation' || action.actionExecutor == 'delay'
? action.entityId ? action.entityId
: const Uuid().v4(), : const Uuid().v4(),
'title': action.actionExecutor == 'delay' 'title': action.actionExecutor == 'delay'
@ -773,8 +732,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
), ),
); );
// emit(state.copyWith(automationActionExecutor: action.actionExecutor)); // emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && } else if (action.executorProperty != null && action.actionExecutor != 'delay') {
action.actionExecutor != 'delay') {
final functions = matchingDevice?.functions ?? []; final functions = matchingDevice?.functions ?? [];
final functionCode = action.executorProperty?.functionCode; final functionCode = action.executorProperty?.functionCode;
for (DeviceFunction function in functions) { for (DeviceFunction function in functions) {
@ -840,8 +798,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onResetRoutineState( FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith( emit(state.copyWith(
ifItems: [], ifItems: [],
thenItems: [], thenItems: [],
@ -865,8 +822,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
createRoutineView: false)); createRoutineView: false));
} }
FutureOr<void> _deleteScene( FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
DeleteScene event, Emitter<RoutineState> emit) async {
try { try {
final projectId = await ProjectManager.getProjectUUID() ?? ''; final projectId = await ProjectManager.getProjectUUID() ?? '';
@ -875,8 +831,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
if (state.isTabToRun) { if (state.isTabToRun) {
await SceneApi.deleteScene( await SceneApi.deleteScene(
unitUuid: spaceBloc.state.selectedSpaces[0], unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
sceneId: state.sceneId ?? '');
} else { } else {
await SceneApi.deleteAutomation( await SceneApi.deleteAutomation(
unitUuid: spaceBloc.state.selectedSpaces[0], unitUuid: spaceBloc.state.selectedSpaces[0],
@ -899,14 +854,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
add(const LoadAutomation()); add(const LoadAutomation());
add(ResetRoutineState()); add(ResetRoutineState());
emit(state.copyWith(isLoading: false, createRoutineView: false)); emit(state.copyWith(isLoading: false, createRoutineView: false));
} on APIException catch (e) { } catch (e) {
final errorData = e.message;
String errorMessage = errorData;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: errorMessage, errorMessage: 'Failed to delete scene',
)); ));
CustomSnackBar.redSnackBar(errorMessage);
} }
} }
@ -924,8 +876,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// } // }
// } // }
FutureOr<void> _fetchDevices( FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true)); emit(state.copyWith(isLoading: true));
try { try {
final projectUuid = await ProjectManager.getProjectUUID() ?? ''; final projectUuid = await ProjectManager.getProjectUUID() ?? '';
@ -934,21 +885,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
var createRoutineBloc = context.read<CreateRoutineBloc>(); var createRoutineBloc = context.read<CreateRoutineBloc>();
var spaceBloc = context.read<SpaceTreeBloc>(); var spaceBloc = context.read<SpaceTreeBloc>();
if (createRoutineBloc.selectedSpaceId == '' && if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
createRoutineBloc.selectedCommunityId == '') {
for (var communityId in spaceBloc.state.selectedCommunities) { for (var communityId in spaceBloc.state.selectedCommunities) {
List<String> spacesList = List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
for (var spaceId in spacesList) { for (var spaceId in spacesList) {
devices.addAll(await DevicesManagementApi() devices.addAll(
.fetchDevices(communityId, spaceId, projectUuid)); await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
} }
} }
} else { } else {
devices.addAll(await DevicesManagementApi().fetchDevices( devices.addAll(await DevicesManagementApi().fetchDevices(
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
createRoutineBloc.selectedSpaceId,
projectUuid));
} }
emit(state.copyWith(isLoading: false, devices: devices)); emit(state.copyWith(isLoading: false, devices: devices));
@ -957,8 +904,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onUpdateScene( FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
UpdateScene event, Emitter<RoutineState> emit) async {
try { try {
// Check if first action is delay // Check if first action is delay
// if (_isFirstActionDelay(state.thenItems)) { // if (_isFirstActionDelay(state.thenItems)) {
@ -972,8 +918,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (_isLastActionDelay(state.thenItems)) { if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith( emit(state.copyWith(
errorMessage: errorMessage: 'A delay condition cannot be the only or the last action',
'A delay condition cannot be the only or the last action',
isLoading: false, isLoading: false,
)); ));
return; return;
@ -1026,8 +971,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
actions: actions, actions: actions,
); );
final result = final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
if (result['success']) { if (result['success']) {
add(ResetRoutineState()); add(ResetRoutineState());
add(const LoadScenes()); add(const LoadScenes());
@ -1046,8 +990,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
FutureOr<void> _onUpdateAutomation( FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
UpdateAutomation event, Emitter<RoutineState> emit) async {
try { try {
if (state.routineName == null || state.routineName!.isEmpty) { if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith( emit(state.copyWith(
@ -1171,11 +1114,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
errorMessage: result['message'], errorMessage: result['message'],
)); ));
} }
} on APIException catch (e) { } catch (e) {
final errorData = e.message;
emit(state.copyWith( emit(state.copyWith(
isLoading: false, isLoading: false,
errorMessage: errorData, errorMessage: 'Something went wrong',
)); ));
} }
} }
@ -1272,8 +1214,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// if (!deviceThenCards.containsKey(deviceId)) { // if (!deviceThenCards.containsKey(deviceId)) {
deviceThenCards[deviceId] = { deviceThenCards[deviceId] = {
'entityId': action.entityId, 'entityId': action.entityId,
'deviceId': 'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': const Uuid().v4(), 'uniqueCustomId': const Uuid().v4(),
'title': action.actionExecutor == 'delay' 'title': action.actionExecutor == 'delay'
? 'Delay' ? 'Delay'
@ -1308,8 +1249,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
updatedFunctions[uniqueCustomId] = []; updatedFunctions[uniqueCustomId] = [];
} }
if (action.executorProperty != null && if (action.executorProperty != null && action.actionExecutor != 'delay') {
action.actionExecutor != 'delay') {
final functions = matchingDevice.functions; final functions = matchingDevice.functions;
final functionCode = action.executorProperty!.functionCode; final functionCode = action.executorProperty!.functionCode;
for (var function in functions) { for (var function in functions) {
@ -1351,14 +1291,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
final ifItems = deviceIfCards.values final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
.where((card) => card['type'] == 'condition')
.toList();
final thenItems = deviceThenCards.values final thenItems = deviceThenCards.values
.where((card) => .where((card) =>
card['type'] == 'action' || card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList(); .toList();
emit(state.copyWith( emit(state.copyWith(
@ -1380,8 +1316,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} }
} }
Future<void> _onSceneTrigger( Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
SceneTrigger event, Emitter<RoutineState> emit) async {
emit(state.copyWith(loadingSceneId: event.sceneId)); emit(state.copyWith(loadingSceneId: event.sceneId));
try { try {
@ -1423,29 +1358,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (success) { if (success) {
final updatedAutomations = await SceneApi.getAutomationByUnitId( final updatedAutomations = await SceneApi.getAutomationByUnitId(
event.automationStatusUpdate.spaceUuid, event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
event.communityId,
projectId);
// Remove from loading set safely // Remove from loading set safely
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
..remove(event.automationId);
emit(state.copyWith( emit(state.copyWith(
automations: updatedAutomations, automations: updatedAutomations,
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
)); ));
} else { } else {
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
..remove(event.automationId);
emit(state.copyWith( emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update failed', errorMessage: 'Update failed',
)); ));
} }
} catch (e) { } catch (e) {
final updatedLoadingIds = {...state.loadingAutomationIds!} final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
..remove(event.automationId);
emit(state.copyWith( emit(state.copyWith(
loadingAutomationIds: updatedLoadingIds, loadingAutomationIds: updatedLoadingIds,
errorMessage: 'Update error: ${e.toString()}', errorMessage: 'Update error: ${e.toString()}',

View File

@ -48,8 +48,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>( return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
builder: (context, state) {
final communities = state.searchQuery.isNotEmpty final communities = state.searchQuery.isNotEmpty
? state.filteredCommunity ? state.filteredCommunity
: state.communityList; : state.communityList;
@ -133,8 +132,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
) )
else else
CustomSearchBar( CustomSearchBar(
onSearchChanged: (query) => onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
context.read<SpaceTreeBloc>().add(
SearchQueryEvent(query), SearchQueryEvent(query),
), ),
), ),
@ -142,15 +140,6 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
Expanded( Expanded(
child: state.isSearching child: state.isSearching
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: communities.isEmpty
? Center(
child: Text(
'No communities found',
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.textGray,
),
),
)
: SidebarCommunitiesList( : SidebarCommunitiesList(
onScrollToEnd: () { onScrollToEnd: () {
if (!state.paginationIsLoading) { if (!state.paginationIsLoading) {
@ -177,8 +166,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
communities[index].uuid, communities[index].uuid,
), ),
), ),
isExpanded: isExpanded: state.expandedCommunities.contains(
state.expandedCommunities.contains(
communities[index].uuid, communities[index].uuid,
), ),
onItemSelected: () { onItemSelected: () {
@ -194,11 +182,10 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
(space) { (space) {
return CustomExpansionTileSpaceTree( return CustomExpansionTileSpaceTree(
title: space.name, title: space.name,
isExpanded: state.expandedSpaces isExpanded:
.contains(space.uuid), state.expandedSpaces.contains(space.uuid),
onItemSelected: () { onItemSelected: () {
final isParentSelected = final isParentSelected = _isParentSelected(
_isParentSelected(
state, state,
communities[index], communities[index],
space, space,
@ -226,10 +213,9 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
), ),
isSelected: state.selectedSpaces isSelected: state.selectedSpaces
.contains(space.uuid) || .contains(space.uuid) ||
state.soldCheck state.soldCheck.contains(space.uuid),
.contains(space.uuid), isSoldCheck:
isSoldCheck: state.soldCheck state.soldCheck.contains(space.uuid),
.contains(space.uuid),
children: _buildNestedSpaces( children: _buildNestedSpaces(
context, context,
state, state,
@ -243,8 +229,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
}, },
), ),
), ),
if (state.paginationIsLoading) if (state.paginationIsLoading) const CircularProgressIndicator(),
const CircularProgressIndicator(),
], ],
), ),
); );

View File

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

View File

@ -1,13 +1,10 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/auth/model/region_model.dart'; import 'package:syncrow_web/pages/auth/model/region_model.dart';
import 'package:syncrow_web/pages/auth/model/token.dart'; import 'package:syncrow_web/pages/auth/model/token.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
class AuthenticationAPI { class AuthenticationAPI {
static Future<Token> loginWithEmail({required var model}) async { static Future<Token> loginWithEmail({required var model}) async {
try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.login, path: ApiEndpoints.login,
body: model.toJson(), body: model.toJson(),
@ -16,11 +13,6 @@ class AuthenticationAPI {
return Token.fromJson(json['data']); return Token.fromJson(json['data']);
}); });
return response; return response;
} on DioException catch (e) {
final message = e.response?.data['error']['message'] ??
'An error occurred while logging in';
throw APIException(message);
}
} }
static Future forgetPassword({ static Future forgetPassword({
@ -28,18 +20,12 @@ class AuthenticationAPI {
required var password, required var password,
required var otpCode, required var otpCode,
}) async { }) async {
try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.forgetPassword, path: ApiEndpoints.forgetPassword,
body: {"email": email, "password": password, "otpCode": otpCode}, body: {"email": email, "password": password, "otpCode": otpCode},
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) {}); expectedResponseModel: (json) {});
return response; return response;
} on DioException catch (e) {
final message = e.response?.data['error']['message'] ??
'An error occurred while resetting the password';
throw APIException(message);
}
} }
static Future<int?> sendOtp({required String email}) async { static Future<int?> sendOtp({required String email}) async {
@ -53,9 +39,7 @@ class AuthenticationAPI {
return response; return response;
} }
static Future verifyOtp( static Future verifyOtp({required String email, required String otpCode}) async {
{required String email, required String otpCode}) async {
try {
final response = await HTTPService().post( final response = await HTTPService().post(
path: ApiEndpoints.verifyOtp, path: ApiEndpoints.verifyOtp,
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode}, body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
@ -68,11 +52,6 @@ class AuthenticationAPI {
} }
}); });
return response; return response;
} on APIException catch (e) {
throw APIException(e.message);
} catch (e) {
throw APIException('An error occurred while verifying the OTP');
}
} }
static Future<List<RegionModel>> fetchRegion() async { static Future<List<RegionModel>> fetchRegion() async {
@ -80,9 +59,7 @@ class AuthenticationAPI {
path: ApiEndpoints.getRegion, path: ApiEndpoints.getRegion,
showServerMessage: true, showServerMessage: true,
expectedResponseModel: (json) { expectedResponseModel: (json) {
return (json as List) return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
.map((zone) => RegionModel.fromJson(zone))
.toList();
}); });
return response; return response;
} }

View File

@ -1,4 +1,3 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart'; import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart'; import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
@ -6,7 +5,6 @@ import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/cr
import 'package:syncrow_web/pages/routines/models/icon_model.dart'; import 'package:syncrow_web/pages/routines/models/icon_model.dart';
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
import 'package:syncrow_web/pages/routines/models/routine_model.dart'; import 'package:syncrow_web/pages/routines/models/routine_model.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/api_const.dart';
@ -28,10 +26,9 @@ class SceneApi {
); );
debugPrint('create scene response: $response'); debugPrint('create scene response: $response');
return response; return response;
} on DioException catch (e) { } catch (e) {
String errorMessage = debugPrint(e.toString());
e.response?.data['error']['message'][0] ?? 'something went wrong'; rethrow;
throw APIException(errorMessage);
} }
} }
@ -51,10 +48,9 @@ class SceneApi {
); );
debugPrint('create automation response: $response'); debugPrint('create automation response: $response');
return response; return response;
} on DioException catch (e) { } catch (e) {
String errorMessage = debugPrint(e.toString());
e.response?.data['error']['message'][0] ?? 'something went wrong'; rethrow;
throw APIException(errorMessage);
} }
} }
@ -169,10 +165,8 @@ class SceneApi {
}, },
); );
return response; return response;
} on DioException catch (e) { } catch (e) {
String errorMessage = rethrow;
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} }
} }
@ -191,10 +185,8 @@ class SceneApi {
}, },
); );
return response; return response;
} on DioException catch (e) { } catch (e) {
String errorMessage = rethrow;
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} }
} }
@ -225,10 +217,8 @@ class SceneApi {
expectedResponseModel: (json) => json['statusCode'] == 200, expectedResponseModel: (json) => json['statusCode'] == 200,
); );
return response; return response;
} on DioException catch (e) { } catch (e) {
String errorMessage = rethrow;
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} }
} }
@ -246,10 +236,8 @@ class SceneApi {
expectedResponseModel: (json) => json['statusCode'] == 200, expectedResponseModel: (json) => json['statusCode'] == 200,
); );
return response; return response;
} on DioException catch (e) { } catch (e) {
String errorMessage = rethrow;
e.response?.data['error']['message'][0] ?? 'something went wrong';
throw APIException(errorMessage);
} }
} }