mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-10 15:17:31 +00:00
Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration
This commit is contained in:
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
import 'package:syncrow_web/pages/device_managment/factories/device_bloc_dependencies_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||
|
||||
abstract final class TwoGangSwitchBlocFactory {
|
||||
const TwoGangSwitchBlocFactory._();
|
||||
|
||||
static TwoGangSwitchBloc create({
|
||||
required String deviceId,
|
||||
}) {
|
||||
return TwoGangSwitchBloc(
|
||||
deviceId: deviceId,
|
||||
controlDeviceService:
|
||||
DeviceBlocDependenciesFactory.createControlDeviceService(),
|
||||
batchControlDevicesService:
|
||||
DeviceBlocDependenciesFactory.createBatchControlDevicesService(),
|
||||
);
|
||||
}
|
||||
}
|
@ -24,16 +24,16 @@ class TwoGangStatusModel {
|
||||
for (var status in jsonList) {
|
||||
switch (status.code) {
|
||||
case 'switch_1':
|
||||
switch1 = status.value ?? false;
|
||||
switch1 = bool.tryParse(status.value.toString()) ?? false;
|
||||
break;
|
||||
case 'countdown_1':
|
||||
countDown = status.value ?? 0;
|
||||
countDown = int.tryParse(status.value.toString()) ?? 0;
|
||||
break;
|
||||
case 'switch_2':
|
||||
switch2 = status.value ?? false;
|
||||
switch2 = bool.tryParse(status.value.toString()) ?? false;
|
||||
break;
|
||||
case 'countdown_2':
|
||||
countDown2 = status.value ?? 0;
|
||||
countDown2 = int.tryParse(status.value.toString()) ?? 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart';
|
||||
// import 'package:syncrow_web/pages/device_managment/shared/batch_control/firmware_update.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/shared/toggle_widget.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_bloc.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_event.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/bloc/two_gang_switch_state.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/factories/two_gang_switch_bloc_factory.dart';
|
||||
import 'package:syncrow_web/pages/device_managment/two_gang_switch/models/two_gang_status_model.dart';
|
||||
import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart';
|
||||
|
||||
@ -18,7 +18,7 @@ class TwoGangBatchControlView extends StatelessWidget with HelperResponsiveLayou
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => TwoGangSwitchBloc(deviceId: deviceIds.first)
|
||||
create: (context) => TwoGangSwitchBlocFactory.create(deviceId: deviceIds.first)
|
||||
..add(TwoGangSwitchFetchBatchEvent(deviceIds)),
|
||||
child: BlocBuilder<TwoGangSwitchBloc, TwoGangSwitchState>(
|
||||
builder: (context, state) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user