mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 14:47:23 +00:00
Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration
This commit is contained in:
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -16,6 +16,7 @@
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_dev.dart",
|
||||
"--web-experimental-hot-reload",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
@ -35,6 +36,7 @@
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main_staging.dart",
|
||||
"--web-experimental-hot-reload",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
@ -54,6 +56,7 @@
|
||||
"3000",
|
||||
"-t",
|
||||
"lib/main.dart",
|
||||
"--web-experimental-hot-reload",
|
||||
],
|
||||
|
||||
"flutterMode": "debug"
|
||||
|
3
assets/icons/close_settings_icon.svg
Normal file
3
assets/icons/close_settings_icon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 24C8.79469 24 5.78123 22.7518 3.51469 20.4853C1.24823 18.2188 0 15.2053 0 12C0 8.79469 1.24823 5.78123 3.51469 3.51469C5.78123 1.24823 8.79469 0 12 0C15.2053 0 18.2188 1.24823 20.4853 3.51469C22.7518 5.78123 24 8.79469 24 12C24 15.2053 22.7518 18.2188 20.4853 20.4853C18.2188 22.7518 15.2053 24 12 24ZM12 1.875C9.2955 1.875 6.75291 2.92819 4.84055 4.84055C2.92819 6.75291 1.875 9.2955 1.875 12C1.875 14.7045 2.92819 17.2471 4.84055 19.1595C6.75291 21.0718 9.2955 22.125 12 22.125C14.7045 22.125 17.2471 21.0718 19.1595 19.1595C21.0718 17.2471 22.125 14.7045 22.125 12C22.125 9.2955 21.0718 6.75291 19.1595 4.84055C17.2471 2.92819 14.7045 1.875 12 1.875ZM15.9775 17.3033L12 13.3258L8.02252 17.3033L6.6967 15.9775L10.6742 12L6.6967 8.02252L8.02252 6.6967L12 10.6742L15.9775 6.6967L17.3033 8.02252L13.3258 12L17.3033 15.9775L15.9775 17.3033Z" fill="#999999" fill-opacity="0.7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 992 B |
3
assets/icons/edit_name_icon_settings.svg
Normal file
3
assets/icons/edit_name_icon_settings.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.4854 3.1332L9.8668 0.515228C9.64704 0.295536 9.34903 0.172119 9.03829 0.172119C8.72755 0.172119 8.42953 0.295536 8.20977 0.515228L0.983989 7.74042C0.874797 7.84895 0.788225 7.97806 0.729285 8.12027C0.670346 8.26249 0.640212 8.41499 0.640629 8.56894V11.1875C0.640629 11.4983 0.764094 11.7964 0.983864 12.0161C1.20363 12.2359 1.5017 12.3594 1.8125 12.3594H11.6563C11.8427 12.3594 12.0216 12.2853 12.1534 12.1534C12.2853 12.0216 12.3594 11.8427 12.3594 11.6562C12.3594 11.4698 12.2853 11.2909 12.1534 11.1591C12.0216 11.0272 11.8427 10.9531 11.6563 10.9531H6.32422L12.4854 4.79081C12.5942 4.68199 12.6806 4.55278 12.7395 4.41057C12.7984 4.26836 12.8288 4.11594 12.8288 3.96201C12.8288 3.80807 12.7984 3.65565 12.7395 3.51344C12.6806 3.37123 12.5942 3.24202 12.4854 3.1332ZM4.33204 10.9531H2.04688V8.66796L6.96875 3.74609L9.25391 6.03124L4.33204 10.9531ZM10.25 5.03515L7.96485 2.74999L9.03946 1.67538L11.3246 3.96054L10.25 5.03515Z" fill="#D5D5D5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
57
lib/pages/analytics/models/air_quality_data_model.dart
Normal file
57
lib/pages/analytics/models/air_quality_data_model.dart
Normal file
@ -0,0 +1,57 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
|
||||
class AirQualityDataModel extends Equatable {
|
||||
const AirQualityDataModel({
|
||||
required this.date,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
final DateTime date;
|
||||
final List<AirQualityPercentageData> data;
|
||||
|
||||
factory AirQualityDataModel.fromJson(Map<String, dynamic> json) {
|
||||
return AirQualityDataModel(
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
data: (json['data'] as List<dynamic>)
|
||||
.map((e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
static final Map<String, Color> metricColors = {
|
||||
'good': ColorsManager.goodGreen.withValues(alpha: 0.7),
|
||||
'moderate': ColorsManager.moderateYellow.withValues(alpha: 0.7),
|
||||
'poor': ColorsManager.poorOrange.withValues(alpha: 0.7),
|
||||
'unhealthy': ColorsManager.unhealthyRed.withValues(alpha: 0.7),
|
||||
'severe': ColorsManager.severePink.withValues(alpha: 0.7),
|
||||
'hazardous': ColorsManager.hazardousPurple.withValues(alpha: 0.7),
|
||||
};
|
||||
|
||||
@override
|
||||
List<Object?> get props => [date, data];
|
||||
}
|
||||
|
||||
class AirQualityPercentageData extends Equatable {
|
||||
const AirQualityPercentageData({
|
||||
required this.type,
|
||||
required this.name,
|
||||
required this.percentage,
|
||||
});
|
||||
|
||||
final String type;
|
||||
final String name;
|
||||
final double percentage;
|
||||
|
||||
factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) {
|
||||
return AirQualityPercentageData(
|
||||
type: json['type'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
percentage: (json['percentage'] as num?)?.toDouble() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [type, name, percentage];
|
||||
}
|
@ -23,17 +23,18 @@ class AnalyticsDevice {
|
||||
return AnalyticsDevice(
|
||||
uuid: json['uuid'] as String,
|
||||
name: json['name'] as String,
|
||||
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
||||
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'] as String)
|
||||
: null,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.parse(json['updatedAt'] as String)
|
||||
: null,
|
||||
deviceTuyaUuid: json['deviceTuyaUuid'] as String?,
|
||||
isActive: json['isActive'] as bool?,
|
||||
productDevice: json['productDevice'] != null
|
||||
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||
: null,
|
||||
spaceUuid: (json['spaces'] as List<dynamic>?)
|
||||
?.map((e) => e['uuid'])
|
||||
.firstOrNull
|
||||
?.toString(),
|
||||
productDevice: json['productDevice'] != null
|
||||
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
|
||||
: null,
|
||||
spaceUuid: json['spaceUuid'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -60,8 +61,12 @@ class ProductDevice {
|
||||
factory ProductDevice.fromJson(Map<String, dynamic> json) {
|
||||
return ProductDevice(
|
||||
uuid: json['uuid'] as String?,
|
||||
createdAt: json['createdAt'] != null ? DateTime.parse(json['createdAt'] as String) : null,
|
||||
updatedAt: json['updatedAt'] != null ? DateTime.parse(json['updatedAt'] as String) : null,
|
||||
createdAt: json['createdAt'] != null
|
||||
? DateTime.parse(json['createdAt'] as String)
|
||||
: null,
|
||||
updatedAt: json['updatedAt'] != null
|
||||
? DateTime.parse(json['updatedAt'] as String)
|
||||
: null,
|
||||
catName: json['catName'] as String?,
|
||||
prodId: json['prodId'] as String?,
|
||||
name: json['name'] as String?,
|
||||
|
@ -1,18 +1,49 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class RangeOfAqi extends Equatable {
|
||||
final double min;
|
||||
final double avg;
|
||||
final double max;
|
||||
final DateTime date;
|
||||
final List<RangeOfAqiValue> data;
|
||||
|
||||
const RangeOfAqi({
|
||||
required this.min,
|
||||
required this.avg,
|
||||
required this.max,
|
||||
required this.data,
|
||||
required this.date,
|
||||
});
|
||||
|
||||
factory RangeOfAqi.fromJson(Map<String, dynamic> json) {
|
||||
return RangeOfAqi(
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
data: (json['data'] as List<dynamic>)
|
||||
.map((e) => RangeOfAqiValue.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [min, avg, max, date];
|
||||
List<Object?> get props => [data, date];
|
||||
}
|
||||
|
||||
class RangeOfAqiValue extends Equatable {
|
||||
final String type;
|
||||
final double min;
|
||||
final double average;
|
||||
final double max;
|
||||
|
||||
const RangeOfAqiValue({
|
||||
required this.type,
|
||||
required this.min,
|
||||
required this.average,
|
||||
required this.max,
|
||||
});
|
||||
|
||||
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
||||
return RangeOfAqiValue(
|
||||
type: json['type'] as String,
|
||||
min: (json['min'] as num).toDouble(),
|
||||
average: (json['average'] as num).toDouble(),
|
||||
max: (json['max'] as num).toDouble(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [type, min, average, max];
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||
|
||||
part 'air_quality_distribution_event.dart';
|
||||
part 'air_quality_distribution_state.dart';
|
||||
|
||||
class AirQualityDistributionBloc
|
||||
extends Bloc<AirQualityDistributionEvent, AirQualityDistributionState> {
|
||||
final AirQualityDistributionService _aqiDistributionService;
|
||||
|
||||
AirQualityDistributionBloc(
|
||||
this._aqiDistributionService,
|
||||
) : super(const AirQualityDistributionState()) {
|
||||
on<LoadAirQualityDistribution>(_onLoadAirQualityDistribution);
|
||||
on<ClearAirQualityDistribution>(_onClearAirQualityDistribution);
|
||||
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
|
||||
}
|
||||
|
||||
Future<void> _onLoadAirQualityDistribution(
|
||||
LoadAirQualityDistribution event,
|
||||
Emitter<AirQualityDistributionState> emit,
|
||||
) async {
|
||||
try {
|
||||
emit(state.copyWith(status: AirQualityDistributionStatus.loading));
|
||||
final result = await _aqiDistributionService.getAirQualityDistribution(
|
||||
event.param,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: AirQualityDistributionStatus.success,
|
||||
chartData: result,
|
||||
filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
AirQualityDistributionState(
|
||||
status: AirQualityDistributionStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
selectedAqiType: state.selectedAqiType,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onClearAirQualityDistribution(
|
||||
ClearAirQualityDistribution event,
|
||||
Emitter<AirQualityDistributionState> emit,
|
||||
) async {
|
||||
emit(const AirQualityDistributionState());
|
||||
}
|
||||
|
||||
void _onUpdateAqiTypeEvent(
|
||||
UpdateAqiTypeEvent event,
|
||||
Emitter<AirQualityDistributionState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedAqiType: event.aqiType,
|
||||
filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<AirQualityDataModel> _arrangeChartDataByType(
|
||||
List<AirQualityDataModel> data,
|
||||
AqiType aqiType,
|
||||
) {
|
||||
final filteredData = data.map(
|
||||
(data) => AirQualityDataModel(
|
||||
date: data.date,
|
||||
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||
),
|
||||
);
|
||||
return filteredData.toList();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
part of 'air_quality_distribution_bloc.dart';
|
||||
|
||||
sealed class AirQualityDistributionEvent extends Equatable {
|
||||
const AirQualityDistributionEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
final class LoadAirQualityDistribution extends AirQualityDistributionEvent {
|
||||
final GetAirQualityDistributionParam param;
|
||||
|
||||
const LoadAirQualityDistribution(this.param);
|
||||
|
||||
@override
|
||||
List<Object> get props => [param];
|
||||
}
|
||||
|
||||
final class UpdateAqiTypeEvent extends AirQualityDistributionEvent {
|
||||
const UpdateAqiTypeEvent(this.aqiType);
|
||||
|
||||
final AqiType aqiType;
|
||||
|
||||
@override
|
||||
List<Object> get props => [aqiType];
|
||||
}
|
||||
|
||||
final class ClearAirQualityDistribution extends AirQualityDistributionEvent {
|
||||
const ClearAirQualityDistribution();
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
part of 'air_quality_distribution_bloc.dart';
|
||||
|
||||
enum AirQualityDistributionStatus {
|
||||
initial,
|
||||
loading,
|
||||
success,
|
||||
failure,
|
||||
}
|
||||
|
||||
class AirQualityDistributionState extends Equatable {
|
||||
const AirQualityDistributionState({
|
||||
this.status = AirQualityDistributionStatus.initial,
|
||||
this.chartData = const [],
|
||||
this.filteredChartData = const [],
|
||||
this.errorMessage,
|
||||
this.selectedAqiType = AqiType.aqi,
|
||||
});
|
||||
|
||||
final AirQualityDistributionStatus status;
|
||||
final List<AirQualityDataModel> chartData;
|
||||
final List<AirQualityDataModel> filteredChartData;
|
||||
final String? errorMessage;
|
||||
final AqiType selectedAqiType;
|
||||
|
||||
AirQualityDistributionState copyWith({
|
||||
AirQualityDistributionStatus? status,
|
||||
List<AirQualityDataModel>? chartData,
|
||||
List<AirQualityDataModel>? filteredChartData,
|
||||
String? errorMessage,
|
||||
AqiType? selectedAqiType,
|
||||
}) {
|
||||
return AirQualityDistributionState(
|
||||
status: status ?? this.status,
|
||||
chartData: chartData ?? this.chartData,
|
||||
filteredChartData: filteredChartData ?? this.filteredChartData,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, chartData, errorMessage, selectedAqiType];
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||
|
||||
@ -11,6 +12,7 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
||||
RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) {
|
||||
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
|
||||
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
|
||||
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
|
||||
}
|
||||
|
||||
final RangeOfAqiService _rangeOfAqiService;
|
||||
@ -20,19 +22,55 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
||||
Emitter<RangeOfAqiState> emit,
|
||||
) async {
|
||||
emit(
|
||||
RangeOfAqiState(
|
||||
status: RangeOfAqiStatus.loading,
|
||||
rangeOfAqi: state.rangeOfAqi,
|
||||
),
|
||||
state.copyWith(status: RangeOfAqiStatus.loading),
|
||||
);
|
||||
try {
|
||||
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
||||
emit(RangeOfAqiState(status: RangeOfAqiStatus.loaded, rangeOfAqi: rangeOfAqi));
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: RangeOfAqiStatus.loaded,
|
||||
rangeOfAqi: rangeOfAqi,
|
||||
filteredRangeOfAqi: _arrangeChartDataByType(
|
||||
rangeOfAqi,
|
||||
state.selectedAqiType,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(RangeOfAqiState(status: RangeOfAqiStatus.failure, errorMessage: '$e'));
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: RangeOfAqiStatus.failure,
|
||||
errorMessage: '$e',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onUpdateAqiTypeEvent(
|
||||
UpdateAqiTypeEvent event,
|
||||
Emitter<RangeOfAqiState> emit,
|
||||
) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedAqiType: event.aqiType,
|
||||
filteredRangeOfAqi: _arrangeChartDataByType(state.rangeOfAqi, event.aqiType),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<RangeOfAqi> _arrangeChartDataByType(
|
||||
List<RangeOfAqi> rangeOfAqi,
|
||||
AqiType aqiType,
|
||||
) {
|
||||
final filteredRangeOfAqi = rangeOfAqi.map(
|
||||
(data) => RangeOfAqi(
|
||||
date: data.date,
|
||||
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||
),
|
||||
);
|
||||
return filteredRangeOfAqi.toList();
|
||||
}
|
||||
|
||||
void _onClearRangeOfAqiEvent(
|
||||
ClearRangeOfAqiEvent event,
|
||||
Emitter<RangeOfAqiState> emit,
|
||||
|
@ -16,6 +16,15 @@ class LoadRangeOfAqiEvent extends RangeOfAqiEvent {
|
||||
List<Object> get props => [param];
|
||||
}
|
||||
|
||||
class UpdateAqiTypeEvent extends RangeOfAqiEvent {
|
||||
const UpdateAqiTypeEvent(this.aqiType);
|
||||
|
||||
final AqiType aqiType;
|
||||
|
||||
@override
|
||||
List<Object> get props => [aqiType];
|
||||
}
|
||||
|
||||
class ClearRangeOfAqiEvent extends RangeOfAqiEvent {
|
||||
const ClearRangeOfAqiEvent();
|
||||
}
|
||||
|
@ -5,14 +5,35 @@ enum RangeOfAqiStatus { initial, loading, loaded, failure }
|
||||
final class RangeOfAqiState extends Equatable {
|
||||
const RangeOfAqiState({
|
||||
this.rangeOfAqi = const [],
|
||||
this.filteredRangeOfAqi = const [],
|
||||
this.status = RangeOfAqiStatus.initial,
|
||||
this.errorMessage,
|
||||
this.selectedAqiType = AqiType.aqi,
|
||||
});
|
||||
|
||||
final RangeOfAqiStatus status;
|
||||
final List<RangeOfAqi> rangeOfAqi;
|
||||
final List<RangeOfAqi> filteredRangeOfAqi;
|
||||
final String? errorMessage;
|
||||
final AqiType selectedAqiType;
|
||||
|
||||
RangeOfAqiState copyWith({
|
||||
RangeOfAqiStatus? status,
|
||||
List<RangeOfAqi>? rangeOfAqi,
|
||||
List<RangeOfAqi>? filteredRangeOfAqi,
|
||||
String? errorMessage,
|
||||
AqiType? selectedAqiType,
|
||||
}) {
|
||||
return RangeOfAqiState(
|
||||
status: status ?? this.status,
|
||||
rangeOfAqi: rangeOfAqi ?? this.rangeOfAqi,
|
||||
filteredRangeOfAqi: filteredRangeOfAqi ?? this.filteredRangeOfAqi,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [status, rangeOfAqi, errorMessage];
|
||||
List<Object?> get props =>
|
||||
[status, rangeOfAqi, filteredRangeOfAqi, errorMessage, selectedAqiType];
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||
@ -15,8 +16,10 @@ abstract final class FetchAirQualityDataHelper {
|
||||
|
||||
static void loadAirQualityData(
|
||||
BuildContext context, {
|
||||
required DateTime date,
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
bool shouldFetchAnalyticsDevices = true,
|
||||
}) {
|
||||
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||
loadAnalyticsDevices(
|
||||
@ -28,7 +31,11 @@ abstract final class FetchAirQualityDataHelper {
|
||||
context,
|
||||
spaceUuid: spaceUuid,
|
||||
date: date,
|
||||
aqiType: AqiType.aqi,
|
||||
);
|
||||
loadAirQualityDistribution(
|
||||
context,
|
||||
spaceUuid: spaceUuid,
|
||||
date: date,
|
||||
);
|
||||
}
|
||||
|
||||
@ -39,7 +46,9 @@ abstract final class FetchAirQualityDataHelper {
|
||||
context.read<RealtimeDeviceChangesBloc>().add(
|
||||
const RealtimeDeviceChangesClosed(),
|
||||
);
|
||||
|
||||
context.read<AirQualityDistributionBloc>().add(
|
||||
const ClearAirQualityDistribution(),
|
||||
);
|
||||
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
|
||||
|
||||
context.read<DeviceLocationBloc>().add(const ClearDeviceLocationEvent());
|
||||
@ -80,16 +89,26 @@ abstract final class FetchAirQualityDataHelper {
|
||||
BuildContext context, {
|
||||
required String spaceUuid,
|
||||
required DateTime date,
|
||||
required AqiType aqiType,
|
||||
}) {
|
||||
context.read<RangeOfAqiBloc>().add(
|
||||
LoadRangeOfAqiEvent(
|
||||
GetRangeOfAqiParam(
|
||||
date: date,
|
||||
spaceUuid: spaceUuid,
|
||||
aqiType: aqiType,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void loadAirQualityDistribution(
|
||||
BuildContext context, {
|
||||
required String spaceUuid,
|
||||
required DateTime date,
|
||||
}) {
|
||||
context.read<AirQualityDistributionBloc>().add(
|
||||
LoadAirQualityDistribution(
|
||||
GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
||||
|
||||
class AirQualityView extends StatelessWidget {
|
||||
@ -23,8 +24,14 @@ class AirQualityView extends StatelessWidget {
|
||||
height: height * 1.2,
|
||||
child: const AirQualityEndSideWidget(),
|
||||
),
|
||||
SizedBox(height: height * 0.5, child: const RangeOfAqiChartBox()),
|
||||
SizedBox(height: height * 0.5, child: const Placeholder()),
|
||||
SizedBox(
|
||||
height: height * 0.5,
|
||||
child: const RangeOfAqiChartBox(),
|
||||
),
|
||||
SizedBox(
|
||||
height: height * 0.5,
|
||||
child: const AqiDistributionChartBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -46,7 +53,7 @@ class AirQualityView extends StatelessWidget {
|
||||
spacing: 20,
|
||||
children: [
|
||||
Expanded(child: RangeOfAqiChartBox()),
|
||||
Expanded(child: Placeholder()),
|
||||
Expanded(child: AqiDistributionChartBox()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -0,0 +1,174 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class AqiDistributionChart extends StatelessWidget {
|
||||
const AqiDistributionChart({super.key, required this.chartData});
|
||||
final List<AirQualityDataModel> chartData;
|
||||
|
||||
static const _rodStackItemsSpacing = 0.4;
|
||||
static const _barWidth = 13.0;
|
||||
static final _barBorderRadius = BorderRadius.circular(22);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sortedData = List<AirQualityDataModel>.from(chartData)
|
||||
..sort(
|
||||
(a, b) => a.date.compareTo(b.date),
|
||||
);
|
||||
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
maxY: 100.1,
|
||||
gridData: EnergyManagementChartsHelper.gridData(
|
||||
horizontalInterval: 20,
|
||||
),
|
||||
borderData: EnergyManagementChartsHelper.borderData(),
|
||||
barTouchData: _barTouchData(context),
|
||||
titlesData: _titlesData(context),
|
||||
barGroups: _buildBarGroups(sortedData),
|
||||
),
|
||||
duration: Duration.zero,
|
||||
);
|
||||
}
|
||||
|
||||
List<BarChartGroupData> _buildBarGroups(List<AirQualityDataModel> sortedData) {
|
||||
return List.generate(sortedData.length, (index) {
|
||||
final data = sortedData[index];
|
||||
final stackItems = <BarChartRodData>[];
|
||||
double currentY = 0;
|
||||
bool isFirstElement = true;
|
||||
|
||||
// Sort data by type to ensure consistent order
|
||||
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||
..sort((a, b) => a.type.compareTo(b.type));
|
||||
|
||||
for (final percentageData in sortedPercentageData) {
|
||||
stackItems.add(
|
||||
BarChartRodData(
|
||||
fromY: currentY,
|
||||
toY: currentY + percentageData.percentage ,
|
||||
color: AirQualityDataModel.metricColors[percentageData.name]!,
|
||||
borderRadius: isFirstElement
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(22),
|
||||
topRight: Radius.circular(22),
|
||||
)
|
||||
: _barBorderRadius,
|
||||
width: _barWidth,
|
||||
),
|
||||
);
|
||||
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
||||
isFirstElement = false;
|
||||
}
|
||||
|
||||
return BarChartGroupData(
|
||||
x: index,
|
||||
barRods: stackItems,
|
||||
groupVertically: true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
BarTouchData _barTouchData(BuildContext context) {
|
||||
return BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (_) => ColorsManager.whiteColors,
|
||||
tooltipBorder: const BorderSide(
|
||||
color: ColorsManager.semiTransparentBlack,
|
||||
),
|
||||
tooltipRoundedRadius: 16,
|
||||
tooltipPadding: const EdgeInsets.all(8),
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
final data = chartData[group.x.toInt()];
|
||||
|
||||
final List<TextSpan> children = [];
|
||||
|
||||
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 12,
|
||||
);
|
||||
|
||||
// Sort data by type to ensure consistent order
|
||||
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||
..sort((a, b) => a.type.compareTo(b.type));
|
||||
|
||||
for (final percentageData in sortedPercentageData) {
|
||||
children.add(TextSpan(
|
||||
text:
|
||||
'\n${percentageData.type.toUpperCase()}: ${percentageData.percentage.toStringAsFixed(1)}%',
|
||||
style: textStyle,
|
||||
));
|
||||
}
|
||||
|
||||
return BarTooltipItem(
|
||||
DateFormat('dd/MM/yyyy').format(data.date),
|
||||
context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.blackColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
FlTitlesData _titlesData(BuildContext context) {
|
||||
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||
context,
|
||||
leftTitlesInterval: 20,
|
||||
);
|
||||
|
||||
final leftTitles = titlesData.leftTitles.copyWith(
|
||||
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||
reservedSize: 70,
|
||||
interval: 20,
|
||||
maxIncluded: false,
|
||||
minIncluded: true,
|
||||
getTitlesWidget: (value, meta) => Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||
child: FittedBox(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'${value.toStringAsFixed(0)}%',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
fontSize: 12,
|
||||
color: ColorsManager.lightGreyColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final bottomTitles = AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, _) => FittedBox(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
chartData[value.toInt()].date.day.toString(),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.lightGreyColor,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
reservedSize: 36,
|
||||
),
|
||||
);
|
||||
|
||||
return titlesData.copyWith(
|
||||
leftTitles: leftTitles,
|
||||
bottomTitles: bottomTitles,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
|
||||
class AqiDistributionChartBox extends StatelessWidget {
|
||||
const AqiDistributionChartBox({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AirQualityDistributionBloc, AirQualityDistributionState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
padding: const EdgeInsetsDirectional.all(30),
|
||||
decoration: subSectionContainerDecoration.copyWith(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (state.errorMessage != null) ...[
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
AqiDistributionChartTitle(
|
||||
isLoading: state.status == AirQualityDistributionStatus.loading,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: AqiDistributionChart(chartData: state.filteredChartData),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||
|
||||
class AqiDistributionChartTitle extends StatelessWidget {
|
||||
const AqiDistributionChartTitle({required this.isLoading, super.key});
|
||||
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
ChartsLoadingWidget(isLoading: isLoading),
|
||||
const Expanded(
|
||||
flex: 3,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: ChartTitle(
|
||||
title: Text('Distribution over Air Quality Index'),
|
||||
),
|
||||
),
|
||||
),
|
||||
FittedBox(
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: AqiTypeDropdown(
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<AirQualityDistributionBloc>()
|
||||
.add(UpdateAqiTypeEvent(value));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -3,17 +3,18 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
enum AqiType {
|
||||
aqi('AQI', ''),
|
||||
pm25('PM2.5', 'µg/m³'),
|
||||
pm10('PM10', 'µg/m³'),
|
||||
hcho('HCHO', 'mg/m³'),
|
||||
tvoc('TVOC', 'µg/m³'),
|
||||
co2('CO2', 'ppm');
|
||||
aqi('AQI', '', 'aqi'),
|
||||
pm25('PM2.5', 'µg/m³', 'pm25'),
|
||||
pm10('PM10', 'µg/m³', 'pm10'),
|
||||
hcho('HCHO', 'mg/m³', 'hcho'),
|
||||
tvoc('TVOC', 'µg/m³', 'tvoc'),
|
||||
co2('CO2', 'ppm', 'co2');
|
||||
|
||||
const AqiType(this.value, this.unit);
|
||||
const AqiType(this.value, this.unit, this.code);
|
||||
|
||||
final String value;
|
||||
final String unit;
|
||||
final String code;
|
||||
}
|
||||
|
||||
class AqiTypeDropdown extends StatefulWidget {
|
||||
|
@ -13,23 +13,37 @@ class RangeOfAqiChart extends StatelessWidget {
|
||||
required this.chartData,
|
||||
});
|
||||
|
||||
List<(List<double> values, Color color, Color? dotColor)> get _lines => [
|
||||
(
|
||||
chartData.map((e) => e.max).toList(),
|
||||
ColorsManager.maxPurple,
|
||||
ColorsManager.maxPurpleDot,
|
||||
),
|
||||
(
|
||||
chartData.map((e) => e.avg).toList(),
|
||||
Colors.white,
|
||||
null,
|
||||
),
|
||||
(
|
||||
chartData.map((e) => e.min).toList(),
|
||||
ColorsManager.minBlue,
|
||||
ColorsManager.minBlueDot,
|
||||
),
|
||||
];
|
||||
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
||||
final sortedData = List<RangeOfAqi>.from(chartData)
|
||||
..sort((a, b) => a.date.compareTo(b.date));
|
||||
|
||||
return [
|
||||
(
|
||||
sortedData.map((e) {
|
||||
final value = e.data.firstOrNull;
|
||||
return value?.max ?? 0;
|
||||
}).toList(),
|
||||
ColorsManager.maxPurple,
|
||||
ColorsManager.maxPurpleDot,
|
||||
),
|
||||
(
|
||||
sortedData.map((e) {
|
||||
final value = e.data.firstOrNull;
|
||||
return value?.average ?? 0;
|
||||
}).toList(),
|
||||
Colors.white,
|
||||
null,
|
||||
),
|
||||
(
|
||||
sortedData.map((e) {
|
||||
final value = e.data.firstOrNull;
|
||||
return value?.min ?? 0;
|
||||
}).toList(),
|
||||
ColorsManager.minBlue,
|
||||
ColorsManager.minBlueDot,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -32,7 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
|
||||
const SizedBox(height: 10),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(child: RangeOfAqiChart(chartData: state.rangeOfAqi)),
|
||||
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -1,15 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
|
||||
class RangeOfAqiChartTitle extends StatelessWidget {
|
||||
const RangeOfAqiChartTitle({required this.isLoading, super.key});
|
||||
const RangeOfAqiChartTitle({
|
||||
required this.isLoading,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool isLoading;
|
||||
|
||||
static const List<(Color color, String title, bool hasBorder)> _colors = [
|
||||
@ -66,12 +69,9 @@ class RangeOfAqiChartTitle extends StatelessWidget {
|
||||
|
||||
if (spaceUuid == null) return;
|
||||
|
||||
FetchAirQualityDataHelper.loadRangeOfAqi(
|
||||
context,
|
||||
spaceUuid: spaceUuid,
|
||||
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||
aqiType: value ?? AqiType.aqi,
|
||||
);
|
||||
if (value != null) {
|
||||
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
@ -26,10 +27,10 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||
|
||||
if (isSpaceSelected) {
|
||||
clearData(context);
|
||||
return;
|
||||
}
|
||||
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
||||
if (hasSelectedSpaces) clearData(context);
|
||||
|
||||
if (isSpaceSelected) return;
|
||||
|
||||
spaceTreeBloc
|
||||
..add(const SpaceTreeClearSelectionEvent())
|
||||
@ -39,21 +40,15 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
context,
|
||||
communityUuid: community.uuid,
|
||||
spaceUuid: space.uuid ?? '',
|
||||
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onChildSpaceSelected(
|
||||
BuildContext context,
|
||||
CommunityModel community,
|
||||
SpaceModel child,
|
||||
) {
|
||||
return onSpaceSelected(context, community, child);
|
||||
}
|
||||
|
||||
@override
|
||||
void clearData(BuildContext context) {
|
||||
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
||||
);
|
||||
FetchAirQualityDataHelper.clearAllData(context);
|
||||
}
|
||||
}
|
||||
|
@ -13,10 +13,5 @@ abstract class AnalyticsDataLoadingStrategy {
|
||||
CommunityModel community,
|
||||
SpaceModel space,
|
||||
);
|
||||
void onChildSpaceSelected(
|
||||
BuildContext context,
|
||||
CommunityModel community,
|
||||
SpaceModel child,
|
||||
);
|
||||
void clearData(BuildContext context);
|
||||
}
|
||||
|
@ -14,24 +14,14 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
CommunityModel community,
|
||||
List<SpaceModel> spaces,
|
||||
) {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnCommunitySelected(
|
||||
community.uuid,
|
||||
spaces,
|
||||
),
|
||||
);
|
||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||
final isCommunitySelected =
|
||||
spaceTreeBloc.state.selectedCommunities.contains(community.uuid);
|
||||
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
|
||||
if (isCommunitySelected) {
|
||||
clearData(context);
|
||||
return;
|
||||
}
|
||||
|
||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||
context,
|
||||
communityId: community.uuid,
|
||||
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -40,21 +30,31 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
CommunityModel community,
|
||||
SpaceModel space,
|
||||
) {
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
OnSpaceSelected(
|
||||
community,
|
||||
space.uuid ?? '',
|
||||
space.children,
|
||||
),
|
||||
);
|
||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
||||
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
||||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
||||
clearData(context);
|
||||
if (isSpaceSelected) {
|
||||
final firstSelectedSpace = spaceTreeBloc.state.selectedSpaces.first;
|
||||
final isTheFirstSelectedSpace = firstSelectedSpace == space.uuid;
|
||||
if (isTheFirstSelectedSpace) {
|
||||
clearData(context);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasSelectedSpaces) {
|
||||
clearData(context);
|
||||
}
|
||||
|
||||
spaceTreeBloc.add(
|
||||
OnSpaceSelected(
|
||||
community,
|
||||
space.uuid ?? '',
|
||||
space.children,
|
||||
),
|
||||
);
|
||||
|
||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||
context,
|
||||
communityId: community.uuid,
|
||||
@ -62,18 +62,11 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onChildSpaceSelected(
|
||||
BuildContext context,
|
||||
CommunityModel community,
|
||||
SpaceModel child,
|
||||
) {
|
||||
return onSpaceSelected(context, community, child);
|
||||
}
|
||||
|
||||
@override
|
||||
void clearData(BuildContext context) {
|
||||
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
||||
);
|
||||
FetchEnergyManagementDataHelper.clearAllData(context);
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,10 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||
|
||||
if (isSpaceSelected) {
|
||||
clearData(context);
|
||||
return;
|
||||
}
|
||||
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
||||
if (hasSelectedSpaces) clearData(context);
|
||||
|
||||
if (isSpaceSelected) return;
|
||||
|
||||
spaceTreeBloc
|
||||
..add(const SpaceTreeClearSelectionEvent())
|
||||
@ -42,18 +42,11 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onChildSpaceSelected(
|
||||
BuildContext context,
|
||||
CommunityModel community,
|
||||
SpaceModel child,
|
||||
) {
|
||||
return onSpaceSelected(context, community, child);
|
||||
}
|
||||
|
||||
@override
|
||||
void clearData(BuildContext context) {
|
||||
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||
context.read<SpaceTreeBloc>().add(
|
||||
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
||||
);
|
||||
FetchOccupancyDataHelper.clearAllData(context);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
||||
strategy.onSpaceSelected(context, community, space);
|
||||
},
|
||||
onSelectChildSpace: (community, child) {
|
||||
strategy.onChildSpaceSelected(context, community, child);
|
||||
strategy.onSpaceSelected(context, community, child);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
||||
@ -56,33 +57,16 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
||||
const Spacer(),
|
||||
Visibility(
|
||||
key: ValueKey(selectedTab),
|
||||
visible: selectedTab == AnalyticsPageTab.energyManagement,
|
||||
visible: selectedTab == AnalyticsPageTab.energyManagement ||
|
||||
selectedTab == AnalyticsPageTab.airQuality,
|
||||
child: Expanded(
|
||||
flex: 2,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
child: AnalyticsDateFilterButton(
|
||||
onDateSelected: (DateTime value) {
|
||||
context.read<AnalyticsDatePickerBloc>().add(
|
||||
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
||||
);
|
||||
|
||||
final spaceTreeState =
|
||||
context.read<SpaceTreeBloc>().state;
|
||||
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||
FetchEnergyManagementDataHelper
|
||||
.loadEnergyManagementData(
|
||||
context,
|
||||
shouldFetchAnalyticsDevices: false,
|
||||
selectedDate: value,
|
||||
communityId:
|
||||
spaceTreeState.selectedCommunities.firstOrNull ??
|
||||
'',
|
||||
spaceId:
|
||||
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||
);
|
||||
}
|
||||
onDateSelected: (value) {
|
||||
_onDateChanged(context, value, selectedTab);
|
||||
},
|
||||
selectedDate: context
|
||||
.watch<AnalyticsDatePickerBloc>()
|
||||
@ -112,4 +96,73 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void _onDateChanged(
|
||||
BuildContext context,
|
||||
DateTime date,
|
||||
AnalyticsPageTab selectedTab,
|
||||
) {
|
||||
context.read<AnalyticsDatePickerBloc>().add(
|
||||
UpdateAnalyticsDatePickerEvent(montlyDate: date),
|
||||
);
|
||||
|
||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||
final communities = spaceTreeState.selectedCommunities;
|
||||
final spaces = spaceTreeState.selectedSpaces;
|
||||
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||
switch (selectedTab) {
|
||||
case AnalyticsPageTab.energyManagement:
|
||||
_onEnergyManagementDateChanged(
|
||||
context,
|
||||
date: date,
|
||||
communityUuid: communities.firstOrNull ?? '',
|
||||
spaceUuid: spaces.firstOrNull ?? '',
|
||||
);
|
||||
break;
|
||||
case AnalyticsPageTab.airQuality:
|
||||
_onAirQualityDateChanged(
|
||||
context,
|
||||
date: date,
|
||||
communityUuid: communities.firstOrNull ?? '',
|
||||
spaceUuid: spaces.firstOrNull ?? '',
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onEnergyManagementDateChanged(
|
||||
BuildContext context, {
|
||||
required DateTime date,
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
}) {
|
||||
context.read<AnalyticsDatePickerBloc>().add(
|
||||
UpdateAnalyticsDatePickerEvent(montlyDate: date),
|
||||
);
|
||||
|
||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||
context,
|
||||
shouldFetchAnalyticsDevices: false,
|
||||
selectedDate: date,
|
||||
communityId: communityUuid,
|
||||
spaceId: spaceUuid,
|
||||
);
|
||||
}
|
||||
|
||||
void _onAirQualityDateChanged(
|
||||
BuildContext context, {
|
||||
required DateTime date,
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
}) {
|
||||
FetchAirQualityDataHelper.loadAirQualityData(
|
||||
context,
|
||||
date: date,
|
||||
communityUuid: communityUuid,
|
||||
spaceUuid: spaceUuid,
|
||||
shouldFetchAnalyticsDevices: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ abstract final class EnergyManagementChartsHelper {
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
maxIncluded: false,
|
||||
minIncluded: false,
|
||||
minIncluded: true,
|
||||
interval: leftTitlesInterval,
|
||||
reservedSize: 110,
|
||||
getTitlesWidget: (value, meta) => Padding(
|
||||
|
@ -16,7 +16,6 @@ import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_
|
||||
abstract final class FetchEnergyManagementDataHelper {
|
||||
const FetchEnergyManagementDataHelper._();
|
||||
|
||||
// static const String _powerClampId = 'cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa';
|
||||
static AnalyticsDevice? getSelectedDevice(BuildContext context) {
|
||||
return context.read<AnalyticsDevicesBloc>().state.selectedDevice;
|
||||
}
|
||||
@ -48,7 +47,6 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
loadTotalEnergyConsumption(
|
||||
context,
|
||||
selectedDate: selectedDate0,
|
||||
communityId: communityId,
|
||||
spaceId: spaceId,
|
||||
);
|
||||
final selectedDevice = getSelectedDevice(context);
|
||||
@ -61,7 +59,6 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
}
|
||||
loadEnergyConsumptionPerDevice(
|
||||
context,
|
||||
communityId: communityId,
|
||||
spaceId: spaceId,
|
||||
selectedDate: selectedDate0,
|
||||
);
|
||||
@ -84,12 +81,10 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
static void loadTotalEnergyConsumption(
|
||||
BuildContext context, {
|
||||
DateTime? selectedDate,
|
||||
required String communityId,
|
||||
required String spaceId,
|
||||
}) {
|
||||
final param = GetTotalEnergyConsumptionParam(
|
||||
spaceId: spaceId,
|
||||
communityId: communityId,
|
||||
monthDate: selectedDate,
|
||||
);
|
||||
context.read<TotalEnergyConsumptionBloc>().add(
|
||||
@ -100,12 +95,10 @@ abstract final class FetchEnergyManagementDataHelper {
|
||||
static void loadEnergyConsumptionPerDevice(
|
||||
BuildContext context, {
|
||||
DateTime? selectedDate,
|
||||
required String communityId,
|
||||
required String spaceId,
|
||||
}) {
|
||||
final param = GetEnergyConsumptionPerDeviceParam(
|
||||
spaceId: spaceId,
|
||||
communityId: communityId,
|
||||
monthDate: selectedDate,
|
||||
);
|
||||
context.read<EnergyConsumptionPerDeviceBloc>().add(
|
||||
|
@ -23,7 +23,6 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
||||
),
|
||||
padding: const EdgeInsets.all(30),
|
||||
child: Column(
|
||||
spacing: 20,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
@ -52,7 +51,9 @@ class EnergyConsumptionPerDeviceChartBox extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: EnergyConsumptionPerDeviceChart(chartData: state.chartData),
|
||||
),
|
||||
|
@ -41,7 +41,7 @@ class EnergyConsumptionPerDeviceDevicesList extends StatelessWidget {
|
||||
.color;
|
||||
|
||||
return Tooltip(
|
||||
message: '${device.name}\n${device.productDevice?.uuid ?? ''}',
|
||||
message: '${device.name}\n${device.spaceUuid ?? ''}',
|
||||
child: ChartInformativeCell(title: Text(device.name), color: deviceColor),
|
||||
);
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
AnalyticsSidebarHeader(
|
||||
title: 'Smart Power Clamp',
|
||||
showSpaceUuid: true,
|
||||
showSpaceUuidInDevicesDropdown: true,
|
||||
onChanged: (device) {
|
||||
FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases(
|
||||
context,
|
||||
|
@ -19,7 +19,6 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
||||
),
|
||||
padding: const EdgeInsets.all(30),
|
||||
child: Column(
|
||||
spacing: 20,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnalyticsErrorWidget(state.errorMessage),
|
||||
@ -39,7 +38,9 @@ class TotalEnergyConsumptionChartBox extends StatelessWidget {
|
||||
const Spacer(flex: 4),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
TotalEnergyConsumptionChart(chartData: state.chartData),
|
||||
],
|
||||
),
|
||||
|
@ -16,7 +16,7 @@ class OccupancyChart extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
maxY: 100.0,
|
||||
maxY: 100.001,
|
||||
gridData: EnergyManagementChartsHelper.gridData().copyWith(
|
||||
checkToShowHorizontalLine: (value) => true,
|
||||
horizontalInterval: 20,
|
||||
@ -134,7 +134,7 @@ class OccupancyChart extends StatelessWidget {
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
(value + 1).toString(),
|
||||
chartData[value.toInt()].date.day.toString(),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.greyColor,
|
||||
fontSize: 8,
|
||||
|
@ -22,7 +22,6 @@ class OccupancyChartBox extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(30),
|
||||
decoration: containerWhiteDecoration,
|
||||
child: Column(
|
||||
spacing: 20,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -65,7 +64,9 @@ class OccupancyChartBox extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(child: OccupancyChart(chartData: state.chartData)),
|
||||
],
|
||||
),
|
||||
|
@ -22,7 +22,6 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(30),
|
||||
decoration: containerWhiteDecoration,
|
||||
child: Column(
|
||||
spacing: 20,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -66,7 +65,9 @@ class OccupancyHeatMapBox extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(height: 0),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: OccupancyHeatMap(
|
||||
heatMapData: state.heatMapData.asMap().map(
|
||||
|
@ -0,0 +1,9 @@
|
||||
class GetAirQualityDistributionParam {
|
||||
final DateTime date;
|
||||
final String spaceUuid;
|
||||
|
||||
const GetAirQualityDistributionParam({
|
||||
required this.date,
|
||||
required this.spaceUuid,
|
||||
});
|
||||
}
|
@ -2,18 +2,15 @@ class GetEnergyConsumptionPerDeviceParam {
|
||||
const GetEnergyConsumptionPerDeviceParam({
|
||||
this.monthDate,
|
||||
this.spaceId,
|
||||
this.communityId,
|
||||
});
|
||||
|
||||
final DateTime? monthDate;
|
||||
final String? spaceId;
|
||||
final String? communityId;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'monthDate':
|
||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||
'communityUuid': communityId,
|
||||
'groupByDevice': true,
|
||||
};
|
||||
}
|
||||
|
@ -1,16 +1,12 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
|
||||
class GetRangeOfAqiParam extends Equatable {
|
||||
final DateTime date;
|
||||
final String spaceUuid;
|
||||
final AqiType aqiType;
|
||||
|
||||
const GetRangeOfAqiParam(
|
||||
{
|
||||
const GetRangeOfAqiParam({
|
||||
required this.date,
|
||||
required this.spaceUuid,
|
||||
required this.aqiType,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -1,12 +1,10 @@
|
||||
class GetTotalEnergyConsumptionParam {
|
||||
final DateTime? monthDate;
|
||||
final String? spaceId;
|
||||
final String? communityId;
|
||||
|
||||
const GetTotalEnergyConsumptionParam({
|
||||
this.monthDate,
|
||||
this.spaceId,
|
||||
this.communityId,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@ -14,7 +12,6 @@ class GetTotalEnergyConsumptionParam {
|
||||
'monthDate':
|
||||
'${monthDate?.year}-${monthDate?.month.toString().padLeft(2, '0')}',
|
||||
if (spaceId == null || spaceId == null) 'spaceUuid': spaceId,
|
||||
'communityUuid': communityId,
|
||||
'groupByDevice': false,
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||
|
||||
abstract interface class AirQualityDistributionService {
|
||||
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||
GetAirQualityDistributionParam param,
|
||||
);
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||
|
||||
class FakeAirQualityDistributionService implements AirQualityDistributionService {
|
||||
final _random = Random();
|
||||
|
||||
@override
|
||||
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||
GetAirQualityDistributionParam param,
|
||||
) async {
|
||||
return Future.delayed(
|
||||
const Duration(milliseconds: 400),
|
||||
() => List.generate(30, (index) {
|
||||
final date = DateTime(2025, 5, 1).add(Duration(days: index));
|
||||
|
||||
final values = _generateRandomPercentages();
|
||||
final nullMask = List.generate(6, (_) => _shouldBeNull());
|
||||
|
||||
if (nullMask.every((isNull) => isNull)) {
|
||||
nullMask[_random.nextInt(6)] = false;
|
||||
}
|
||||
|
||||
final nonNullValues = _redistributePercentages(values, nullMask);
|
||||
|
||||
return AirQualityDataModel(
|
||||
date: date,
|
||||
data: [
|
||||
AirQualityPercentageData(
|
||||
type: AqiType.aqi.code,
|
||||
percentage: nonNullValues[0],
|
||||
name: 'good',
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'moderate',
|
||||
type: AqiType.co2.code,
|
||||
percentage: nonNullValues[1],
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'poor',
|
||||
percentage: nonNullValues[2],
|
||||
type: AqiType.hcho.code,
|
||||
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'unhealthy',
|
||||
percentage: nonNullValues[3],
|
||||
type: AqiType.pm10.code,
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'severe',
|
||||
type: AqiType.pm25.code,
|
||||
percentage: nonNullValues[4],
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'hazardous',
|
||||
percentage: nonNullValues[5],
|
||||
type: AqiType.co2.code,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
List<double> _redistributePercentages(
|
||||
List<double> originalValues,
|
||||
List<bool> nullMask,
|
||||
) {
|
||||
double nonNullSum = 0;
|
||||
for (int i = 0; i < originalValues.length; i++) {
|
||||
if (!nullMask[i]) {
|
||||
nonNullSum += originalValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
return List.generate(originalValues.length, (i) {
|
||||
if (nullMask[i]) return 0;
|
||||
return (originalValues[i] / nonNullSum * 100).roundToDouble();
|
||||
});
|
||||
}
|
||||
|
||||
bool _shouldBeNull() => _random.nextDouble() < 0.6;
|
||||
|
||||
List<double> _generateRandomPercentages() {
|
||||
final values = List.generate(6, (_) => _random.nextDouble());
|
||||
|
||||
final sum = values.reduce((a, b) => a + b);
|
||||
|
||||
return values.map((value) => (value / sum * 100).roundToDouble()).toList();
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class RemoteAirQualityDistributionService implements AirQualityDistributionService {
|
||||
RemoteAirQualityDistributionService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||
GetAirQualityDistributionParam param,
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
queryParameters: {
|
||||
'spaceUuid': param.spaceUuid,
|
||||
'date': param.date.toIso8601String(),
|
||||
},
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.map((e) {
|
||||
final jsonData = e as Map<String, dynamic>;
|
||||
return AirQualityDataModel.fromJson(jsonData);
|
||||
}).toList();
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load energy consumption per phase: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||
|
||||
@ -18,10 +19,15 @@ class FakeRangeOfAqiService implements RangeOfAqiService {
|
||||
final avg = (min + avgDelta).clamp(0.0, 301.0);
|
||||
final max = (avg + maxDelta).clamp(0.0, 301.0);
|
||||
|
||||
return RangeOfAqi(
|
||||
min: min,
|
||||
avg: avg,
|
||||
max: max,
|
||||
return RangeOfAqi(
|
||||
data: [
|
||||
RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
|
||||
RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
|
||||
RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max),
|
||||
RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max),
|
||||
RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max),
|
||||
RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max),
|
||||
],
|
||||
date: date,
|
||||
);
|
||||
});
|
||||
|
@ -0,0 +1,34 @@
|
||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
final class RemoteRangeOfAqiService implements RangeOfAqiService {
|
||||
const RemoteRangeOfAqiService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
|
||||
@override
|
||||
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
queryParameters: {
|
||||
'spaceUuid': param.spaceUuid,
|
||||
'date': param.date.toIso8601String(),
|
||||
},
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||
return mappedData.map((e) {
|
||||
final jsonData = e as Map<String, dynamic>;
|
||||
return RangeOfAqi.fromJson(jsonData);
|
||||
}).toList();
|
||||
},
|
||||
);
|
||||
return response;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load energy consumption per phase: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -11,14 +11,17 @@ class AnalyticsErrorWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Visibility(
|
||||
visible: errorMessage != null || (errorMessage?.isNotEmpty ?? false),
|
||||
child: Text(
|
||||
errorMessage ?? 'Something went wrong',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 8,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(bottom: 10),
|
||||
child: Text(
|
||||
errorMessage ?? 'Something went wrong',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -10,13 +10,13 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
class AnalyticsSidebarHeader extends StatelessWidget {
|
||||
const AnalyticsSidebarHeader({
|
||||
required this.title,
|
||||
this.showSpaceUuid = false,
|
||||
this.showSpaceUuidInDevicesDropdown = false,
|
||||
this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final bool showSpaceUuid;
|
||||
final bool showSpaceUuidInDevicesDropdown;
|
||||
final void Function(AnalyticsDevice device)? onChanged;
|
||||
|
||||
@override
|
||||
@ -49,6 +49,7 @@ class AnalyticsSidebarHeader extends StatelessWidget {
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: AnalyticsDeviceDropdown(
|
||||
showSpaceUuid: showSpaceUuidInDevicesDropdown,
|
||||
onChanged: (value) {
|
||||
context.read<AnalyticsDevicesBloc>().add(
|
||||
SelectAnalyticsDeviceEvent(value),
|
||||
|
@ -13,6 +13,7 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
||||
import 'package:syncrow_web/services/auth_api.dart';
|
||||
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
||||
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
||||
@ -99,7 +100,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
}
|
||||
|
||||
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||
Future<void> changePassword(
|
||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||
emit(LoadingForgetState());
|
||||
try {
|
||||
var response = await AuthenticationAPI.verifyOtp(
|
||||
@ -113,14 +115,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||
emit(SuccessForgetState());
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
final errorData = e.response!.data;
|
||||
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
|
||||
} on APIException catch (e) {
|
||||
final errorMessage = e.message;
|
||||
validate = errorMessage;
|
||||
emit(AuthInitialState());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String? validateCode(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Code is required';
|
||||
@ -149,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
static UserModel? user;
|
||||
bool showValidationMessage = false;
|
||||
|
||||
|
||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||
emit(AuthLoading());
|
||||
if (isChecked) {
|
||||
@ -161,25 +164,24 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
|
||||
token = await AuthenticationAPI.loginWithEmail(
|
||||
model: LoginWithEmailModel(
|
||||
email: event.username,
|
||||
email: event.username.toLowerCase(),
|
||||
password: event.password,
|
||||
),
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
final errorData = e.response!.data;
|
||||
String errorMessage = errorData['error']['message'];
|
||||
if (errorMessage == "Access denied for web platform") {
|
||||
validate = errorMessage;
|
||||
} else {
|
||||
validate = 'Invalid Credentials!';
|
||||
}
|
||||
} on APIException catch (e) {
|
||||
validate = e.message;
|
||||
emit(LoginInitial());
|
||||
return;
|
||||
} catch (e) {
|
||||
validate = 'Something went wrong';
|
||||
emit(LoginInitial());
|
||||
return;
|
||||
}
|
||||
|
||||
if (token.accessTokenIsNotEmpty) {
|
||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||
await storage.write(
|
||||
key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||
const FlutterSecureStorage().write(
|
||||
key: UserModel.userUuidKey,
|
||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
||||
@ -195,6 +197,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
checkBoxToggle(
|
||||
CheckBoxEvent event,
|
||||
Emitter<AuthState> emit,
|
||||
|
@ -211,6 +211,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
onChanged: widget.withSelectAll && widget.data.isNotEmpty
|
||||
? _toggleSelectAll
|
||||
: null,
|
||||
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -281,6 +282,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
|
||||
vertical: 4),
|
||||
|
||||
child: Text(
|
||||
title,
|
||||
style: context.textTheme.titleSmall!.copyWith(
|
||||
@ -301,6 +303,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
required int rowIndex,
|
||||
required int columnIndex,
|
||||
}) {
|
||||
|
||||
bool isBatteryLevel = content.endsWith('%');
|
||||
double? batteryLevel;
|
||||
|
||||
@ -312,6 +315,7 @@ class _DynamicTableState extends State<DynamicTable> {
|
||||
if (isSettingsColumn) {
|
||||
return _buildSettingsIcon(rowIndex, size);
|
||||
}
|
||||
|
||||
|
||||
Color? statusColor;
|
||||
switch (content) {
|
||||
|
@ -1,21 +1,27 @@
|
||||
import 'dart:async';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/model/ac_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
late AcStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
Timer? _countdownTimer;
|
||||
|
||||
AcBloc({required this.deviceId}) : super(AcsInitialState()) {
|
||||
AcBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(AcsInitialState()) {
|
||||
on<AcFetchDeviceStatusEvent>(_onFetchAcStatus);
|
||||
on<AcFetchBatchStatusEvent>(_onFetchAcBatchStatus);
|
||||
on<AcControlEvent>(_onAcControl);
|
||||
@ -34,14 +40,14 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
int scheduledMinutes = 0;
|
||||
|
||||
FutureOr<void> _onFetchAcStatus(
|
||||
AcFetchDeviceStatusEvent event, Emitter<AcsState> emit) async {
|
||||
AcFetchDeviceStatusEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = AcStatusModel.fromJson(event.deviceId, status.status);
|
||||
if (deviceStatus.countdown1 != 0) {
|
||||
// Convert API value to minutes
|
||||
final totalMinutes = deviceStatus.countdown1 * 6;
|
||||
scheduledHours = totalMinutes ~/ 60;
|
||||
scheduledMinutes = totalMinutes % 60;
|
||||
@ -62,30 +68,24 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
void _listenToChanges(deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) async {
|
||||
if (event.snapshot.value == null) return;
|
||||
|
||||
if (_timer != null) {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
}
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(AcStatusUpdated(deviceStatus));
|
||||
}
|
||||
@ -93,146 +93,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onAcStatusUpdated(AcStatusUpdated event, Emitter<AcsState> emit) {
|
||||
void _onAcStatusUpdated(
|
||||
AcStatusUpdated event,
|
||||
Emitter<AcsState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(ACStatusLoaded(status: deviceStatus));
|
||||
}
|
||||
|
||||
FutureOr<void> _onAcControl(
|
||||
AcControlEvent event, Emitter<AcsState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
|
||||
AcControlEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) async {
|
||||
emit(AcsLoadingState());
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(ACStatusLoaded(status: deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
isBatch: false,
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
}
|
||||
try {
|
||||
final success = await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<AcsState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is DioException && e.response != null) {
|
||||
debugPrint('Error response: ${e.response?.data}');
|
||||
}
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
if (!success) {
|
||||
emit(const AcsFailedState(error: 'Failed to control device'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(
|
||||
String deviceId, String code, dynamic oldValue, Emitter<AcsState> emit) {
|
||||
_updateLocalValue(code, oldValue, emit);
|
||||
emit(ACStatusLoaded(status: deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value, Emitter<AcsState> emit) {
|
||||
switch (code) {
|
||||
case 'switch':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(acSwitch: value);
|
||||
}
|
||||
break;
|
||||
case 'temp_set':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(tempSet: value);
|
||||
}
|
||||
break;
|
||||
case 'mode':
|
||||
if (value is String) {
|
||||
deviceStatus = deviceStatus.copyWith(
|
||||
modeString: value,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'level':
|
||||
if (value is String) {
|
||||
deviceStatus = deviceStatus.copyWith(
|
||||
fanSpeedsString: value,
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'child_lock':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(childLock: value);
|
||||
}
|
||||
|
||||
case 'countdown_time':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(countdown1: value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
emit(ACStatusLoaded(status: deviceStatus));
|
||||
}
|
||||
|
||||
dynamic _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch':
|
||||
return deviceStatus.acSwitch;
|
||||
case 'temp_set':
|
||||
return deviceStatus.tempSet;
|
||||
case 'mode':
|
||||
return deviceStatus.modeString;
|
||||
case 'level':
|
||||
return deviceStatus.fanSpeedsString;
|
||||
case 'child_lock':
|
||||
return deviceStatus.childLock;
|
||||
case 'countdown_time':
|
||||
return deviceStatus.countdown1;
|
||||
default:
|
||||
return null;
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchAcBatchStatus(
|
||||
AcFetchBatchStatusEvent event, Emitter<AcsState> emit) async {
|
||||
AcFetchBatchStatusEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = AcStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(ACStatusLoaded(status: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
@ -240,25 +138,32 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
|
||||
FutureOr<void> _onAcBatchControl(
|
||||
AcBatchControlEvent event, Emitter<AcsState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value, emit);
|
||||
|
||||
AcBatchControlEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) async {
|
||||
emit(AcsLoadingState());
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(ACStatusLoaded(status: deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
isBatch: true,
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
);
|
||||
try {
|
||||
final success = await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
emit(const AcsFailedState(error: 'Failed to control devices'));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
AcFactoryResetEvent event, Emitter<AcsState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
AcFactoryResetEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) async {
|
||||
emit(AcsLoadingState());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
@ -275,9 +180,11 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _onClose(OnClose event, Emitter<AcsState> emit) {
|
||||
void _onClose(
|
||||
OnClose event,
|
||||
Emitter<AcsState> emit,
|
||||
) {
|
||||
_countdownTimer?.cancel();
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
void _handleIncreaseTime(IncreaseTimeEvent event, Emitter<AcsState> emit) {
|
||||
@ -300,7 +207,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
));
|
||||
}
|
||||
|
||||
void _handleDecreaseTime(DecreaseTimeEvent event, Emitter<AcsState> emit) {
|
||||
void _handleDecreaseTime(
|
||||
DecreaseTimeEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) {
|
||||
if (state is! ACStatusLoaded) return;
|
||||
final currentState = state as ACStatusLoaded;
|
||||
int totalMinutes = (scheduledHours * 60) + scheduledMinutes;
|
||||
@ -315,7 +225,9 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
|
||||
Future<void> _handleToggleTimer(
|
||||
ToggleScheduleEvent event, Emitter<AcsState> emit) async {
|
||||
ToggleScheduleEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) async {
|
||||
if (state is! ACStatusLoaded) return;
|
||||
final currentState = state as ACStatusLoaded;
|
||||
|
||||
@ -331,37 +243,44 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
|
||||
try {
|
||||
final scaledValue = totalMinutes ~/ 6;
|
||||
await _runDebounce(
|
||||
isBatch: false,
|
||||
deviceId: deviceId,
|
||||
code: 'countdown_time',
|
||||
value: scaledValue,
|
||||
oldValue: scaledValue,
|
||||
emit: emit,
|
||||
final success = await controlDeviceService.controlDevice(
|
||||
deviceUuid: deviceId,
|
||||
status: Status(code: 'countdown_time', value: scaledValue),
|
||||
);
|
||||
_startCountdownTimer(emit);
|
||||
emit(currentState.copyWith(isTimerActive: timerActive));
|
||||
|
||||
if (success) {
|
||||
_startCountdownTimer(emit);
|
||||
emit(currentState.copyWith(isTimerActive: timerActive));
|
||||
} else {
|
||||
timerActive = false;
|
||||
emit(const AcsFailedState(error: 'Failed to set timer'));
|
||||
}
|
||||
} catch (e) {
|
||||
timerActive = false;
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
} else {
|
||||
await _runDebounce(
|
||||
isBatch: false,
|
||||
deviceId: deviceId,
|
||||
code: 'countdown_time',
|
||||
value: 0,
|
||||
oldValue: 0,
|
||||
emit: emit,
|
||||
);
|
||||
_countdownTimer?.cancel();
|
||||
scheduledHours = 0;
|
||||
scheduledMinutes = 0;
|
||||
emit(currentState.copyWith(
|
||||
isTimerActive: timerActive,
|
||||
scheduledHours: 0,
|
||||
scheduledMinutes: 0,
|
||||
));
|
||||
try {
|
||||
final success = await controlDeviceService.controlDevice(
|
||||
deviceUuid: deviceId,
|
||||
status: Status(code: 'countdown_time', value: 0),
|
||||
);
|
||||
|
||||
if (success) {
|
||||
_countdownTimer?.cancel();
|
||||
scheduledHours = 0;
|
||||
scheduledMinutes = 0;
|
||||
emit(currentState.copyWith(
|
||||
isTimerActive: timerActive,
|
||||
scheduledHours: 0,
|
||||
scheduledMinutes: 0,
|
||||
));
|
||||
} else {
|
||||
emit(const AcsFailedState(error: 'Failed to stop timer'));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(AcsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -385,7 +304,10 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
});
|
||||
}
|
||||
|
||||
void _handleUpdateTimer(UpdateTimerEvent event, Emitter<AcsState> emit) {
|
||||
void _handleUpdateTimer(
|
||||
UpdateTimerEvent event,
|
||||
Emitter<AcsState> emit,
|
||||
) {
|
||||
if (state is ACStatusLoaded) {
|
||||
final currentState = state as ACStatusLoaded;
|
||||
emit(currentState.copyWith(
|
||||
@ -400,7 +322,6 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
ApiCountdownValueEvent event, Emitter<AcsState> emit) {
|
||||
if (state is ACStatusLoaded) {
|
||||
final totalMinutes = event.apiValue * 6;
|
||||
final scheduledHours = totalMinutes ~/ 60;
|
||||
scheduledMinutes = totalMinutes % 60;
|
||||
_startCountdownTimer(
|
||||
emit,
|
||||
@ -409,6 +330,43 @@ class AcBloc extends Bloc<AcsEvent, AcsState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
||||
switch (code) {
|
||||
case 'switch':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(acSwitch: value);
|
||||
}
|
||||
break;
|
||||
case 'temp_set':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(tempSet: value);
|
||||
}
|
||||
break;
|
||||
case 'mode':
|
||||
if (value is String) {
|
||||
deviceStatus = deviceStatus.copyWith(modeString: value);
|
||||
}
|
||||
break;
|
||||
case 'level':
|
||||
if (value is String) {
|
||||
deviceStatus = deviceStatus.copyWith(fanSpeedsString: value);
|
||||
}
|
||||
break;
|
||||
case 'child_lock':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(childLock: value);
|
||||
}
|
||||
break;
|
||||
case 'countdown_time':
|
||||
if (value is int) {
|
||||
deviceStatus = deviceStatus.copyWith(countdown1: value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
add(OnClose());
|
||||
|
18
lib/pages/device_managment/ac/factories/ac_bloc_factory.dart
Normal file
18
lib/pages/device_managment/ac/factories/ac_bloc_factory.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
|
||||
abstract final class AcBlocFactory {
|
||||
const AcBlocFactory._();
|
||||
|
||||
static AcBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return AcBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -3,12 +3,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/factories/ac_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_ac_mode.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_current_temp.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/batch_control_list/batch_fan_speed.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
@ -26,8 +26,9 @@ class AcDeviceBatchControlView extends StatelessWidget with HelperResponsiveLayo
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
AcBloc(deviceId: devicesIds.first)..add(AcFetchBatchStatusEvent(devicesIds)),
|
||||
create: (context) => AcBlocFactory.create(
|
||||
deviceId: devicesIds.first,
|
||||
)..add(AcFetchBatchStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<AcBloc, AcsState>(
|
||||
builder: (context, state) {
|
||||
if (state is ACStatusLoaded) {
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/bloc/ac_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/factories/ac_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/ac_mode.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/current_temp.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ac/view/control_list/fan_speed.dart';
|
||||
@ -24,8 +25,9 @@ class AcDeviceControlsView extends StatelessWidget with HelperResponsiveLayout {
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => AcBloc(deviceId: device.uuid!)
|
||||
..add(AcFetchDeviceStatusEvent(device.uuid!)),
|
||||
create: (context) => AcBlocFactory.create(
|
||||
deviceId: device.uuid!,
|
||||
)..add(AcFetchDeviceStatusEvent(device.uuid!)),
|
||||
child: BlocBuilder<AcBloc, AcsState>(
|
||||
builder: (context, state) {
|
||||
final acBloc = BlocProvider.of<AcBloc>(context);
|
||||
|
@ -95,7 +95,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
||||
return const RoutinesView();
|
||||
}
|
||||
if (state.createRoutineView) {
|
||||
return CreateNewRoutineView();
|
||||
return const CreateNewRoutineView();
|
||||
}
|
||||
|
||||
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
||||
|
@ -6,9 +6,11 @@ import 'package:syncrow_web/pages/common/filter/filter_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/bloc/device_mgmt_bloc/device_managment_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/widgets/device_search_filters.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/device_settings_panel.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_batch_control_dialog.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/device_control_dialog.dart';
|
||||
import 'package:syncrow_web/pages/space_tree/view/space_tree_view.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/format_date_time.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
import 'package:syncrow_web/utils/style.dart';
|
||||
@ -58,7 +60,8 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
'Low Battery ($lowBatteryCount)',
|
||||
];
|
||||
|
||||
final buttonLabel = (selectedDevices.length > 1) ? 'Batch Control' : 'Control';
|
||||
final buttonLabel =
|
||||
(selectedDevices.length > 1) ? 'Batch Control' : 'Control';
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
@ -105,18 +108,23 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
if (selectedDevices.length == 1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => DeviceControlDialog(
|
||||
builder: (context) =>
|
||||
DeviceControlDialog(
|
||||
device: selectedDevices.first,
|
||||
),
|
||||
);
|
||||
} else if (selectedDevices.length > 1) {
|
||||
final productTypes = selectedDevices
|
||||
.map((device) => device.productType)
|
||||
.toSet();
|
||||
} else if (selectedDevices.length >
|
||||
1) {
|
||||
final productTypes =
|
||||
selectedDevices
|
||||
.map((device) =>
|
||||
device.productType)
|
||||
.toSet();
|
||||
if (productTypes.length == 1) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => DeviceBatchControlDialog(
|
||||
builder: (context) =>
|
||||
DeviceBatchControlDialog(
|
||||
devices: selectedDevices,
|
||||
),
|
||||
);
|
||||
@ -130,7 +138,9 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: isControlButtonEnabled ? Colors.white : Colors.grey,
|
||||
color: isControlButtonEnabled
|
||||
? Colors.white
|
||||
: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -166,29 +176,40 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
'Installation Date and Time',
|
||||
'Status',
|
||||
'Last Offline Date and Time',
|
||||
'Settings'
|
||||
],
|
||||
data: devicesToShow.map((device) {
|
||||
final combinedSpaceNames = device.spaces != null
|
||||
? device.spaces!.map((space) => space.spaceName).join(' > ') +
|
||||
? device.spaces!
|
||||
.map((space) => space.spaceName)
|
||||
.join(' > ') +
|
||||
(device.community != null
|
||||
? ' > ${device.community!.name}'
|
||||
: '')
|
||||
: (device.community != null ? device.community!.name : '');
|
||||
: (device.community != null
|
||||
? device.community!.name
|
||||
: '');
|
||||
|
||||
return [
|
||||
device.name ?? '',
|
||||
device.productName ?? '',
|
||||
device.uuid ?? '',
|
||||
(device.spaces != null && device.spaces!.isNotEmpty)
|
||||
(device.spaces != null &&
|
||||
device.spaces!.isNotEmpty)
|
||||
? device.spaces![0].spaceName
|
||||
: '',
|
||||
combinedSpaceNames,
|
||||
device.batteryLevel != null ? '${device.batteryLevel}%' : '-',
|
||||
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
|
||||
(device.createTime ?? 0) * 1000)),
|
||||
device.batteryLevel != null
|
||||
? '${device.batteryLevel}%'
|
||||
: '-',
|
||||
formatDateTime(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
(device.createTime ?? 0) * 1000)),
|
||||
device.online == true ? 'Online' : 'Offline',
|
||||
formatDateTime(DateTime.fromMillisecondsSinceEpoch(
|
||||
(device.updateTime ?? 0) * 1000)),
|
||||
formatDateTime(
|
||||
DateTime.fromMillisecondsSinceEpoch(
|
||||
(device.updateTime ?? 0) * 1000)),
|
||||
'Settings',
|
||||
];
|
||||
}).toList(),
|
||||
onSelectionChanged: (selectedRows) {
|
||||
@ -202,6 +223,10 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
.map((device) => device.uuid!)
|
||||
.toList(),
|
||||
isEmpty: devicesToShow.isEmpty,
|
||||
onSettingsPressed: (rowIndex) {
|
||||
final device = devicesToShow[rowIndex];
|
||||
showDeviceSettingsSidebar(context, device);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -213,4 +238,37 @@ class DeviceManagementBody extends StatelessWidget with HelperResponsiveLayout {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void showDeviceSettingsSidebar(BuildContext context, AllDevicesModel device) {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: "Device Settings",
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
pageBuilder: (context, anim1, anim2) {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Material(
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.3,
|
||||
color: ColorsManager.whiteColors,
|
||||
child: DeviceSettingsPanel(
|
||||
device: device,
|
||||
onClose: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, anim1, anim2, child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(anim1),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
@ -7,14 +5,21 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_e
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/help_description.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
late CeilingSensorModel deviceStatus;
|
||||
Timer? _timer;
|
||||
|
||||
CeilingSensorBloc({required this.deviceId}) : super(CeilingInitialState()) {
|
||||
CeilingSensorBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(CeilingInitialState()) {
|
||||
on<CeilingInitialEvent>(_fetchCeilingSensorStatus);
|
||||
on<CeilingFetchDeviceStatusEvent>(_fetchCeilingSensorBatchControl);
|
||||
on<CeilingChangeValueEvent>(_changeValue);
|
||||
@ -26,35 +31,34 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
void _fetchCeilingSensorStatus(
|
||||
CeilingInitialEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
Future<void> _fetchCeilingSensorStatus(
|
||||
CeilingInitialEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
final response = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
_listenToChanges(event.deviceId);
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
if (event.snapshot.value == null) return;
|
||||
|
||||
final usersMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
final statusList = <Status>[];
|
||||
|
||||
List<Status> statusList = [];
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus = CeilingSensorModel.fromJson(statusList);
|
||||
@ -65,149 +69,127 @@ class CeilingSensorBloc extends Bloc<CeilingSensorEvent, CeilingSensorState> {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(StatusUpdated event, Emitter<CeilingSensorState> emit) {
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
}
|
||||
|
||||
void _changeValue(
|
||||
CeilingChangeValueEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
Future<void> _changeValue(
|
||||
CeilingChangeValueEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
if (event.code == 'sensitivity') {
|
||||
deviceStatus.sensitivity = event.value;
|
||||
} else if (event.code == 'none_body_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
} else if (event.code == 'moving_max_dis') {
|
||||
deviceStatus.maxDistance = event.value;
|
||||
} else if (event.code == 'scene') {
|
||||
deviceStatus.spaceType = getSpaceType(event.value);
|
||||
}
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
CeilingBatchControlEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
CeilingBatchControlEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
if (event.code == 'sensitivity') {
|
||||
deviceStatus.sensitivity = event.value;
|
||||
} else if (event.code == 'none_body_time') {
|
||||
deviceStatus.noBodyTime = event.value;
|
||||
} else if (event.code == 'moving_max_dis') {
|
||||
deviceStatus.maxDistance = event.value;
|
||||
} else if (event.code == 'scene') {
|
||||
deviceStatus.spaceType = getSpaceType(event.value);
|
||||
}
|
||||
_updateDeviceFunctionFromCode(event.code, event.value);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
await _runDeBouncer(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
_runDeBouncer({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required Emitter<CeilingSensorState> emit,
|
||||
required bool isBatch,
|
||||
}) {
|
||||
late String id;
|
||||
try {
|
||||
final success = await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
if (response == true && code == 'scene') {
|
||||
emit(CeilingLoadingInitialState());
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
add(CeilingInitialEvent(id));
|
||||
}
|
||||
} catch (_) {
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
add(CeilingInitialEvent(id));
|
||||
if (!success) {
|
||||
emit(const CeilingFailedState(error: 'Failed to control devices'));
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _getDeviceReports(GetCeilingDeviceReportsEvent event,
|
||||
Emitter<CeilingSensorState> emit) async {
|
||||
void _updateDeviceFunctionFromCode(String code, dynamic value) {
|
||||
switch (code) {
|
||||
case 'sensitivity':
|
||||
deviceStatus.sensitivity = value;
|
||||
break;
|
||||
case 'none_body_time':
|
||||
deviceStatus.noBodyTime = value;
|
||||
break;
|
||||
case 'moving_max_dis':
|
||||
deviceStatus.maxDistance = value;
|
||||
break;
|
||||
case 'scene':
|
||||
deviceStatus.spaceType = getSpaceType(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getDeviceReports(
|
||||
GetCeilingDeviceReportsEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) async {
|
||||
if (event.code.isEmpty) {
|
||||
emit(ShowCeilingDescriptionState(description: reportString));
|
||||
return;
|
||||
} else {
|
||||
emit(CeilingReportsLoadingState());
|
||||
// final from = DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch;
|
||||
// final to = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
|
||||
try {
|
||||
// await DevicesManagementApi.getDeviceReportsByDate(deviceId, event.code, from.toString(), to.toString())
|
||||
await DevicesManagementApi.getDeviceReports(deviceId, event.code)
|
||||
.then((value) {
|
||||
emit(CeilingReportsState(deviceReport: value));
|
||||
});
|
||||
} catch (e) {
|
||||
emit(CeilingReportsFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
emit(CeilingReportsLoadingState());
|
||||
try {
|
||||
final value = await DevicesManagementApi.getDeviceReports(
|
||||
deviceId,
|
||||
event.code,
|
||||
);
|
||||
emit(CeilingReportsState(deviceReport: value));
|
||||
} catch (e) {
|
||||
emit(CeilingReportsFailedState(error: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _showDescription(
|
||||
ShowCeilingDescriptionEvent event, Emitter<CeilingSensorState> emit) {
|
||||
ShowCeilingDescriptionEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) {
|
||||
emit(ShowCeilingDescriptionState(description: event.description));
|
||||
}
|
||||
|
||||
void _backToGridView(
|
||||
BackToCeilingGridViewEvent event, Emitter<CeilingSensorState> emit) {
|
||||
BackToCeilingGridViewEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) {
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
}
|
||||
|
||||
FutureOr<void> _fetchCeilingSensorBatchControl(
|
||||
CeilingFetchDeviceStatusEvent event,
|
||||
Emitter<CeilingSensorState> emit) async {
|
||||
Future<void> _fetchCeilingSensorBatchControl(
|
||||
CeilingFetchDeviceStatusEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) async {
|
||||
emit(CeilingLoadingInitialState());
|
||||
try {
|
||||
var response =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
final response = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = CeilingSensorModel.fromJson(response.status);
|
||||
emit(CeilingUpdateState(ceilingSensorModel: deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CeilingFailedState(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
CeilingFactoryResetEvent event, Emitter<CeilingSensorState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
CeilingFactoryResetEvent event,
|
||||
Emitter<CeilingSensorState> emit,
|
||||
) async {
|
||||
emit(CeilingLoadingNewSate(ceilingSensorModel: deviceStatus));
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
|
||||
abstract final class CeilingSensorBlocFactory {
|
||||
const CeilingSensorBlocFactory._();
|
||||
|
||||
static CeilingSensorBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return CeilingSensorBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/factories/ceiling_sensor_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presense_nobody_time.dart';
|
||||
@ -23,8 +23,9 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => CeilingSensorBloc(deviceId: devicesIds.first)
|
||||
..add(CeilingFetchDeviceStatusEvent(devicesIds)),
|
||||
create: (context) => CeilingSensorBlocFactory.create(
|
||||
deviceId: devicesIds.first,
|
||||
)..add(CeilingFetchDeviceStatusEvent(devicesIds)),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState || state is CeilingReportsLoadingState) {
|
||||
@ -110,7 +111,6 @@ class CeilingSensorBatchControlView extends StatelessWidget with HelperResponsiv
|
||||
),
|
||||
),
|
||||
),
|
||||
// FirmwareUpdateWidget(deviceId: devicesIds.first, version: 4),
|
||||
FactoryResetWidget(
|
||||
callFactoryReset: () {
|
||||
context.read<CeilingSensorBloc>().add(
|
||||
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/bloc/ceiling_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/factories/ceiling_sensor_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/model/ceiling_sensor_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_space_type.dart';
|
||||
@ -28,8 +29,9 @@ class CeilingSensorControlsView extends StatelessWidget
|
||||
final isLarge = isLargeScreenSize(context);
|
||||
final isMedium = isMediumScreenSize(context);
|
||||
return BlocProvider(
|
||||
create: (context) => CeilingSensorBloc(deviceId: device.uuid ?? '')
|
||||
..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||
create: (context) => CeilingSensorBlocFactory.create(
|
||||
deviceId: device.uuid ?? '',
|
||||
)..add(CeilingInitialEvent(device.uuid ?? '')),
|
||||
child: BlocBuilder<CeilingSensorBloc, CeilingSensorState>(
|
||||
builder: (context, state) {
|
||||
if (state is CeilingLoadingInitialState ||
|
||||
|
@ -1,17 +1,25 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
late bool deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
CurtainBloc({required this.deviceId}) : super(CurtainInitial()) {
|
||||
CurtainBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(CurtainInitial()) {
|
||||
on<CurtainFetchDeviceStatus>(_onFetchDeviceStatus);
|
||||
on<CurtainFetchBatchStatus>(_onFetchBatchStatus);
|
||||
on<CurtainControl>(_onCurtainControl);
|
||||
@ -20,32 +28,31 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(
|
||||
CurtainFetchDeviceStatus event, Emitter<CurtainState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
CurtainFetchDeviceStatus event,
|
||||
Emitter<CurtainState> emit,
|
||||
) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId, emit);
|
||||
deviceStatus = _checkStatus(status.status[0].value);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CurtainError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
void _listenToChanges(String deviceId, Emitter<CurtainState> emit) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
|
||||
List<Status> statusList = [];
|
||||
final statusList = <Status>[];
|
||||
if (data['status'] != null) {
|
||||
for (var element in data['status']) {
|
||||
statusList.add(
|
||||
@ -57,7 +64,7 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
}
|
||||
}
|
||||
if (statusList.isNotEmpty) {
|
||||
bool newStatus = _checkStatus(statusList[0].value);
|
||||
final newStatus = _checkStatus(statusList[0].value);
|
||||
if (newStatus != deviceStatus) {
|
||||
deviceStatus = newStatus;
|
||||
if (!isClosed) {
|
||||
@ -71,76 +78,32 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(StatusUpdated event, Emitter<CurtainState> emit) {
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<CurtainState> emit,
|
||||
) {
|
||||
emit(CurtainStatusLoading());
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
FutureOr<void> _onCurtainControl(
|
||||
CurtainControl event, Emitter<CurtainState> emit) async {
|
||||
final oldValue = deviceStatus;
|
||||
|
||||
Future<void> _onCurtainControl(
|
||||
CurtainControl event,
|
||||
Emitter<CurtainState> emit,
|
||||
) async {
|
||||
emit(CurtainStatusLoading());
|
||||
_updateLocalValue(event.value, emit);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<CurtainState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
try {
|
||||
final controlValue = event.value ? 'open' : 'close';
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: controlValue),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(!event.value, emit);
|
||||
emit(CurtainControlError(e.toString()));
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
final controlValue = value ? 'open' : 'close';
|
||||
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, controlValue);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: controlValue));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(
|
||||
String deviceId, bool oldValue, Emitter<CurtainState> emit) {
|
||||
_updateLocalValue(oldValue, emit);
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
emit(const CurtainControlError('Failed to control the device.'));
|
||||
}
|
||||
|
||||
void _updateLocalValue(bool value, Emitter<CurtainState> emit) {
|
||||
@ -152,41 +115,44 @@ class CurtainBloc extends Bloc<CurtainEvent, CurtainState> {
|
||||
return command.toLowerCase() == 'open';
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchBatchStatus(
|
||||
CurtainFetchBatchStatus event, Emitter<CurtainState> emit) async {
|
||||
Future<void> _onFetchBatchStatus(
|
||||
CurtainFetchBatchStatus event,
|
||||
Emitter<CurtainState> emit,
|
||||
) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = _checkStatus(status.status[0].value);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(CurtainError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onCurtainBatchControl(
|
||||
CurtainBatchControl event, Emitter<CurtainState> emit) async {
|
||||
final oldValue = deviceStatus;
|
||||
|
||||
Future<void> _onCurtainBatchControl(
|
||||
CurtainBatchControl event,
|
||||
Emitter<CurtainState> emit,
|
||||
) async {
|
||||
emit(CurtainStatusLoading());
|
||||
_updateLocalValue(event.value, emit);
|
||||
|
||||
emit(CurtainStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
try {
|
||||
final controlValue = event.value ? 'open' : 'stop';
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.devicesIds,
|
||||
code: event.code,
|
||||
value: controlValue,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(!event.value, emit);
|
||||
emit(CurtainControlError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
CurtainFactoryReset event, Emitter<CurtainState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
CurtainFactoryReset event,
|
||||
Emitter<CurtainState> emit,
|
||||
) async {
|
||||
emit(CurtainStatusLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
|
||||
abstract final class CurtainBlocFactory {
|
||||
const CurtainBlocFactory._();
|
||||
|
||||
static CurtainBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return CurtainBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/factories/curtain_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
@ -18,7 +19,7 @@ class CurtainBatchStatusView extends StatelessWidget with HelperResponsiveLayout
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
CurtainBloc(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
|
||||
CurtainBlocFactory.create(deviceId: devicesIds.first)..add(CurtainFetchBatchStatus(devicesIds)),
|
||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||
builder: (context, state) {
|
||||
if (state is CurtainStatusLoading) {
|
||||
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/common/curtain_toggle.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/bloc/curtain_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/curtain/factories/curtain_bloc_factory.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
class CurtainStatusControlsView extends StatelessWidget
|
||||
@ -15,7 +16,7 @@ class CurtainStatusControlsView extends StatelessWidget
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => CurtainBloc(deviceId: deviceId)
|
||||
create: (context) => CurtainBlocFactory.create(deviceId: deviceId)
|
||||
..add(CurtainFetchDeviceStatus(deviceId)),
|
||||
child: BlocBuilder<CurtainBloc, CurtainState>(
|
||||
builder: (context, state) {
|
||||
|
@ -0,0 +1,165 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
import 'package:syncrow_web/services/space_mana_api.dart';
|
||||
import 'package:syncrow_web/utils/snack_bar.dart';
|
||||
part 'setting_bloc_event.dart';
|
||||
|
||||
class SettingDeviceBloc extends Bloc<DeviceSettingEvent, DeviceSettingsState> {
|
||||
final String deviceId;
|
||||
SettingDeviceBloc({
|
||||
required this.deviceId,
|
||||
}) : super(const DeviceSettingsInitial()) {
|
||||
on<DeviceSettingInitialInfo>(_fetchDeviceInfo);
|
||||
on<SettingBlocSaveName>(_saveName);
|
||||
on<ChangeNameEvent>(_changeName);
|
||||
on<SettingBlocDeleteDevice>(_deleteDevice);
|
||||
on<SettingBlocFetchRooms>(_fetchRooms);
|
||||
on<SettingBlocAssignRoom>(_onAssignDevice);
|
||||
}
|
||||
final TextEditingController nameController = TextEditingController();
|
||||
List<SubSpaceModel> roomsList = [];
|
||||
bool isEditingName = false;
|
||||
|
||||
bool _validateInputs() {
|
||||
final nameError = _fullNameValidator(nameController.text);
|
||||
if (nameError != null) {
|
||||
CustomSnackBar.displaySnackBar(nameError);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String? _fullNameValidator(String? value) {
|
||||
if (value == null) return 'name is required';
|
||||
final withoutExtraSpaces = value.replaceAll(RegExp(r"\s+"), ' ').trim();
|
||||
if (withoutExtraSpaces.length < 2 || withoutExtraSpaces.length > 30) {
|
||||
return 'name must be between 2 and 30 characters long';
|
||||
}
|
||||
if (RegExp(r"/[^ a-zA-Z0-9-\']/").hasMatch(withoutExtraSpaces)) {
|
||||
return 'Only alphanumeric characters, space, dash and single quote are allowed';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _saveName(
|
||||
SettingBlocSaveName event, Emitter<DeviceSettingsState> emit) async {
|
||||
if (_validateInputs()) return;
|
||||
try {
|
||||
emit(DeviceSettingsLoading());
|
||||
await DevicesManagementApi.putDeviceName(
|
||||
deviceId: deviceId, deviceName: nameController.text);
|
||||
add(DeviceSettingInitialInfo());
|
||||
CustomSnackBar.displaySnackBar('Save Successfully');
|
||||
emit(DeviceSettingsUpdate(deviceName: nameController.text));
|
||||
} catch (e) {
|
||||
emit(DeviceSettingsError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _fetchDeviceInfo(
|
||||
DeviceSettingInitialInfo event, Emitter<DeviceSettingsState> emit) async {
|
||||
try {
|
||||
emit(DeviceSettingsLoading());
|
||||
var response = await DevicesManagementApi.getDeviceInfo(deviceId);
|
||||
DeviceInfoModel deviceInfo = DeviceInfoModel.fromJson(response);
|
||||
nameController.text = deviceInfo.name;
|
||||
emit(DeviceSettingsUpdate(
|
||||
deviceName: nameController.text,
|
||||
deviceInfo: deviceInfo,
|
||||
roomsList: roomsList,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(DeviceSettingsError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
bool editName = false;
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
||||
void _changeName(ChangeNameEvent event, Emitter<DeviceSettingsState> emit) {
|
||||
emit(DeviceSettingsInitial(
|
||||
deviceName: nameController.text,
|
||||
deviceId: deviceId,
|
||||
isEditingName: event.value ?? false,
|
||||
editingNameValue: event.value?.toString() ?? '',
|
||||
deviceInfo: state.deviceInfo,
|
||||
));
|
||||
editName = event.value!;
|
||||
if (editName) {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
focusNode.requestFocus();
|
||||
});
|
||||
} else {
|
||||
add(const SettingBlocSaveName());
|
||||
focusNode.unfocus();
|
||||
}
|
||||
emit(DeviceSettingsUpdate(
|
||||
deviceName: nameController.text,
|
||||
deviceInfo: state.deviceInfo,
|
||||
roomsList: roomsList,
|
||||
));
|
||||
}
|
||||
|
||||
void _deleteDevice(
|
||||
SettingBlocDeleteDevice event, Emitter<DeviceSettingsState> emit) async {
|
||||
try {
|
||||
emit(DeviceSettingsLoading());
|
||||
await DevicesManagementApi.resetDevice(devicesUuid: deviceId);
|
||||
CustomSnackBar.displaySnackBar('Reset Successfully');
|
||||
emit(DeviceSettingsUpdate(
|
||||
deviceName: nameController.text,
|
||||
deviceInfo: state.deviceInfo,
|
||||
roomsList: roomsList,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(DeviceSettingsError(message: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _onAssignDevice(
|
||||
SettingBlocAssignRoom event, Emitter<DeviceSettingsState> emit) async {
|
||||
try {
|
||||
emit(DeviceSettingsLoading());
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
await CommunitySpaceManagementApi.assignDeviceToRoom(
|
||||
communityId: event.communityUuid,
|
||||
spaceId: event.spaceUuid,
|
||||
subSpaceId: event.subSpaceUuid,
|
||||
deviceId: deviceId,
|
||||
projectId: projectUuid);
|
||||
add(DeviceSettingInitialInfo());
|
||||
CustomSnackBar.displaySnackBar('Save Successfully');
|
||||
emit(DeviceSettingsSaveSelectionSuccess());
|
||||
} catch (e) {
|
||||
emit(DeviceSettingsError(message: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void _fetchRooms(
|
||||
SettingBlocFetchRooms event, Emitter<DeviceSettingsState> emit) async {
|
||||
try {
|
||||
emit(DeviceSettingsLoading());
|
||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||
roomsList = await CommunitySpaceManagementApi.getSubSpaceBySpaceId(
|
||||
communityId: event.communityUuid,
|
||||
spaceId: event.spaceUuid,
|
||||
projectId: projectUuid);
|
||||
emit(DeviceSettingsUpdate(
|
||||
deviceName: nameController.text,
|
||||
deviceInfo: state.deviceInfo,
|
||||
roomsList: roomsList,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(DeviceSettingsError(message: e.toString()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
part of 'setting_bloc_bloc.dart';
|
||||
|
||||
abstract class DeviceSettingEvent extends Equatable {
|
||||
const DeviceSettingEvent();
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class SettingBlocSaveDeviceName extends DeviceSettingEvent {
|
||||
final String deviceName;
|
||||
final String deviceId;
|
||||
|
||||
const SettingBlocSaveDeviceName(
|
||||
{required this.deviceName, required this.deviceId});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceName, deviceId];
|
||||
}
|
||||
|
||||
class SettingBlocStartEditingName extends DeviceSettingEvent {}
|
||||
|
||||
class SettingBlocCancelEditingName extends DeviceSettingEvent {}
|
||||
|
||||
class SettingBlocChangeEditingNameValue extends DeviceSettingEvent {
|
||||
final String value;
|
||||
const SettingBlocChangeEditingNameValue(this.value);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [value];
|
||||
}
|
||||
|
||||
class SettingBlocFetchRooms extends DeviceSettingEvent {
|
||||
final String communityUuid;
|
||||
final String spaceUuid;
|
||||
|
||||
const SettingBlocFetchRooms(
|
||||
{required this.communityUuid, required this.spaceUuid});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [communityUuid, spaceUuid];
|
||||
}
|
||||
|
||||
class SettingBlocSaveName extends DeviceSettingEvent {
|
||||
const SettingBlocSaveName();
|
||||
}
|
||||
|
||||
class DeviceSettingInitialInfo extends DeviceSettingEvent {}
|
||||
|
||||
class ChangeNameEvent extends DeviceSettingEvent {
|
||||
final bool? value;
|
||||
const ChangeNameEvent({this.value});
|
||||
}
|
||||
|
||||
class SettingBlocDeleteDevice extends DeviceSettingEvent {}
|
||||
|
||||
class SettingBlocAssignRoom extends DeviceSettingEvent {
|
||||
final String communityUuid;
|
||||
final String spaceUuid;
|
||||
final String subSpaceUuid;
|
||||
|
||||
const SettingBlocAssignRoom({
|
||||
required this.communityUuid,
|
||||
required this.spaceUuid,
|
||||
required this.subSpaceUuid,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [spaceUuid, communityUuid, subSpaceUuid];
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||
|
||||
abstract class DeviceSettingsState extends Equatable {
|
||||
const DeviceSettingsState({this.deviceInfo});
|
||||
|
||||
final DeviceInfoModel? deviceInfo;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceInfo];
|
||||
}
|
||||
|
||||
class DeviceSettingsInitial extends DeviceSettingsState {
|
||||
final String deviceName;
|
||||
final String deviceId;
|
||||
final bool isEditingName;
|
||||
final String editingNameValue;
|
||||
|
||||
const DeviceSettingsInitial({
|
||||
this.deviceName = '',
|
||||
this.deviceId = '',
|
||||
this.isEditingName = false,
|
||||
this.editingNameValue = '',
|
||||
super.deviceInfo,
|
||||
});
|
||||
|
||||
DeviceSettingsInitial copyWith({
|
||||
String? deviceName,
|
||||
String? deviceId,
|
||||
bool? isEditingName,
|
||||
String? editingNameValue,
|
||||
}) =>
|
||||
DeviceSettingsInitial(
|
||||
deviceName: deviceName ?? this.deviceName,
|
||||
deviceId: deviceId ?? this.deviceId,
|
||||
isEditingName: isEditingName ?? this.isEditingName,
|
||||
editingNameValue: editingNameValue ?? this.editingNameValue,
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props =>
|
||||
[deviceName, deviceId, isEditingName, editingNameValue];
|
||||
}
|
||||
|
||||
class DeviceSettingsLoading extends DeviceSettingsState {}
|
||||
|
||||
class DeviceSettingsUpdate extends DeviceSettingsState {
|
||||
final String? deviceName;
|
||||
final List<SubSpaceModel> roomsList;
|
||||
|
||||
const DeviceSettingsUpdate({
|
||||
this.deviceName,
|
||||
super.deviceInfo,
|
||||
this.roomsList = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceName, deviceInfo, roomsList];
|
||||
}
|
||||
|
||||
class DeviceSettingsError extends DeviceSettingsState {
|
||||
final String message;
|
||||
|
||||
const DeviceSettingsError({required this.message});
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
|
||||
class DeviceSettingsFetchRooms extends DeviceSettingsState {
|
||||
final List<SubSpaceModel> roomsList;
|
||||
|
||||
const DeviceSettingsFetchRooms({required this.roomsList});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [roomsList];
|
||||
}
|
||||
|
||||
class DeviceSettingsSaveSelectionSuccess extends DeviceSettingsState {}
|
||||
|
||||
class ChangeNameState extends DeviceSettingsState {}
|
@ -0,0 +1,28 @@
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
|
||||
class DeviceIconTypeHelper {
|
||||
static const Map<String, String> _iconMap = {
|
||||
'AC': Assets.ac,
|
||||
'GW': Assets.gateway,
|
||||
'CPS': Assets.sensors,
|
||||
'DL': Assets.doorLock,
|
||||
'WPS': Assets.sensors,
|
||||
'3G': Assets.gangSwitch,
|
||||
'2G': Assets.twoGang,
|
||||
'1G': Assets.oneGang,
|
||||
'CUR': Assets.curtain,
|
||||
'WH': Assets.waterHeater,
|
||||
'DS': Assets.doorSensor,
|
||||
'1GT': Assets.oneTouchSwitch,
|
||||
'2GT': Assets.twoTouchSwitch,
|
||||
'3GT': Assets.threeTouchSwitch,
|
||||
'GD': Assets.garageDoor,
|
||||
'WL': Assets.waterLeakNormal,
|
||||
'NCPS': Assets.sensors,
|
||||
};
|
||||
|
||||
static String getDeviceIconByTypeCode(String? typeCode) {
|
||||
if (typeCode == null) return Assets.logoHorizontal;
|
||||
return _iconMap[typeCode] ?? Assets.logoHorizontal;
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/web_layout/default_container.dart';
|
||||
|
||||
class DeviceManagementContent extends StatelessWidget {
|
||||
const DeviceManagementContent({
|
||||
super.key,
|
||||
required this.device,
|
||||
required this.subSpaces,
|
||||
required this.deviceInfo,
|
||||
});
|
||||
|
||||
final AllDevicesModel device;
|
||||
final List<SubSpaceModel> subSpaces;
|
||||
final DeviceInfoModel deviceInfo;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget infoRow(
|
||||
{required String label,
|
||||
required String value,
|
||||
Widget? trailing,
|
||||
required Color? valueColor}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: context.theme.textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
textAlign: TextAlign.end,
|
||||
style: context.theme.textTheme.bodyMedium!
|
||||
.copyWith(fontSize: 14, color: valueColor),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
trailing ?? const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return DefaultContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 5),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showSubSpaceDialog(
|
||||
context,
|
||||
communityUuid: device.community!.uuid!,
|
||||
spaceUuid: device.spaces!.first.uuid!,
|
||||
subSpaces: subSpaces,
|
||||
selected: device.subspace!.uuid,
|
||||
);
|
||||
},
|
||||
child: infoRow(
|
||||
label: 'Sub-Space:',
|
||||
value: deviceInfo.subspace.subspaceName,
|
||||
valueColor: ColorsManager.textGray,
|
||||
trailing: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(color: ColorsManager.dividerColor),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: infoRow(
|
||||
label: 'Virtual Address:',
|
||||
value: deviceInfo.productUuid,
|
||||
valueColor: ColorsManager.blackColor,
|
||||
trailing: InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(text: device.productUuid ?? ''),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Virtual Address copied to clipboard'),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.copy,
|
||||
size: 16,
|
||||
color: ColorsManager.greyColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(color: ColorsManager.dividerColor),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: infoRow(
|
||||
label: 'MAC Address:',
|
||||
valueColor: ColorsManager.blackColor,
|
||||
value: deviceInfo.macAddress,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/device_icon_type_helper.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/device_management_content.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/remove_device_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/device_info_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/web_layout/default_container.dart';
|
||||
|
||||
class DeviceSettingsPanel extends StatelessWidget {
|
||||
final VoidCallback? onClose;
|
||||
final AllDevicesModel device;
|
||||
const DeviceSettingsPanel({super.key, this.onClose, required this.device});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final sectionTitle = context.theme.textTheme.titleMedium!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorsManager.grayColor,
|
||||
);
|
||||
return BlocProvider(
|
||||
create: (context) => SettingDeviceBloc(
|
||||
deviceId: device.uuid ?? '',
|
||||
)
|
||||
..add(DeviceSettingInitialInfo())
|
||||
..add(SettingBlocFetchRooms(
|
||||
communityUuid: device.community!.uuid!,
|
||||
spaceUuid: device.spaces!.first.uuid!,
|
||||
)),
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return BlocBuilder<SettingDeviceBloc, DeviceSettingsState>(
|
||||
builder: (context, state) {
|
||||
final _bloc = context.read<SettingDeviceBloc>();
|
||||
final iconPath = DeviceIconTypeHelper.getDeviceIconByTypeCode(
|
||||
device.productType);
|
||||
final deviceInfo = state is DeviceSettingsUpdate
|
||||
? state.deviceInfo ?? DeviceInfoModel.empty()
|
||||
: DeviceInfoModel.empty();
|
||||
final subSpaces =
|
||||
state is DeviceSettingsUpdate ? state.roomsList ?? [] : [];
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width * 0.3,
|
||||
color: ColorsManager.grey25,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 24),
|
||||
child: ListView(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(Assets.closeSettingsIcon),
|
||||
onPressed:
|
||||
onClose ?? () => Navigator.of(context).pop(),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Device Settings',
|
||||
style:
|
||||
context.theme.textTheme.titleLarge!.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
DefaultContainer(
|
||||
child: Row(
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 40,
|
||||
backgroundColor:
|
||||
const Color.fromARGB(177, 213, 213, 213),
|
||||
child: CircleAvatar(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
radius: 36,
|
||||
child: SvgPicture.asset(
|
||||
iconPath,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Device Name:',
|
||||
style: context.textTheme.bodyMedium!
|
||||
.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
maxLength: 30,
|
||||
style: const TextStyle(
|
||||
color: ColorsManager.blackColor,
|
||||
),
|
||||
textAlign: TextAlign.start,
|
||||
focusNode: _bloc.focusNode,
|
||||
controller: _bloc.nameController,
|
||||
enabled: _bloc.editName,
|
||||
onFieldSubmitted: (value) {
|
||||
_bloc.add(const ChangeNameEvent(
|
||||
value: false));
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
fillColor: Colors.white10,
|
||||
counterText: '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Visibility(
|
||||
visible: _bloc.editName != true,
|
||||
replacement: const SizedBox(),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_bloc.add(
|
||||
const ChangeNameEvent(value: true));
|
||||
},
|
||||
child: SvgPicture.asset(
|
||||
Assets.editNameIconSettings,
|
||||
color: ColorsManager.grayColor,
|
||||
height: 20,
|
||||
width: 20,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
Text('Device Management', style: sectionTitle),
|
||||
DeviceManagementContent(
|
||||
device: device,
|
||||
subSpaces: subSpaces.cast<SubSpaceModel>(),
|
||||
deviceInfo: deviceInfo,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
RemoveDeviceWidget(bloc: _bloc),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (state is DeviceSettingsLoading)
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: ColorsManager.primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
import 'package:syncrow_web/web_layout/default_container.dart';
|
||||
|
||||
class RemoveDeviceWidget extends StatelessWidget {
|
||||
const RemoveDeviceWidget({
|
||||
super.key,
|
||||
required SettingDeviceBloc bloc,
|
||||
}) : _bloc = bloc;
|
||||
|
||||
final SettingDeviceBloc _bloc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Remove Device',
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.red,
|
||||
),
|
||||
),
|
||||
content: Text(
|
||||
'Are you sure you want to remove this device?',
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.grayColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_bloc.add(SettingBlocDeleteDevice());
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Remove',
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
color: ColorsManager.red,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: DefaultContainer(
|
||||
padding: const EdgeInsets.all(25),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Remove Device',
|
||||
style: context.textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
color: ColorsManager.red,
|
||||
fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
class DeviceInfoModel {
|
||||
final int activeTime;
|
||||
final String category;
|
||||
final String categoryName;
|
||||
final int createTime;
|
||||
final String gatewayId;
|
||||
final String icon;
|
||||
final String ip;
|
||||
final String lat;
|
||||
final String localKey;
|
||||
final String lon;
|
||||
final String model;
|
||||
final String name;
|
||||
final String nodeId;
|
||||
final bool online;
|
||||
final String ownerId;
|
||||
final String productName;
|
||||
final bool sub;
|
||||
final String timeZone;
|
||||
final int updateTime;
|
||||
final String uuid;
|
||||
final String productUuid;
|
||||
final String productType;
|
||||
final String permissionType;
|
||||
final String macAddress;
|
||||
final Subspace subspace;
|
||||
|
||||
DeviceInfoModel({
|
||||
required this.activeTime,
|
||||
required this.category,
|
||||
required this.categoryName,
|
||||
required this.createTime,
|
||||
required this.gatewayId,
|
||||
required this.icon,
|
||||
required this.ip,
|
||||
required this.lat,
|
||||
required this.localKey,
|
||||
required this.lon,
|
||||
required this.model,
|
||||
required this.name,
|
||||
required this.nodeId,
|
||||
required this.online,
|
||||
required this.ownerId,
|
||||
required this.productName,
|
||||
required this.sub,
|
||||
required this.timeZone,
|
||||
required this.updateTime,
|
||||
required this.uuid,
|
||||
required this.productUuid,
|
||||
required this.productType,
|
||||
required this.permissionType,
|
||||
required this.macAddress,
|
||||
required this.subspace,
|
||||
});
|
||||
|
||||
factory DeviceInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
return DeviceInfoModel(
|
||||
activeTime: json['activeTime'] as int? ?? 0,
|
||||
category: json['category'] ?? '',
|
||||
categoryName: json['categoryName'] as String? ?? '',
|
||||
createTime: json['createTime'] as int? ?? 0,
|
||||
gatewayId: json['gatewayId'] as String? ?? '',
|
||||
icon: json['icon'] as String? ?? '',
|
||||
ip: json['ip'] as String? ?? '',
|
||||
lat: json['lat'] as String? ?? '',
|
||||
localKey: json['localKey'] as String? ?? '',
|
||||
lon: json['lon'] as String? ?? '',
|
||||
model: json['model'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
nodeId: json['nodeId'] as String? ?? '',
|
||||
online: json['online'] as bool? ?? false,
|
||||
ownerId: json['ownerId'] as String? ?? '',
|
||||
productName: json['productName'] as String? ?? '',
|
||||
sub: json['sub'] as bool? ?? false,
|
||||
timeZone: json['timeZone'] as String? ?? '',
|
||||
updateTime: json['updateTime'] as int? ?? 0,
|
||||
uuid: json['uuid'] as String? ?? '',
|
||||
productUuid: json['productUuid'] as String? ?? '',
|
||||
productType: json['productType'] as String? ?? '',
|
||||
permissionType: json['permissionType'] as String? ?? '',
|
||||
macAddress: json['macAddress'] as String? ?? '',
|
||||
subspace:
|
||||
Subspace.fromJson(json['subspace'] as Map<String, dynamic>? ?? {}),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'activeTime': activeTime,
|
||||
'category': category,
|
||||
'categoryName': categoryName,
|
||||
'createTime': createTime,
|
||||
'gatewayId': gatewayId,
|
||||
'icon': icon,
|
||||
'ip': ip,
|
||||
'lat': lat,
|
||||
'localKey': localKey,
|
||||
'lon': lon,
|
||||
'model': model,
|
||||
'name': name,
|
||||
'nodeId': nodeId,
|
||||
'online': online,
|
||||
'ownerId': ownerId,
|
||||
'productName': productName,
|
||||
'sub': sub,
|
||||
'timeZone': timeZone,
|
||||
'updateTime': updateTime,
|
||||
'uuid': uuid,
|
||||
'productUuid': productUuid,
|
||||
'productType': productType,
|
||||
'permissionType': permissionType,
|
||||
'macAddress': macAddress,
|
||||
'subspace': subspace.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
static DeviceInfoModel empty() {
|
||||
return DeviceInfoModel(
|
||||
activeTime: 0,
|
||||
category: '',
|
||||
categoryName: '',
|
||||
createTime: 0,
|
||||
gatewayId: '',
|
||||
icon: '',
|
||||
ip: '',
|
||||
lat: '',
|
||||
localKey: '',
|
||||
lon: '',
|
||||
model: '',
|
||||
name: '',
|
||||
nodeId: '',
|
||||
online: false,
|
||||
ownerId: '',
|
||||
productName: '',
|
||||
sub: false,
|
||||
timeZone: '',
|
||||
updateTime: 0,
|
||||
uuid: '',
|
||||
productUuid: '',
|
||||
productType: '',
|
||||
permissionType: '',
|
||||
macAddress: '',
|
||||
subspace: Subspace(
|
||||
uuid: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
subspaceName: '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Subspace {
|
||||
final String uuid;
|
||||
final String createdAt;
|
||||
final String updatedAt;
|
||||
final String subspaceName;
|
||||
|
||||
Subspace({
|
||||
required this.uuid,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.subspaceName,
|
||||
});
|
||||
|
||||
factory Subspace.fromJson(Map<String, dynamic> json) {
|
||||
return Subspace(
|
||||
uuid: json['uuid'] as String? ?? '',
|
||||
createdAt: json['createdAt'] as String? ?? '',
|
||||
updatedAt: json['updatedAt'] as String? ?? '',
|
||||
subspaceName: json['subspaceName'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'uuid': uuid,
|
||||
'createdAt': createdAt,
|
||||
'updatedAt': updatedAt,
|
||||
'subspaceName': subspaceName,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import 'package:syncrow_web/pages/visitor_password/model/device_model.dart';
|
||||
|
||||
class SubSpaceModel {
|
||||
final String? id;
|
||||
final String? name;
|
||||
List<DeviceModel>? devices;
|
||||
|
||||
SubSpaceModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.devices,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'devices': devices?.map((device) => device.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
factory SubSpaceModel.fromJson(Map<String, dynamic> json) {
|
||||
List<DeviceModel> devices = [];
|
||||
if (json['devices'] != null) {
|
||||
for (var device in json['devices']) {
|
||||
devices.add(DeviceModel.fromJson(device));
|
||||
}
|
||||
}
|
||||
return SubSpaceModel(
|
||||
id: json['uuid'] as String? ?? '',
|
||||
name: json['subspaceName'] as String? ?? '',
|
||||
devices: devices.isNotEmpty ? devices : null as List<DeviceModel>?,
|
||||
);
|
||||
}
|
||||
}
|
115
lib/pages/device_managment/device_setting/sub_space_dialog.dart
Normal file
115
lib/pages/device_managment/device_setting/sub_space_dialog.dart
Normal file
@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/subspace_dialog_buttons.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SubSpaceDialog extends StatefulWidget {
|
||||
final List<SubSpaceModel> subSpaces;
|
||||
final String? selected;
|
||||
final void Function(SubSpaceModel?) onConfirmed;
|
||||
|
||||
const SubSpaceDialog({
|
||||
Key? key,
|
||||
required this.subSpaces,
|
||||
this.selected,
|
||||
required this.onConfirmed,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SubSpaceDialog> createState() => _SubSpaceDialogState();
|
||||
}
|
||||
|
||||
class _SubSpaceDialogState extends State<SubSpaceDialog> {
|
||||
String? _selectedId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedId = widget.selected;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Dialog(
|
||||
backgroundColor: ColorsManager.whiteColors,
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 60),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width * 0.35,
|
||||
padding: const EdgeInsets.fromLTRB(0, 24, 0, 0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Sub-Space',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
color: ColorsManager.blueColor,
|
||||
fontSize: 20),
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 10),
|
||||
...widget.subSpaces.map((space) {
|
||||
return RadioListTile<String>(
|
||||
value: space.id!,
|
||||
groupValue: _selectedId,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectedId = value;
|
||||
});
|
||||
},
|
||||
activeColor: Color(0xFF2962FF),
|
||||
title: Text(
|
||||
space.name ?? 'Unnamed Sub-Space',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 15,
|
||||
color: ColorsManager.grayColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
controlAffinity: ListTileControlAffinity.trailing,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
);
|
||||
}).toList(),
|
||||
const SizedBox(height: 12),
|
||||
const Divider(height: 1, thickness: 1),
|
||||
SubSpaceDialogButtons(selectedId: _selectedId, widget: widget),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showSubSpaceDialog(
|
||||
BuildContext context, {
|
||||
required List<SubSpaceModel> subSpaces,
|
||||
String? selected,
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (ctx) => SubSpaceDialog(
|
||||
subSpaces: subSpaces,
|
||||
selected: selected,
|
||||
onConfirmed: (selectedModel) {
|
||||
if (selectedModel != null) {
|
||||
context.read<SettingDeviceBloc>().add(
|
||||
SettingBlocAssignRoom(
|
||||
communityUuid: communityUuid,
|
||||
spaceUuid: spaceUuid,
|
||||
subSpaceUuid: selectedModel.id ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/bloc/setting_bloc_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/settings_model/sub_space_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/device_setting/sub_space_dialog.dart';
|
||||
import 'package:syncrow_web/utils/color_manager.dart';
|
||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||
|
||||
class SubSpaceDialogButtons extends StatelessWidget {
|
||||
const SubSpaceDialogButtons({
|
||||
super.key,
|
||||
required String? selectedId,
|
||||
required this.widget,
|
||||
}) : _selectedId = selectedId;
|
||||
|
||||
final String? _selectedId;
|
||||
final SubSpaceDialog widget;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
right: BorderSide(
|
||||
color: ColorsManager.dividerColor,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Cancel',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.textGray,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: ColorsManager.dividerColor,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: TextButton(
|
||||
onPressed: _selectedId == null
|
||||
? null
|
||||
: () {
|
||||
final selectedModel = widget.subSpaces.firstWhere(
|
||||
(space) => space.id == _selectedId,
|
||||
orElse: () =>
|
||||
SubSpaceModel(id: null, name: '', devices: []));
|
||||
widget.onConfirmed(selectedModel);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
'Confirm',
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: ColorsManager.secondaryColor,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void showSubSpaceDialog(
|
||||
BuildContext context, {
|
||||
required List<SubSpaceModel> subSpaces,
|
||||
String? selected,
|
||||
required String communityUuid,
|
||||
required String spaceUuid,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
builder: (ctx) => SubSpaceDialog(
|
||||
subSpaces: subSpaces,
|
||||
selected: selected,
|
||||
onConfirmed: (selectedModel) {
|
||||
if (selectedModel != null) {
|
||||
context.read<SettingDeviceBloc>().add(
|
||||
SettingBlocAssignRoom(
|
||||
communityUuid: communityUuid,
|
||||
spaceUuid: spaceUuid,
|
||||
subSpaceUuid: selectedModel.id ?? '',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
|
||||
abstract final class DeviceBlocDependenciesFactory {
|
||||
const DeviceBlocDependenciesFactory._();
|
||||
|
||||
static ControlDeviceService createControlDeviceService() {
|
||||
return DebouncedControlDeviceService(
|
||||
decoratee: RemoteControlDeviceService(),
|
||||
);
|
||||
}
|
||||
|
||||
static BatchControlDevicesService createBatchControlDevicesService() {
|
||||
return DebouncedBatchControlDevicesService(
|
||||
decoratee: RemoteBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
|
||||
abstract final class FlushMountedPresenceSensorBlocFactory {
|
||||
const FlushMountedPresenceSensorBlocFactory._();
|
||||
@ -10,12 +9,8 @@ abstract final class FlushMountedPresenceSensorBlocFactory {
|
||||
}) {
|
||||
return FlushMountedPresenceSensorBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService: DebouncedControlDeviceService(
|
||||
decoratee: RemoteControlDeviceService(),
|
||||
),
|
||||
batchControlDevicesService: DebouncedBatchControlDevicesService(
|
||||
decoratee: RemoteBatchControlDevicesService(),
|
||||
),
|
||||
controlDeviceService: DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService: DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'one_gang_glass_switch_event.dart';
|
||||
@ -13,13 +15,16 @@ part 'one_gang_glass_switch_state.dart';
|
||||
|
||||
class OneGangGlassSwitchBloc
|
||||
extends Bloc<OneGangGlassSwitchEvent, OneGangGlassSwitchState> {
|
||||
OneGangGlassStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
late OneGangGlassStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
OneGangGlassSwitchBloc({required String deviceId})
|
||||
: deviceStatus = OneGangGlassStatusModel(
|
||||
uuid: deviceId, switch1: false, countDown: 0),
|
||||
super(OneGangGlassSwitchInitial()) {
|
||||
OneGangGlassSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(OneGangGlassSwitchInitial()) {
|
||||
on<OneGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<OneGangGlassSwitchControl>(_onControl);
|
||||
on<OneGangGlassSwitchBatchControl>(_onBatchControl);
|
||||
@ -28,160 +33,140 @@ class OneGangGlassSwitchBloc
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDeviceStatus(OneGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<OneGangGlassSwitchState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
OneGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId);
|
||||
deviceStatus =
|
||||
OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId, emit);
|
||||
deviceStatus = OneGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
void _listenToChanges(
|
||||
String deviceId,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
|
||||
List<Status> statusList = [];
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus = OneGangGlassStatusModel.fromJson(
|
||||
usersMap['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
final statusList = <Status>[];
|
||||
if (data['status'] != null) {
|
||||
for (var element in data['status']) {
|
||||
statusList.add(
|
||||
Status(
|
||||
code: element['code'].toString(),
|
||||
value: element['value'].toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (statusList.isNotEmpty) {
|
||||
final newStatus = OneGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||
if (newStatus != deviceStatus) {
|
||||
deviceStatus = newStatus;
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError('Failed to listen to changes: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event, Emitter<OneGangGlassSwitchState> emit) {
|
||||
StatusUpdated event,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
Future<void> _onControl(OneGangGlassSwitchControl event,
|
||||
Emitter<OneGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onControl(
|
||||
OneGangGlassSwitchControl event,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(OneGangGlassFactoryResetEvent event,
|
||||
Emitter<OneGangGlassSwitchState> emit) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.factoryReset(event.factoryReset, event.deviceId);
|
||||
if (!response) {
|
||||
emit(OneGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(OneGangGlassSwitchBatchControl event,
|
||||
Emitter<OneGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
OneGangGlassSwitchBatchControl event,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(
|
||||
OneGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<OneGangGlassSwitchState> emit) async {
|
||||
OneGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = OneGangGlassStatusModel.fromJson(
|
||||
event.deviceIds.first, status.status);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus =
|
||||
OneGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<OneGangGlassSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
Future<void> _onFactoryReset(
|
||||
OneGangGlassFactoryResetEvent event,
|
||||
Emitter<OneGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(OneGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(OneGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
add(OneGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<OneGangGlassSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(OneGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(OneGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
@ -189,19 +174,4 @@ class OneGangGlassSwitchBloc
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
||||
|
||||
abstract final class OneGangGlassSwitchBlocFactory {
|
||||
const OneGangGlassSwitchBlocFactory._();
|
||||
|
||||
static OneGangGlassSwitchBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return OneGangGlassSwitchBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -16,7 +16,7 @@ class OneGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => OneGangGlassSwitchBloc(deviceId: deviceIds.first)
|
||||
create: (context) => OneGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||
..add(OneGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/bloc/one_gang_glass_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/factories/one_gang_glass_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/models/once_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
@ -9,13 +10,13 @@ import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_la
|
||||
class OneGangGlassSwitchControlView extends StatelessWidget with HelperResponsiveLayout {
|
||||
final String deviceId;
|
||||
|
||||
const OneGangGlassSwitchControlView({required this.deviceId, Key? key}) : super(key: key);
|
||||
const OneGangGlassSwitchControlView({required this.deviceId, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
OneGangGlassSwitchBloc(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||
OneGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(OneGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<OneGangGlassSwitchBloc, OneGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is OneGangGlassSwitchLoading) {
|
||||
|
@ -6,12 +6,21 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class WallLightSwitchBloc
|
||||
extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
|
||||
WallLightSwitchBloc({required this.deviceId})
|
||||
: super(WallLightSwitchInitial()) {
|
||||
class WallLightSwitchBloc extends Bloc<WallLightSwitchEvent, WallLightSwitchState> {
|
||||
late WallLightStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
WallLightSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(WallLightSwitchInitial()) {
|
||||
on<WallLightSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<WallLightSwitchControl>(_onControl);
|
||||
on<WallLightSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
||||
@ -20,143 +29,114 @@ class WallLightSwitchBloc
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
late WallLightStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(WallLightSwitchFetchDeviceEvent event,
|
||||
Emitter<WallLightSwitchState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
WallLightSwitchFetchDeviceEvent event,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
|
||||
deviceStatus =
|
||||
WallLightStatusModel.fromJson(event.deviceId, status.status);
|
||||
_listenToChanges(event.deviceId);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId, emit);
|
||||
deviceStatus = WallLightStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(WallLightSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
void _listenToChanges(
|
||||
String deviceId,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
|
||||
List<Status> statusList = [];
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
WallLightStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
final statusList = <Status>[];
|
||||
if (data['status'] != null) {
|
||||
for (var element in data['status']) {
|
||||
statusList.add(
|
||||
Status(
|
||||
code: element['code'].toString(),
|
||||
value: element['value'].toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (statusList.isNotEmpty) {
|
||||
final newStatus = WallLightStatusModel.fromJson(deviceId, statusList);
|
||||
if (newStatus != deviceStatus) {
|
||||
deviceStatus = newStatus;
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
emit(WallLightSwitchError('Failed to listen to changes: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event, Emitter<WallLightSwitchState> emit) {
|
||||
StatusUpdated event,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) {
|
||||
emit(WallLightSwitchLoading());
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
FutureOr<void> _onControl(
|
||||
WallLightSwitchControl event, Emitter<WallLightSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onControl(
|
||||
WallLightSwitchControl event,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(WallLightSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<WallLightSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<WallLightSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
Future<void> _onBatchControl(
|
||||
WallLightSwitchBatchControl event,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(WallLightSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(WallLightSwitchFetchBatchEvent event,
|
||||
Emitter<WallLightSwitchState> emit) async {
|
||||
Future<void> _onFetchBatchStatus(
|
||||
WallLightSwitchFetchBatchEvent event,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
WallLightStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
@ -165,32 +145,10 @@ class WallLightSwitchBloc
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FutureOr<void> _onBatchControl(WallLightSwitchBatchControl event,
|
||||
Emitter<WallLightSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
WallLightFactoryReset event, Emitter<WallLightSwitchState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
WallLightFactoryReset event,
|
||||
Emitter<WallLightSwitchState> emit,
|
||||
) async {
|
||||
emit(WallLightSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
@ -198,12 +156,18 @@ class WallLightSwitchBloc
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(WallLightSwitchError('Failed'));
|
||||
emit(WallLightSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
emit(WallLightSwitchStatusLoaded(deviceStatus));
|
||||
add(WallLightSwitchFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(WallLightSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||
|
||||
abstract final class WallLightSwitchBlocFactory {
|
||||
const WallLightSwitchBlocFactory._();
|
||||
|
||||
static WallLightSwitchBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return WallLightSwitchBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,9 +4,9 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/factories/wall_light_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -18,7 +18,7 @@ class WallLightBatchControlView extends StatelessWidget with HelperResponsiveLay
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => WallLightSwitchBloc(deviceId: deviceIds.first)
|
||||
create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||
..add(WallLightSwitchFetchBatchEvent(deviceIds)),
|
||||
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/bloc/wall_light_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/factories/wall_light_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/one_gang_switch/models/wall_light_status_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
@ -15,7 +16,7 @@ class WallLightDeviceControl extends StatelessWidget
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => WallLightSwitchBloc(deviceId: deviceId)
|
||||
create: (context) => WallLightSwitchBlocFactory.create(deviceId: deviceId)
|
||||
..add(WallLightSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<WallLightSwitchBloc, WallLightSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
@ -157,7 +157,7 @@ class DeviceControlDialog extends StatelessWidget with RouteControlsBasedCode {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
SelectableText(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
|
@ -1,11 +1,14 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'three_gang_glass_switch_event.dart';
|
||||
@ -13,19 +16,16 @@ part 'three_gang_glass_switch_state.dart';
|
||||
|
||||
class ThreeGangGlassSwitchBloc
|
||||
extends Bloc<ThreeGangGlassSwitchEvent, ThreeGangGlassSwitchState> {
|
||||
ThreeGangGlassStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
late ThreeGangGlassStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
ThreeGangGlassSwitchBloc({required String deviceId})
|
||||
: deviceStatus = ThreeGangGlassStatusModel(
|
||||
uuid: deviceId,
|
||||
switch1: false,
|
||||
countDown1: 0,
|
||||
switch2: false,
|
||||
countDown2: 0,
|
||||
switch3: false,
|
||||
countDown3: 0),
|
||||
super(ThreeGangGlassSwitchInitial()) {
|
||||
ThreeGangGlassSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(ThreeGangGlassSwitchInitial()) {
|
||||
on<ThreeGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<ThreeGangGlassSwitchControl>(_onControl);
|
||||
on<ThreeGangGlassSwitchBatchControl>(_onBatchControl);
|
||||
@ -34,188 +34,154 @@ class ThreeGangGlassSwitchBloc
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDeviceStatus(ThreeGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
ThreeGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(event.deviceId, emit);
|
||||
deviceStatus =
|
||||
ThreeGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
_listenToChanges(event.deviceId);
|
||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
void _listenToChanges(
|
||||
String deviceId,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
final stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
final data = event.snapshot.value as Map<dynamic, dynamic>?;
|
||||
if (data == null) return;
|
||||
|
||||
List<Status> statusList = [];
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus = ThreeGangGlassStatusModel.fromJson(
|
||||
usersMap['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
final statusList = <Status>[];
|
||||
if (data['status'] != null) {
|
||||
for (var element in data['status']) {
|
||||
statusList.add(
|
||||
Status(
|
||||
code: element['code'].toString(),
|
||||
value: element['value'].toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (statusList.isNotEmpty) {
|
||||
final newStatus = ThreeGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||
if (newStatus != deviceStatus) {
|
||||
deviceStatus = newStatus;
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
} catch (e) {
|
||||
emit(ThreeGangGlassSwitchError('Failed to listen to changes: $e'));
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event, Emitter<ThreeGangGlassSwitchState> emit) {
|
||||
StatusUpdated event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) {
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
Future<void> _onControl(ThreeGangGlassSwitchControl event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onControl(
|
||||
ThreeGangGlassSwitchControl event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(ThreeGangGlassSwitchBatchControl event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
ThreeGangGlassSwitchBatchControl event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(
|
||||
ThreeGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
||||
ThreeGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = ThreeGangGlassStatusModel.fromJson(
|
||||
event.deviceIds.first, status.status);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus =
|
||||
ThreeGangGlassStatusModel.fromJson(event.deviceIds.first, status.status);
|
||||
emit(ThreeGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(ThreeGangGlassFactoryReset event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
ThreeGangGlassFactoryReset event,
|
||||
Emitter<ThreeGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(ThreeGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.factoryReset(event.factoryReset, event.deviceId);
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(ThreeGangGlassSwitchError('Failed'));
|
||||
emit(ThreeGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
add(ThreeGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(ThreeGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<ThreeGangGlassSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<ThreeGangGlassSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
emit(ThreeGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
} else if (code == 'switch_2') {
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
} else if (code == 'switch_3') {
|
||||
deviceStatus = deviceStatus.copyWith(switch3: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
break;
|
||||
case 'switch_2':
|
||||
return deviceStatus.switch2;
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
break;
|
||||
case 'switch_3':
|
||||
return deviceStatus.switch3;
|
||||
default:
|
||||
return false;
|
||||
deviceStatus = deviceStatus.copyWith(switch3: value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
part of 'three_gang_glass_switch_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class ThreeGangGlassSwitchEvent {}
|
||||
abstract class ThreeGangGlassSwitchEvent extends Equatable {
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class ThreeGangGlassSwitchFetchDeviceEvent extends ThreeGangGlassSwitchEvent {
|
||||
final String deviceId;
|
||||
@ -19,6 +22,9 @@ class ThreeGangGlassSwitchControl extends ThreeGangGlassSwitchEvent {
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
|
||||
@ -31,6 +37,9 @@ class ThreeGangGlassSwitchBatchControl extends ThreeGangGlassSwitchEvent {
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class ThreeGangGlassSwitchFetchBatchStatusEvent
|
||||
@ -38,6 +47,9 @@ class ThreeGangGlassSwitchFetchBatchStatusEvent
|
||||
final List<String> deviceIds;
|
||||
|
||||
ThreeGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceIds];
|
||||
}
|
||||
|
||||
class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
|
||||
@ -48,6 +60,9 @@ class ThreeGangGlassFactoryReset extends ThreeGangGlassSwitchEvent {
|
||||
required this.deviceId,
|
||||
required this.factoryReset,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [deviceId, factoryReset];
|
||||
}
|
||||
|
||||
class StatusUpdated extends ThreeGangGlassSwitchEvent {
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
||||
|
||||
abstract final class ThreeGangGlassSwitchBlocFactory {
|
||||
const ThreeGangGlassSwitchBlocFactory._();
|
||||
|
||||
static ThreeGangGlassSwitchBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return ThreeGangGlassSwitchBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/models/three_gang_glass_switch.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchBatchControlView extends StatelessWidget with HelperRe
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => ThreeGangGlassSwitchBloc(deviceId: deviceIds.first)
|
||||
create: (context) => ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||
..add(ThreeGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/bloc/three_gang_glass_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_g_glass_switch/factories/three_gang_glass_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -16,7 +17,7 @@ class ThreeGangGlassSwitchControlView extends StatelessWidget with HelperRespons
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
ThreeGangGlassSwitchBloc(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||
ThreeGangGlassSwitchBlocFactory.create(deviceId: deviceId)..add(ThreeGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<ThreeGangGlassSwitchBloc, ThreeGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
if (state is ThreeGangGlassSwitchLoading) {
|
||||
|
@ -1,12 +1,14 @@
|
||||
// ignore_for_file: invalid_use_of_visible_for_testing_member
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'living_room_event.dart';
|
||||
@ -15,9 +17,14 @@ part 'living_room_state.dart';
|
||||
class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||
late LivingRoomStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
LivingRoomBloc({required this.deviceId}) : super(LivingRoomInitial()) {
|
||||
LivingRoomBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(LivingRoomInitial()) {
|
||||
on<LivingRoomFetchDeviceStatusEvent>(_onFetchDeviceStatus);
|
||||
on<LivingRoomControl>(_livingRoomControl);
|
||||
on<LivingRoomBatchControl>(_livingRoomBatchControl);
|
||||
@ -26,156 +33,108 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(LivingRoomFetchDeviceStatusEvent event,
|
||||
Emitter<LivingRoomState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
LivingRoomFetchDeviceStatusEvent event,
|
||||
Emitter<LivingRoomState> emit,
|
||||
) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus =
|
||||
LivingRoomStatusModel.fromJson(event.deviceId, status.status);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
_listenToChanges(deviceId);
|
||||
deviceStatus = LivingRoomStatusModel.fromJson(event.deviceId, status.status);
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomControl(
|
||||
LivingRoomControl event, Emitter<LivingRoomState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(
|
||||
Status(code: element['code'], value: element['value']),
|
||||
);
|
||||
});
|
||||
|
||||
deviceStatus = LivingRoomStatusModel.fromJson(deviceId, statusList);
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (_) {
|
||||
log('Error listening to changes');
|
||||
}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<LivingRoomState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
Future<void> _livingRoomControl(
|
||||
LivingRoomControl event,
|
||||
Emitter<LivingRoomState> emit,
|
||||
) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required dynamic value,
|
||||
required dynamic oldValue,
|
||||
required Emitter<LivingRoomState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
_timer = Timer(const Duration(seconds: 1), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, dynamic oldValue,
|
||||
Emitter<LivingRoomState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
Future<void> _livingRoomBatchControl(
|
||||
LivingRoomBatchControl event,
|
||||
Emitter<LivingRoomState> emit,
|
||||
) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, dynamic value) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
break;
|
||||
case 'switch_2':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
}
|
||||
break;
|
||||
case 'switch_3':
|
||||
if (value is bool) {
|
||||
deviceStatus = deviceStatus.copyWith(switch3: value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
dynamic _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
case 'switch_2':
|
||||
return deviceStatus.switch2;
|
||||
case 'switch_3':
|
||||
return deviceStatus.switch3;
|
||||
default:
|
||||
return null;
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomFetchBatchControl(
|
||||
LivingRoomFetchBatchEvent event, Emitter<LivingRoomState> emit) async {
|
||||
Future<void> _livingRoomFetchBatchControl(
|
||||
LivingRoomFetchBatchEvent event,
|
||||
Emitter<LivingRoomState> emit,
|
||||
) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
LivingRoomStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
// for (var deviceId in event.devicesIds) {
|
||||
// _listenToChanges(deviceId);
|
||||
// }
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomBatchControl(
|
||||
LivingRoomBatchControl event, Emitter<LivingRoomState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.devicesIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
FutureOr<void> _livingRoomFactoryReset(
|
||||
LivingRoomFactoryResetEvent event, Emitter<LivingRoomState> emit) async {
|
||||
Future<void> _livingRoomFactoryReset(
|
||||
LivingRoomFactoryResetEvent event,
|
||||
Emitter<LivingRoomState> emit,
|
||||
) async {
|
||||
emit(LivingRoomDeviceStatusLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
@ -183,42 +142,28 @@ class LivingRoomBloc extends Bloc<LivingRoomEvent, LivingRoomState> {
|
||||
event.uuid,
|
||||
);
|
||||
if (!response) {
|
||||
emit(const LivingRoomDeviceManagementError('Failed'));
|
||||
emit(const LivingRoomDeviceManagementError('Failed to reset device'));
|
||||
} else {
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
add(LivingRoomFetchDeviceStatusEvent(event.uuid));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(LivingRoomDeviceManagementError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
void _updateLocalValue(String code, dynamic value) {
|
||||
if (value is! bool) return;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
LivingRoomStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(StatusUpdated event, Emitter<LivingRoomState> emit) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(LivingRoomDeviceStatusLoaded(deviceStatus));
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
break;
|
||||
case 'switch_2':
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
break;
|
||||
case 'switch_3':
|
||||
deviceStatus = deviceStatus.copyWith(switch3: value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||
|
||||
abstract final class LivingRoomBlocFactory {
|
||||
const LivingRoomBlocFactory._();
|
||||
|
||||
static LivingRoomBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return LivingRoomBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_re
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
@ -17,7 +18,7 @@ class LivingRoomBatchControlsView extends StatelessWidget with HelperResponsiveL
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) =>
|
||||
LivingRoomBloc(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
|
||||
LivingRoomBlocFactory.create(deviceId: deviceIds.first)..add(LivingRoomFetchBatchEvent(deviceIds)),
|
||||
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
||||
builder: (context, state) {
|
||||
if (state is LivingRoomDeviceStatusLoading) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/bloc/living_room_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/factories/living_room_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/three_gang_switch/models/living_room_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
@ -14,7 +15,7 @@ class LivingRoomDeviceControlsView extends StatelessWidget
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LivingRoomBloc(deviceId: deviceId)
|
||||
create: (context) => LivingRoomBlocFactory.create(deviceId: deviceId)
|
||||
..add(LivingRoomFetchDeviceStatusEvent(deviceId)),
|
||||
child: BlocBuilder<LivingRoomBloc, LivingRoomState>(
|
||||
builder: (context, state) {
|
||||
|
@ -1,26 +1,33 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
part 'two_gang_glass_switch_event.dart';
|
||||
part 'two_gang_glass_switch_state.dart';
|
||||
|
||||
class TwoGangGlassSwitchBloc
|
||||
extends Bloc<TwoGangGlassSwitchEvent, TwoGangGlassSwitchState> {
|
||||
TwoGangGlassStatusModel deviceStatus;
|
||||
Timer? _timer;
|
||||
TwoGangGlassSwitchBloc({required String deviceId})
|
||||
: deviceStatus = TwoGangGlassStatusModel(
|
||||
uuid: deviceId,
|
||||
switch1: false,
|
||||
countDown1: 0,
|
||||
switch2: false,
|
||||
countDown2: 0),
|
||||
super(TwoGangGlassSwitchInitial()) {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
late TwoGangGlassStatusModel deviceStatus;
|
||||
|
||||
TwoGangGlassSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(TwoGangGlassSwitchInitial()) {
|
||||
on<TwoGangGlassSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<TwoGangGlassSwitchControl>(_onControl);
|
||||
on<TwoGangGlassSwitchBatchControl>(_onBatchControl);
|
||||
@ -29,14 +36,14 @@ class TwoGangGlassSwitchBloc
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
Future<void> _onFetchDeviceStatus(TwoGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
TwoGangGlassSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus =
|
||||
TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(event.deviceId, status.status);
|
||||
_listenToChanges(event.deviceId);
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
@ -46,200 +53,121 @@ class TwoGangGlassSwitchBloc
|
||||
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
ref.onValue.listen((DatabaseEvent event) {
|
||||
if (event.snapshot.value == null) return;
|
||||
final ref = FirebaseDatabase.instance.ref(
|
||||
'device-status/$deviceId',
|
||||
);
|
||||
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
Map<dynamic, dynamic> data =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
List<Status> statusList = [];
|
||||
|
||||
data['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
// Parse the new status and add the event
|
||||
final updatedStatus =
|
||||
TwoGangGlassStatusModel.fromJson(data['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(updatedStatus));
|
||||
}
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(deviceId, statusList);
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (e) {
|
||||
// Handle errors and emit an error state if necessary
|
||||
if (!isClosed) {
|
||||
// add(TwoGangGlassSwitchError('Error listening to updates: $e'));
|
||||
}
|
||||
} catch (_) {
|
||||
log(
|
||||
'Error listening to changes',
|
||||
name: 'TwoGangGlassSwitchBloc._listenToChanges',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onControl(TwoGangGlassSwitchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onControl(
|
||||
TwoGangGlassSwitchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onBatchControl(TwoGangGlassSwitchBatchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
Future<void> _onBatchControl(
|
||||
TwoGangGlassSwitchBatchControl event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceIds,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(
|
||||
TwoGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
||||
TwoGangGlassSwitchFetchBatchStatusEvent event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.deviceIds);
|
||||
deviceStatus = TwoGangGlassStatusModel.fromJson(
|
||||
event.deviceIds.first, status.status);
|
||||
event.deviceIds.first,
|
||||
status.status,
|
||||
);
|
||||
emit(TwoGangGlassSwitchBatchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFactoryReset(TwoGangGlassFactoryReset event,
|
||||
Emitter<TwoGangGlassSwitchState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
TwoGangGlassFactoryReset event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangGlassSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi()
|
||||
.factoryReset(event.factoryReset, event.deviceId);
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
event.factoryReset,
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(TwoGangGlassSwitchError('Failed'));
|
||||
emit(TwoGangGlassSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
add(TwoGangGlassSwitchFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(TwoGangGlassSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<TwoGangGlassSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<TwoGangGlassSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<TwoGangGlassSwitchState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
} else if (code == 'switch_2') {
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
break;
|
||||
case 'switch_2':
|
||||
return deviceStatus.switch2;
|
||||
default:
|
||||
return false;
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
// _listenToChanges(deviceId) {
|
||||
// try {
|
||||
// DatabaseReference ref =
|
||||
// FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
// Stream<DatabaseEvent> stream = ref.onValue;
|
||||
|
||||
// stream.listen((DatabaseEvent event) {
|
||||
// Map<dynamic, dynamic> usersMap =
|
||||
// event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
// List<Status> statusList = [];
|
||||
// usersMap['status'].forEach((element) {
|
||||
// statusList
|
||||
// .add(Status(code: element['code'], value: element['value']));
|
||||
// });
|
||||
|
||||
// deviceStatus = TwoGangGlassStatusModel.fromJson(
|
||||
// usersMap['productUuid'], statusList);
|
||||
// if (!isClosed) {
|
||||
// add(StatusUpdated(deviceStatus));
|
||||
// }
|
||||
// });
|
||||
// } catch (_) {}
|
||||
// }
|
||||
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event, Emitter<TwoGangGlassSwitchState> emit) {
|
||||
// Update the local deviceStatus with the new status from the event
|
||||
deviceStatus = event.deviceStatus;
|
||||
// Emit the new state with the updated status
|
||||
emit(TwoGangGlassSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
part of 'two_gang_glass_switch_bloc.dart';
|
||||
|
||||
@immutable
|
||||
abstract class TwoGangGlassSwitchEvent {}
|
||||
abstract class TwoGangGlassSwitchEvent extends Equatable {
|
||||
const TwoGangGlassSwitchEvent();
|
||||
}
|
||||
|
||||
class TwoGangGlassSwitchFetchDeviceEvent extends TwoGangGlassSwitchEvent {
|
||||
final String deviceId;
|
||||
|
||||
TwoGangGlassSwitchFetchDeviceEvent(this.deviceId);
|
||||
const TwoGangGlassSwitchFetchDeviceEvent(this.deviceId);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId];
|
||||
}
|
||||
|
||||
class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
|
||||
@ -14,11 +19,14 @@ class TwoGangGlassSwitchControl extends TwoGangGlassSwitchEvent {
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
TwoGangGlassSwitchControl({
|
||||
const TwoGangGlassSwitchControl({
|
||||
required this.deviceId,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, code, value];
|
||||
}
|
||||
|
||||
class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
|
||||
@ -26,33 +34,43 @@ class TwoGangGlassSwitchBatchControl extends TwoGangGlassSwitchEvent {
|
||||
final String code;
|
||||
final bool value;
|
||||
|
||||
TwoGangGlassSwitchBatchControl({
|
||||
const TwoGangGlassSwitchBatchControl({
|
||||
required this.deviceIds,
|
||||
required this.code,
|
||||
required this.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds, code, value];
|
||||
}
|
||||
|
||||
class TwoGangGlassSwitchFetchBatchStatusEvent extends TwoGangGlassSwitchEvent {
|
||||
final List<String> deviceIds;
|
||||
|
||||
TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
||||
const TwoGangGlassSwitchFetchBatchStatusEvent(this.deviceIds);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceIds];
|
||||
}
|
||||
|
||||
class TwoGangGlassFactoryReset extends TwoGangGlassSwitchEvent {
|
||||
final String deviceId;
|
||||
final FactoryResetModel factoryReset;
|
||||
|
||||
TwoGangGlassFactoryReset({
|
||||
const TwoGangGlassFactoryReset({
|
||||
required this.deviceId,
|
||||
required this.factoryReset,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceId, factoryReset];
|
||||
}
|
||||
|
||||
class StatusUpdated extends TwoGangGlassSwitchEvent {
|
||||
final TwoGangGlassStatusModel deviceStatus;
|
||||
StatusUpdated(this.deviceStatus);
|
||||
|
||||
const StatusUpdated(this.deviceStatus);
|
||||
|
||||
@override
|
||||
List<Object> get props => [deviceStatus];
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
||||
|
||||
abstract final class TwoGangGlassSwitchBlocFactory {
|
||||
const TwoGangGlassSwitchBlocFactory._();
|
||||
|
||||
static TwoGangGlassSwitchBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return TwoGangGlassSwitchBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/factories/two_gang_glass_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -16,7 +17,7 @@ class TwoGangGlassSwitchBatchControlView extends StatelessWidget with HelperResp
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceIds.first)
|
||||
create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||
..add(TwoGangGlassSwitchFetchBatchStatusEvent(deviceIds)),
|
||||
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/bloc/two_gang_glass_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/factories/two_gang_glass_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_g_glass_switch/models/two_gang_glass_status_model.dart';
|
||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
@ -15,7 +16,7 @@ class TwoGangGlassSwitchControlView extends StatelessWidget
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => TwoGangGlassSwitchBloc(deviceId: deviceId)
|
||||
create: (context) => TwoGangGlassSwitchBlocFactory.create(deviceId: deviceId)
|
||||
..add(TwoGangGlassSwitchFetchDeviceEvent(deviceId)),
|
||||
child: BlocBuilder<TwoGangGlassSwitchBloc, TwoGangGlassSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:firebase_database/firebase_database.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -6,10 +7,22 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/device_sta
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||
import 'package:syncrow_web/services/batch_control_devices_service.dart';
|
||||
import 'package:syncrow_web/services/control_device_service.dart';
|
||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||
|
||||
class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
||||
TwoGangSwitchBloc({required this.deviceId}) : super(TwoGangSwitchInitial()) {
|
||||
final String deviceId;
|
||||
final ControlDeviceService controlDeviceService;
|
||||
final BatchControlDevicesService batchControlDevicesService;
|
||||
|
||||
late TwoGangStatusModel deviceStatus;
|
||||
|
||||
TwoGangSwitchBloc({
|
||||
required this.deviceId,
|
||||
required this.controlDeviceService,
|
||||
required this.batchControlDevicesService,
|
||||
}) : super(TwoGangSwitchInitial()) {
|
||||
on<TwoGangSwitchFetchDeviceEvent>(_onFetchDeviceStatus);
|
||||
on<TwoGangSwitchControl>(_onControl);
|
||||
on<TwoGangSwitchFetchBatchEvent>(_onFetchBatchStatus);
|
||||
@ -18,16 +31,13 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
||||
on<StatusUpdated>(_onStatusUpdated);
|
||||
}
|
||||
|
||||
late TwoGangStatusModel deviceStatus;
|
||||
final String deviceId;
|
||||
Timer? _timer;
|
||||
|
||||
FutureOr<void> _onFetchDeviceStatus(TwoGangSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangSwitchState> emit) async {
|
||||
Future<void> _onFetchDeviceStatus(
|
||||
TwoGangSwitchFetchDeviceEvent event,
|
||||
Emitter<TwoGangSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
|
||||
deviceStatus = TwoGangStatusModel.fromJson(event.deviceId, status.status);
|
||||
_listenToChanges(event.deviceId);
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
@ -36,131 +46,91 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onControl(
|
||||
TwoGangSwitchControl event, Emitter<TwoGangSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
void _listenToChanges(String deviceId) {
|
||||
try {
|
||||
final ref = FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
|
||||
ref.onValue.listen((event) {
|
||||
final eventsMap = event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
eventsMap['status'].forEach((element) {
|
||||
statusList.add(
|
||||
Status(code: element['code'], value: element['value']),
|
||||
);
|
||||
});
|
||||
|
||||
deviceStatus = TwoGangStatusModel.fromJson(deviceId, statusList);
|
||||
add(StatusUpdated(deviceStatus));
|
||||
});
|
||||
} catch (_) {
|
||||
log(
|
||||
'Error listening to changes',
|
||||
name: 'TwoGangSwitchBloc._listenToChanges',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onControl(
|
||||
TwoGangSwitchControl event,
|
||||
Emitter<TwoGangSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: false,
|
||||
);
|
||||
try {
|
||||
await controlDeviceService.controlDevice(
|
||||
deviceUuid: event.deviceId,
|
||||
status: Status(code: event.code, value: event.value),
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _runDebounce({
|
||||
required dynamic deviceId,
|
||||
required String code,
|
||||
required bool value,
|
||||
required bool oldValue,
|
||||
required Emitter<TwoGangSwitchState> emit,
|
||||
required bool isBatch,
|
||||
}) async {
|
||||
late String id;
|
||||
|
||||
if (deviceId is List) {
|
||||
id = deviceId.first;
|
||||
} else {
|
||||
id = deviceId;
|
||||
}
|
||||
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
}
|
||||
|
||||
_timer = Timer(const Duration(milliseconds: 500), () async {
|
||||
try {
|
||||
late bool response;
|
||||
if (isBatch) {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceBatchControl(deviceId, code, value);
|
||||
} else {
|
||||
response = await DevicesManagementApi()
|
||||
.deviceControl(deviceId, Status(code: code, value: value));
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
} catch (e) {
|
||||
_revertValueAndEmit(id, code, oldValue, emit);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _revertValueAndEmit(String deviceId, String code, bool oldValue,
|
||||
Emitter<TwoGangSwitchState> emit) {
|
||||
_updateLocalValue(code, oldValue);
|
||||
Future<void> _onBatchControl(
|
||||
TwoGangSwitchBatchControl event,
|
||||
Emitter<TwoGangSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
_updateLocalValue(event.code, event.value);
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
if (code == 'switch_1') {
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
}
|
||||
|
||||
if (code == 'switch_2') {
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
try {
|
||||
await batchControlDevicesService.batchControlDevices(
|
||||
uuids: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
);
|
||||
} catch (e) {
|
||||
_updateLocalValue(event.code, !event.value);
|
||||
emit(TwoGangSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
bool _getValueByCode(String code) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
return deviceStatus.switch1;
|
||||
case 'switch_2':
|
||||
return deviceStatus.switch2;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onFetchBatchStatus(TwoGangSwitchFetchBatchEvent event,
|
||||
Emitter<TwoGangSwitchState> emit) async {
|
||||
Future<void> _onFetchBatchStatus(
|
||||
TwoGangSwitchFetchBatchEvent event,
|
||||
Emitter<TwoGangSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
try {
|
||||
final status =
|
||||
await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus =
|
||||
TwoGangStatusModel.fromJson(event.devicesIds.first, status.status);
|
||||
final status = await DevicesManagementApi().getBatchStatus(event.devicesIds);
|
||||
deviceStatus = TwoGangStatusModel.fromJson(
|
||||
event.devicesIds.first,
|
||||
status.status,
|
||||
);
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
} catch (e) {
|
||||
emit(TwoGangSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_timer?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FutureOr<void> _onBatchControl(
|
||||
TwoGangSwitchBatchControl event, Emitter<TwoGangSwitchState> emit) async {
|
||||
final oldValue = _getValueByCode(event.code);
|
||||
|
||||
_updateLocalValue(event.code, event.value);
|
||||
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
|
||||
await _runDebounce(
|
||||
deviceId: event.deviceId,
|
||||
code: event.code,
|
||||
value: event.value,
|
||||
oldValue: oldValue,
|
||||
emit: emit,
|
||||
isBatch: true,
|
||||
);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFactoryReset(
|
||||
TwoGangFactoryReset event, Emitter<TwoGangSwitchState> emit) async {
|
||||
Future<void> _onFactoryReset(
|
||||
TwoGangFactoryReset event,
|
||||
Emitter<TwoGangSwitchState> emit,
|
||||
) async {
|
||||
emit(TwoGangSwitchLoading());
|
||||
try {
|
||||
final response = await DevicesManagementApi().factoryReset(
|
||||
@ -168,42 +138,31 @@ class TwoGangSwitchBloc extends Bloc<TwoGangSwitchEvent, TwoGangSwitchState> {
|
||||
event.deviceId,
|
||||
);
|
||||
if (!response) {
|
||||
emit(TwoGangSwitchError('Failed'));
|
||||
emit(TwoGangSwitchError('Failed to reset device'));
|
||||
} else {
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
add(TwoGangSwitchFetchDeviceEvent(event.deviceId));
|
||||
}
|
||||
} catch (e) {
|
||||
emit(TwoGangSwitchError(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
_listenToChanges(deviceId) {
|
||||
try {
|
||||
DatabaseReference ref =
|
||||
FirebaseDatabase.instance.ref('device-status/$deviceId');
|
||||
Stream<DatabaseEvent> stream = ref.onValue;
|
||||
|
||||
stream.listen((DatabaseEvent event) {
|
||||
Map<dynamic, dynamic> usersMap =
|
||||
event.snapshot.value as Map<dynamic, dynamic>;
|
||||
|
||||
List<Status> statusList = [];
|
||||
usersMap['status'].forEach((element) {
|
||||
statusList
|
||||
.add(Status(code: element['code'], value: element['value']));
|
||||
});
|
||||
|
||||
deviceStatus =
|
||||
TwoGangStatusModel.fromJson(usersMap['productUuid'], statusList);
|
||||
if (!isClosed) {
|
||||
add(StatusUpdated(deviceStatus));
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void _onStatusUpdated(StatusUpdated event, Emitter<TwoGangSwitchState> emit) {
|
||||
void _onStatusUpdated(
|
||||
StatusUpdated event,
|
||||
Emitter<TwoGangSwitchState> emit,
|
||||
) {
|
||||
deviceStatus = event.deviceStatus;
|
||||
emit(TwoGangSwitchStatusLoaded(deviceStatus));
|
||||
}
|
||||
|
||||
void _updateLocalValue(String code, bool value) {
|
||||
switch (code) {
|
||||
case 'switch_1':
|
||||
deviceStatus = deviceStatus.copyWith(switch1: value);
|
||||
break;
|
||||
case 'switch_2':
|
||||
deviceStatus = deviceStatus.copyWith(switch2: value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user