mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 09:45:25 +00:00
Compare commits
23 Commits
SP-1175-FE
...
SP-1593-FE
Author | SHA1 | Date | |
---|---|---|---|
8e11749ed7 | |||
7bc9079212 | |||
97801872e0 | |||
fa9210f387 | |||
57b6f01177 | |||
066f967cd1 | |||
e28f3c3c03 | |||
2be15e648a | |||
2e12d73151 | |||
c50ed693ae | |||
8dc7d2b3d0 | |||
accafb150e | |||
736e0c3d9c | |||
455d9c1f01 | |||
4479ed04b7 | |||
286dea3f51 | |||
44c4648941 | |||
ca1feb9600 | |||
7b31914e1c | |||
10f35d3747 | |||
1998a629b6 | |||
5940e52826 | |||
7c55e8bbf9 |
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -16,7 +16,6 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_dev.dart",
|
"lib/main_dev.dart",
|
||||||
"--web-experimental-hot-reload",
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
@ -36,7 +35,6 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_staging.dart",
|
"lib/main_staging.dart",
|
||||||
"--web-experimental-hot-reload",
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
@ -56,7 +54,6 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main.dart",
|
"lib/main.dart",
|
||||||
"--web-experimental-hot-reload",
|
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
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];
|
||||||
|
}
|
@ -1,18 +1,49 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
|
||||||
class RangeOfAqi extends Equatable {
|
class RangeOfAqi extends Equatable {
|
||||||
final double min;
|
|
||||||
final double avg;
|
|
||||||
final double max;
|
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
|
final List<RangeOfAqiValue> data;
|
||||||
|
|
||||||
const RangeOfAqi({
|
const RangeOfAqi({
|
||||||
required this.min,
|
required this.data,
|
||||||
required this.avg,
|
|
||||||
required this.max,
|
|
||||||
required this.date,
|
required this.date,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
factory RangeOfAqi.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RangeOfAqi(
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
data: (json['data'] as List<dynamic>)
|
||||||
|
.map((e) => RangeOfAqiValue.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [min, avg, max, date];
|
List<Object?> get props => [data, date];
|
||||||
|
}
|
||||||
|
|
||||||
|
class RangeOfAqiValue extends Equatable {
|
||||||
|
final String type;
|
||||||
|
final double min;
|
||||||
|
final double average;
|
||||||
|
final double max;
|
||||||
|
|
||||||
|
const RangeOfAqiValue({
|
||||||
|
required this.type,
|
||||||
|
required this.min,
|
||||||
|
required this.average,
|
||||||
|
required this.max,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory RangeOfAqiValue.fromJson(Map<String, dynamic> json) {
|
||||||
|
return RangeOfAqiValue(
|
||||||
|
type: json['type'] as String,
|
||||||
|
min: (json['min'] as num).toDouble(),
|
||||||
|
average: (json['average'] as num).toDouble(),
|
||||||
|
max: (json['max'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [type, min, average, max];
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
|
||||||
|
part 'air_quality_distribution_event.dart';
|
||||||
|
part 'air_quality_distribution_state.dart';
|
||||||
|
|
||||||
|
class AirQualityDistributionBloc
|
||||||
|
extends Bloc<AirQualityDistributionEvent, AirQualityDistributionState> {
|
||||||
|
final AirQualityDistributionService _aqiDistributionService;
|
||||||
|
|
||||||
|
AirQualityDistributionBloc(
|
||||||
|
this._aqiDistributionService,
|
||||||
|
) : super(const AirQualityDistributionState()) {
|
||||||
|
on<LoadAirQualityDistribution>(_onLoadAirQualityDistribution);
|
||||||
|
on<ClearAirQualityDistribution>(_onClearAirQualityDistribution);
|
||||||
|
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLoadAirQualityDistribution(
|
||||||
|
LoadAirQualityDistribution event,
|
||||||
|
Emitter<AirQualityDistributionState> emit,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
emit(state.copyWith(status: AirQualityDistributionStatus.loading));
|
||||||
|
final result = await _aqiDistributionService.getAirQualityDistribution(
|
||||||
|
event.param,
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: AirQualityDistributionStatus.success,
|
||||||
|
chartData: result,
|
||||||
|
filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
emit(
|
||||||
|
AirQualityDistributionState(
|
||||||
|
status: AirQualityDistributionStatus.failure,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
selectedAqiType: state.selectedAqiType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onClearAirQualityDistribution(
|
||||||
|
ClearAirQualityDistribution event,
|
||||||
|
Emitter<AirQualityDistributionState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const AirQualityDistributionState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onUpdateAqiTypeEvent(
|
||||||
|
UpdateAqiTypeEvent event,
|
||||||
|
Emitter<AirQualityDistributionState> emit,
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedAqiType: event.aqiType,
|
||||||
|
filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AirQualityDataModel> _arrangeChartDataByType(
|
||||||
|
List<AirQualityDataModel> data,
|
||||||
|
AqiType aqiType,
|
||||||
|
) {
|
||||||
|
final filteredData = data.map(
|
||||||
|
(data) => AirQualityDataModel(
|
||||||
|
date: data.date,
|
||||||
|
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return filteredData.toList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
part of 'air_quality_distribution_bloc.dart';
|
||||||
|
|
||||||
|
sealed class AirQualityDistributionEvent extends Equatable {
|
||||||
|
const AirQualityDistributionEvent();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class LoadAirQualityDistribution extends AirQualityDistributionEvent {
|
||||||
|
final GetAirQualityDistributionParam param;
|
||||||
|
|
||||||
|
const LoadAirQualityDistribution(this.param);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [param];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UpdateAqiTypeEvent extends AirQualityDistributionEvent {
|
||||||
|
const UpdateAqiTypeEvent(this.aqiType);
|
||||||
|
|
||||||
|
final AqiType aqiType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [aqiType];
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ClearAirQualityDistribution extends AirQualityDistributionEvent {
|
||||||
|
const ClearAirQualityDistribution();
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
part of 'air_quality_distribution_bloc.dart';
|
||||||
|
|
||||||
|
enum AirQualityDistributionStatus {
|
||||||
|
initial,
|
||||||
|
loading,
|
||||||
|
success,
|
||||||
|
failure,
|
||||||
|
}
|
||||||
|
|
||||||
|
class AirQualityDistributionState extends Equatable {
|
||||||
|
const AirQualityDistributionState({
|
||||||
|
this.status = AirQualityDistributionStatus.initial,
|
||||||
|
this.chartData = const [],
|
||||||
|
this.filteredChartData = const [],
|
||||||
|
this.errorMessage,
|
||||||
|
this.selectedAqiType = AqiType.aqi,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AirQualityDistributionStatus status;
|
||||||
|
final List<AirQualityDataModel> chartData;
|
||||||
|
final List<AirQualityDataModel> filteredChartData;
|
||||||
|
final String? errorMessage;
|
||||||
|
final AqiType selectedAqiType;
|
||||||
|
|
||||||
|
AirQualityDistributionState copyWith({
|
||||||
|
AirQualityDistributionStatus? status,
|
||||||
|
List<AirQualityDataModel>? chartData,
|
||||||
|
List<AirQualityDataModel>? filteredChartData,
|
||||||
|
String? errorMessage,
|
||||||
|
AqiType? selectedAqiType,
|
||||||
|
}) {
|
||||||
|
return AirQualityDistributionState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
chartData: chartData ?? this.chartData,
|
||||||
|
filteredChartData: filteredChartData ?? this.filteredChartData,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [status, chartData, errorMessage, selectedAqiType];
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
|||||||
RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) {
|
RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) {
|
||||||
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
|
on<LoadRangeOfAqiEvent>(_onLoadRangeOfAqiEvent);
|
||||||
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
|
on<ClearRangeOfAqiEvent>(_onClearRangeOfAqiEvent);
|
||||||
|
on<UpdateAqiTypeEvent>(_onUpdateAqiTypeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
final RangeOfAqiService _rangeOfAqiService;
|
final RangeOfAqiService _rangeOfAqiService;
|
||||||
@ -20,19 +22,55 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
|||||||
Emitter<RangeOfAqiState> emit,
|
Emitter<RangeOfAqiState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(
|
emit(
|
||||||
RangeOfAqiState(
|
state.copyWith(status: RangeOfAqiStatus.loading),
|
||||||
status: RangeOfAqiStatus.loading,
|
|
||||||
rangeOfAqi: state.rangeOfAqi,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
||||||
emit(RangeOfAqiState(status: RangeOfAqiStatus.loaded, rangeOfAqi: rangeOfAqi));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RangeOfAqiStatus.loaded,
|
||||||
|
rangeOfAqi: rangeOfAqi,
|
||||||
|
filteredRangeOfAqi: _arrangeChartDataByType(
|
||||||
|
rangeOfAqi,
|
||||||
|
state.selectedAqiType,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(RangeOfAqiState(status: RangeOfAqiStatus.failure, errorMessage: '$e'));
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
status: RangeOfAqiStatus.failure,
|
||||||
|
errorMessage: '$e',
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onUpdateAqiTypeEvent(
|
||||||
|
UpdateAqiTypeEvent event,
|
||||||
|
Emitter<RangeOfAqiState> emit,
|
||||||
|
) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedAqiType: event.aqiType,
|
||||||
|
filteredRangeOfAqi: _arrangeChartDataByType(state.rangeOfAqi, event.aqiType),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RangeOfAqi> _arrangeChartDataByType(
|
||||||
|
List<RangeOfAqi> rangeOfAqi,
|
||||||
|
AqiType aqiType,
|
||||||
|
) {
|
||||||
|
final filteredRangeOfAqi = rangeOfAqi.map(
|
||||||
|
(data) => RangeOfAqi(
|
||||||
|
date: data.date,
|
||||||
|
data: data.data.where((value) => value.type == aqiType.code).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return filteredRangeOfAqi.toList();
|
||||||
|
}
|
||||||
|
|
||||||
void _onClearRangeOfAqiEvent(
|
void _onClearRangeOfAqiEvent(
|
||||||
ClearRangeOfAqiEvent event,
|
ClearRangeOfAqiEvent event,
|
||||||
Emitter<RangeOfAqiState> emit,
|
Emitter<RangeOfAqiState> emit,
|
||||||
|
@ -16,6 +16,15 @@ class LoadRangeOfAqiEvent extends RangeOfAqiEvent {
|
|||||||
List<Object> get props => [param];
|
List<Object> get props => [param];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpdateAqiTypeEvent extends RangeOfAqiEvent {
|
||||||
|
const UpdateAqiTypeEvent(this.aqiType);
|
||||||
|
|
||||||
|
final AqiType aqiType;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props => [aqiType];
|
||||||
|
}
|
||||||
|
|
||||||
class ClearRangeOfAqiEvent extends RangeOfAqiEvent {
|
class ClearRangeOfAqiEvent extends RangeOfAqiEvent {
|
||||||
const ClearRangeOfAqiEvent();
|
const ClearRangeOfAqiEvent();
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,35 @@ enum RangeOfAqiStatus { initial, loading, loaded, failure }
|
|||||||
final class RangeOfAqiState extends Equatable {
|
final class RangeOfAqiState extends Equatable {
|
||||||
const RangeOfAqiState({
|
const RangeOfAqiState({
|
||||||
this.rangeOfAqi = const [],
|
this.rangeOfAqi = const [],
|
||||||
|
this.filteredRangeOfAqi = const [],
|
||||||
this.status = RangeOfAqiStatus.initial,
|
this.status = RangeOfAqiStatus.initial,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
|
this.selectedAqiType = AqiType.aqi,
|
||||||
});
|
});
|
||||||
|
|
||||||
final RangeOfAqiStatus status;
|
final RangeOfAqiStatus status;
|
||||||
final List<RangeOfAqi> rangeOfAqi;
|
final List<RangeOfAqi> rangeOfAqi;
|
||||||
|
final List<RangeOfAqi> filteredRangeOfAqi;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
final AqiType selectedAqiType;
|
||||||
|
|
||||||
|
RangeOfAqiState copyWith({
|
||||||
|
RangeOfAqiStatus? status,
|
||||||
|
List<RangeOfAqi>? rangeOfAqi,
|
||||||
|
List<RangeOfAqi>? filteredRangeOfAqi,
|
||||||
|
String? errorMessage,
|
||||||
|
AqiType? selectedAqiType,
|
||||||
|
}) {
|
||||||
|
return RangeOfAqiState(
|
||||||
|
status: status ?? this.status,
|
||||||
|
rangeOfAqi: rangeOfAqi ?? this.rangeOfAqi,
|
||||||
|
filteredRangeOfAqi: filteredRangeOfAqi ?? this.filteredRangeOfAqi,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
selectedAqiType: selectedAqiType ?? this.selectedAqiType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [status, rangeOfAqi, errorMessage];
|
List<Object?> get props =>
|
||||||
|
[status, rangeOfAqi, filteredRangeOfAqi, errorMessage, selectedAqiType];
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
|
|
||||||
@ -13,8 +14,10 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
|
|
||||||
static void loadAirQualityData(
|
static void loadAirQualityData(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
required DateTime date,
|
||||||
required String communityUuid,
|
required String communityUuid,
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
|
bool shouldFetchAnalyticsDevices = true,
|
||||||
}) {
|
}) {
|
||||||
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||||
loadAnalyticsDevices(
|
loadAnalyticsDevices(
|
||||||
@ -26,7 +29,11 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
context,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
date: date,
|
date: date,
|
||||||
aqiType: AqiType.aqi,
|
);
|
||||||
|
loadAirQualityDistribution(
|
||||||
|
context,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
date: date,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +44,9 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
context.read<RealtimeDeviceChangesBloc>().add(
|
context.read<RealtimeDeviceChangesBloc>().add(
|
||||||
const RealtimeDeviceChangesClosed(),
|
const RealtimeDeviceChangesClosed(),
|
||||||
);
|
);
|
||||||
|
context.read<AirQualityDistributionBloc>().add(
|
||||||
|
const ClearAirQualityDistribution(),
|
||||||
|
);
|
||||||
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
|
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,16 +76,26 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required String spaceUuid,
|
required String spaceUuid,
|
||||||
required DateTime date,
|
required DateTime date,
|
||||||
required AqiType aqiType,
|
|
||||||
}) {
|
}) {
|
||||||
context.read<RangeOfAqiBloc>().add(
|
context.read<RangeOfAqiBloc>().add(
|
||||||
LoadRangeOfAqiEvent(
|
LoadRangeOfAqiEvent(
|
||||||
GetRangeOfAqiParam(
|
GetRangeOfAqiParam(
|
||||||
date: date,
|
date: date,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
aqiType: aqiType,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void loadAirQualityDistribution(
|
||||||
|
BuildContext context, {
|
||||||
|
required String spaceUuid,
|
||||||
|
required DateTime date,
|
||||||
|
}) {
|
||||||
|
context.read<AirQualityDistributionBloc>().add(
|
||||||
|
LoadAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart';
|
||||||
|
|
||||||
class AirQualityView extends StatelessWidget {
|
class AirQualityView extends StatelessWidget {
|
||||||
@ -23,8 +24,14 @@ class AirQualityView extends StatelessWidget {
|
|||||||
height: height * 1.2,
|
height: height * 1.2,
|
||||||
child: const AirQualityEndSideWidget(),
|
child: const AirQualityEndSideWidget(),
|
||||||
),
|
),
|
||||||
SizedBox(height: height * 0.5, child: const RangeOfAqiChartBox()),
|
SizedBox(
|
||||||
SizedBox(height: height * 0.5, child: const Placeholder()),
|
height: height * 0.5,
|
||||||
|
child: const RangeOfAqiChartBox(),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: height * 0.5,
|
||||||
|
child: const AqiDistributionChartBox(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -46,7 +53,7 @@ class AirQualityView extends StatelessWidget {
|
|||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: RangeOfAqiChartBox()),
|
Expanded(child: RangeOfAqiChartBox()),
|
||||||
Expanded(child: Placeholder()),
|
Expanded(child: AqiDistributionChartBox()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/energy_management_charts_helper.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
|
class AqiDistributionChart extends StatelessWidget {
|
||||||
|
const AqiDistributionChart({super.key, required this.chartData});
|
||||||
|
final List<AirQualityDataModel> chartData;
|
||||||
|
|
||||||
|
static const _rodStackItemsSpacing = 0.4;
|
||||||
|
static const _barWidth = 13.0;
|
||||||
|
static final _barBorderRadius = BorderRadius.circular(22);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final sortedData = List<AirQualityDataModel>.from(chartData)
|
||||||
|
..sort(
|
||||||
|
(a, b) => a.date.compareTo(b.date),
|
||||||
|
);
|
||||||
|
|
||||||
|
return BarChart(
|
||||||
|
BarChartData(
|
||||||
|
maxY: 100.1,
|
||||||
|
gridData: EnergyManagementChartsHelper.gridData(
|
||||||
|
horizontalInterval: 20,
|
||||||
|
),
|
||||||
|
borderData: EnergyManagementChartsHelper.borderData(),
|
||||||
|
barTouchData: _barTouchData(context),
|
||||||
|
titlesData: _titlesData(context),
|
||||||
|
barGroups: _buildBarGroups(sortedData),
|
||||||
|
),
|
||||||
|
duration: Duration.zero,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BarChartGroupData> _buildBarGroups(List<AirQualityDataModel> sortedData) {
|
||||||
|
return List.generate(sortedData.length, (index) {
|
||||||
|
final data = sortedData[index];
|
||||||
|
final stackItems = <BarChartRodData>[];
|
||||||
|
double currentY = 0;
|
||||||
|
bool isFirstElement = true;
|
||||||
|
|
||||||
|
// Sort data by type to ensure consistent order
|
||||||
|
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||||
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
|
|
||||||
|
for (final percentageData in sortedPercentageData) {
|
||||||
|
stackItems.add(
|
||||||
|
BarChartRodData(
|
||||||
|
fromY: currentY,
|
||||||
|
toY: currentY + percentageData.percentage ,
|
||||||
|
color: AirQualityDataModel.metricColors[percentageData.name]!,
|
||||||
|
borderRadius: isFirstElement
|
||||||
|
? const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(22),
|
||||||
|
topRight: Radius.circular(22),
|
||||||
|
)
|
||||||
|
: _barBorderRadius,
|
||||||
|
width: _barWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
currentY += percentageData.percentage + _rodStackItemsSpacing;
|
||||||
|
isFirstElement = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BarChartGroupData(
|
||||||
|
x: index,
|
||||||
|
barRods: stackItems,
|
||||||
|
groupVertically: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
BarTouchData _barTouchData(BuildContext context) {
|
||||||
|
return BarTouchData(
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
getTooltipColor: (_) => ColorsManager.whiteColors,
|
||||||
|
tooltipBorder: const BorderSide(
|
||||||
|
color: ColorsManager.semiTransparentBlack,
|
||||||
|
),
|
||||||
|
tooltipRoundedRadius: 16,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||||
|
final data = chartData[group.x.toInt()];
|
||||||
|
|
||||||
|
final List<TextSpan> children = [];
|
||||||
|
|
||||||
|
final textStyle = context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 12,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sort data by type to ensure consistent order
|
||||||
|
final sortedPercentageData = List<AirQualityPercentageData>.from(data.data)
|
||||||
|
..sort((a, b) => a.type.compareTo(b.type));
|
||||||
|
|
||||||
|
for (final percentageData in sortedPercentageData) {
|
||||||
|
children.add(TextSpan(
|
||||||
|
text:
|
||||||
|
'\n${percentageData.type.toUpperCase()}: ${percentageData.percentage.toStringAsFixed(1)}%',
|
||||||
|
style: textStyle,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return BarTooltipItem(
|
||||||
|
DateFormat('dd/MM/yyyy').format(data.date),
|
||||||
|
context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.blackColor,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
children: children,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlTitlesData _titlesData(BuildContext context) {
|
||||||
|
final titlesData = EnergyManagementChartsHelper.titlesData(
|
||||||
|
context,
|
||||||
|
leftTitlesInterval: 20,
|
||||||
|
);
|
||||||
|
|
||||||
|
final leftTitles = titlesData.leftTitles.copyWith(
|
||||||
|
sideTitles: titlesData.leftTitles.sideTitles.copyWith(
|
||||||
|
reservedSize: 70,
|
||||||
|
interval: 20,
|
||||||
|
maxIncluded: false,
|
||||||
|
minIncluded: true,
|
||||||
|
getTitlesWidget: (value, meta) => Padding(
|
||||||
|
padding: const EdgeInsetsDirectional.only(end: 12),
|
||||||
|
child: FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
'${value.toStringAsFixed(0)}%',
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
fontSize: 12,
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final bottomTitles = AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
getTitlesWidget: (value, _) => FittedBox(
|
||||||
|
alignment: AlignmentDirectional.bottomCenter,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Text(
|
||||||
|
chartData[value.toInt()].date.day.toString(),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
|
color: ColorsManager.lightGreyColor,
|
||||||
|
fontSize: 8,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
reservedSize: 36,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return titlesData.copyWith(
|
||||||
|
leftTitles: leftTitles,
|
||||||
|
bottomTitles: bottomTitles,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart';
|
||||||
|
import 'package:syncrow_web/utils/style.dart';
|
||||||
|
|
||||||
|
class AqiDistributionChartBox extends StatelessWidget {
|
||||||
|
const AqiDistributionChartBox({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<AirQualityDistributionBloc, AirQualityDistributionState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsetsDirectional.all(30),
|
||||||
|
decoration: subSectionContainerDecoration.copyWith(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (state.errorMessage != null) ...[
|
||||||
|
AnalyticsErrorWidget(state.errorMessage),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
AqiDistributionChartTitle(
|
||||||
|
isLoading: state.status == AirQualityDistributionStatus.loading,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Expanded(
|
||||||
|
child: AqiDistributionChart(chartData: state.filteredChartData),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
|
|
||||||
|
class AqiDistributionChartTitle extends StatelessWidget {
|
||||||
|
const AqiDistributionChartTitle({required this.isLoading, super.key});
|
||||||
|
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
ChartsLoadingWidget(isLoading: isLoading),
|
||||||
|
const Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: AlignmentDirectional.centerStart,
|
||||||
|
child: ChartTitle(
|
||||||
|
title: Text('Distribution over Air Quality Index'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
FittedBox(
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: AqiTypeDropdown(
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
context
|
||||||
|
.read<AirQualityDistributionBloc>()
|
||||||
|
.add(UpdateAqiTypeEvent(value));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,17 +3,18 @@ import 'package:syncrow_web/utils/color_manager.dart';
|
|||||||
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
|
|
||||||
enum AqiType {
|
enum AqiType {
|
||||||
aqi('AQI', ''),
|
aqi('AQI', '', 'aqi'),
|
||||||
pm25('PM2.5', 'µg/m³'),
|
pm25('PM2.5', 'µg/m³', 'pm25'),
|
||||||
pm10('PM10', 'µg/m³'),
|
pm10('PM10', 'µg/m³', 'pm10'),
|
||||||
hcho('HCHO', 'mg/m³'),
|
hcho('HCHO', 'mg/m³', 'hcho'),
|
||||||
tvoc('TVOC', 'µg/m³'),
|
tvoc('TVOC', 'µg/m³', 'tvoc'),
|
||||||
co2('CO2', 'ppm');
|
co2('CO2', 'ppm', 'co2');
|
||||||
|
|
||||||
const AqiType(this.value, this.unit);
|
const AqiType(this.value, this.unit, this.code);
|
||||||
|
|
||||||
final String value;
|
final String value;
|
||||||
final String unit;
|
final String unit;
|
||||||
|
final String code;
|
||||||
}
|
}
|
||||||
|
|
||||||
class AqiTypeDropdown extends StatefulWidget {
|
class AqiTypeDropdown extends StatefulWidget {
|
||||||
|
@ -13,23 +13,37 @@ class RangeOfAqiChart extends StatelessWidget {
|
|||||||
required this.chartData,
|
required this.chartData,
|
||||||
});
|
});
|
||||||
|
|
||||||
List<(List<double> values, Color color, Color? dotColor)> get _lines => [
|
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
||||||
(
|
final sortedData = List<RangeOfAqi>.from(chartData)
|
||||||
chartData.map((e) => e.max).toList(),
|
..sort((a, b) => a.date.compareTo(b.date));
|
||||||
ColorsManager.maxPurple,
|
|
||||||
ColorsManager.maxPurpleDot,
|
return [
|
||||||
),
|
(
|
||||||
(
|
sortedData.map((e) {
|
||||||
chartData.map((e) => e.avg).toList(),
|
final value = e.data.firstOrNull;
|
||||||
Colors.white,
|
return value?.max ?? 0;
|
||||||
null,
|
}).toList(),
|
||||||
),
|
ColorsManager.maxPurple,
|
||||||
(
|
ColorsManager.maxPurpleDot,
|
||||||
chartData.map((e) => e.min).toList(),
|
),
|
||||||
ColorsManager.minBlue,
|
(
|
||||||
ColorsManager.minBlueDot,
|
sortedData.map((e) {
|
||||||
),
|
final value = e.data.firstOrNull;
|
||||||
];
|
return value?.average ?? 0;
|
||||||
|
}).toList(),
|
||||||
|
Colors.white,
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
sortedData.map((e) {
|
||||||
|
final value = e.data.firstOrNull;
|
||||||
|
return value?.min ?? 0;
|
||||||
|
}).toList(),
|
||||||
|
ColorsManager.minBlue,
|
||||||
|
ColorsManager.minBlueDot,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -32,7 +32,7 @@ class RangeOfAqiChartBox extends StatelessWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Expanded(child: RangeOfAqiChart(chartData: state.rangeOfAqi)),
|
Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
|
|
||||||
class RangeOfAqiChartTitle extends StatelessWidget {
|
class RangeOfAqiChartTitle extends StatelessWidget {
|
||||||
const RangeOfAqiChartTitle({required this.isLoading, super.key});
|
const RangeOfAqiChartTitle({
|
||||||
|
required this.isLoading,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
|
|
||||||
static const List<(Color color, String title, bool hasBorder)> _colors = [
|
static const List<(Color color, String title, bool hasBorder)> _colors = [
|
||||||
@ -66,12 +69,9 @@ class RangeOfAqiChartTitle extends StatelessWidget {
|
|||||||
|
|
||||||
if (spaceUuid == null) return;
|
if (spaceUuid == null) return;
|
||||||
|
|
||||||
FetchAirQualityDataHelper.loadRangeOfAqi(
|
if (value != null) {
|
||||||
context,
|
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
|
||||||
spaceUuid: spaceUuid,
|
}
|
||||||
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
|
||||||
aqiType: value ?? AqiType.aqi,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
@ -26,10 +27,10 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||||
|
|
||||||
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
if (isSpaceSelected) {
|
||||||
if (hasSelectedSpaces) clearData(context);
|
clearData(context);
|
||||||
|
return;
|
||||||
if (isSpaceSelected) return;
|
}
|
||||||
|
|
||||||
spaceTreeBloc
|
spaceTreeBloc
|
||||||
..add(const SpaceTreeClearSelectionEvent())
|
..add(const SpaceTreeClearSelectionEvent())
|
||||||
@ -39,14 +40,22 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
context,
|
context,
|
||||||
communityUuid: community.uuid,
|
communityUuid: community.uuid,
|
||||||
spaceUuid: space.uuid ?? '',
|
spaceUuid: space.uuid ?? '',
|
||||||
|
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChildSpaceSelected(
|
||||||
|
BuildContext context,
|
||||||
|
CommunityModel community,
|
||||||
|
SpaceModel child,
|
||||||
|
) {
|
||||||
|
return onSpaceSelected(context, community, child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearData(BuildContext context) {
|
void clearData(BuildContext context) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||||
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
|
||||||
);
|
|
||||||
FetchAirQualityDataHelper.clearAllData(context);
|
FetchAirQualityDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,5 +13,10 @@ abstract class AnalyticsDataLoadingStrategy {
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel space,
|
SpaceModel space,
|
||||||
);
|
);
|
||||||
|
void onChildSpaceSelected(
|
||||||
|
BuildContext context,
|
||||||
|
CommunityModel community,
|
||||||
|
SpaceModel child,
|
||||||
|
);
|
||||||
void clearData(BuildContext context);
|
void clearData(BuildContext context);
|
||||||
}
|
}
|
||||||
|
@ -14,14 +14,24 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
List<SpaceModel> spaces,
|
List<SpaceModel> spaces,
|
||||||
) {
|
) {
|
||||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
context.read<SpaceTreeBloc>().add(
|
||||||
final isCommunitySelected =
|
OnCommunitySelected(
|
||||||
spaceTreeBloc.state.selectedCommunities.contains(community.uuid);
|
community.uuid,
|
||||||
|
spaces,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (isCommunitySelected) {
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
|
||||||
clearData(context);
|
clearData(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
communityId: community.uuid,
|
||||||
|
spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -30,31 +40,21 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel space,
|
SpaceModel space,
|
||||||
) {
|
) {
|
||||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
context.read<SpaceTreeBloc>().add(
|
||||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
OnSpaceSelected(
|
||||||
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
community,
|
||||||
|
space.uuid ?? '',
|
||||||
|
space.children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
if (isSpaceSelected) {
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
final firstSelectedSpace = spaceTreeBloc.state.selectedSpaces.first;
|
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
||||||
final isTheFirstSelectedSpace = firstSelectedSpace == space.uuid;
|
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
||||||
if (isTheFirstSelectedSpace) {
|
clearData(context);
|
||||||
clearData(context);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSelectedSpaces) {
|
|
||||||
clearData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
spaceTreeBloc.add(
|
|
||||||
OnSpaceSelected(
|
|
||||||
community,
|
|
||||||
space.uuid ?? '',
|
|
||||||
space.children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
context,
|
context,
|
||||||
communityId: community.uuid,
|
communityId: community.uuid,
|
||||||
@ -62,11 +62,18 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChildSpaceSelected(
|
||||||
|
BuildContext context,
|
||||||
|
CommunityModel community,
|
||||||
|
SpaceModel child,
|
||||||
|
) {
|
||||||
|
return onSpaceSelected(context, community, child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearData(BuildContext context) {
|
void clearData(BuildContext context) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||||
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
|
||||||
);
|
|
||||||
FetchEnergyManagementDataHelper.clearAllData(context);
|
FetchEnergyManagementDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,10 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||||
|
|
||||||
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
if (isSpaceSelected) {
|
||||||
if (hasSelectedSpaces) clearData(context);
|
clearData(context);
|
||||||
|
return;
|
||||||
if (isSpaceSelected) return;
|
}
|
||||||
|
|
||||||
spaceTreeBloc
|
spaceTreeBloc
|
||||||
..add(const SpaceTreeClearSelectionEvent())
|
..add(const SpaceTreeClearSelectionEvent())
|
||||||
@ -42,11 +42,18 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onChildSpaceSelected(
|
||||||
|
BuildContext context,
|
||||||
|
CommunityModel community,
|
||||||
|
SpaceModel child,
|
||||||
|
) {
|
||||||
|
return onSpaceSelected(context, community, child);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearData(BuildContext context) {
|
void clearData(BuildContext context) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
||||||
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
|
||||||
);
|
|
||||||
FetchOccupancyDataHelper.clearAllData(context);
|
FetchOccupancyDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||||
@ -13,6 +14,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
|
|||||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
||||||
@ -101,6 +103,11 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
|||||||
FakeRangeOfAqiService(),
|
FakeRangeOfAqiService(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (context) => AirQualityDistributionBloc(
|
||||||
|
FakeAirQualityDistributionService(),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const AnalyticsPageForm(),
|
child: const AnalyticsPageForm(),
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,7 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
|
|||||||
strategy.onSpaceSelected(context, community, space);
|
strategy.onSpaceSelected(context, community, space);
|
||||||
},
|
},
|
||||||
onSelectChildSpace: (community, child) {
|
onSelectChildSpace: (community, child) {
|
||||||
strategy.onSpaceSelected(context, community, child);
|
strategy.onChildSpaceSelected(context, community, child);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_tab/analytics_tab_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
import 'package:syncrow_web/pages/analytics/modules/analytics/enums/analytics_page_tab.dart';
|
||||||
@ -56,33 +57,16 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
const Spacer(),
|
const Spacer(),
|
||||||
Visibility(
|
Visibility(
|
||||||
key: ValueKey(selectedTab),
|
key: ValueKey(selectedTab),
|
||||||
visible: selectedTab == AnalyticsPageTab.energyManagement,
|
visible: selectedTab == AnalyticsPageTab.energyManagement ||
|
||||||
|
selectedTab == AnalyticsPageTab.airQuality,
|
||||||
child: Expanded(
|
child: Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: FittedBox(
|
child: FittedBox(
|
||||||
fit: BoxFit.scaleDown,
|
fit: BoxFit.scaleDown,
|
||||||
alignment: AlignmentDirectional.centerEnd,
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
child: AnalyticsDateFilterButton(
|
child: AnalyticsDateFilterButton(
|
||||||
onDateSelected: (DateTime value) {
|
onDateSelected: (value) {
|
||||||
context.read<AnalyticsDatePickerBloc>().add(
|
_onDateChanged(context, value, selectedTab);
|
||||||
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
|
||||||
);
|
|
||||||
|
|
||||||
final spaceTreeState =
|
|
||||||
context.read<SpaceTreeBloc>().state;
|
|
||||||
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
|
||||||
FetchEnergyManagementDataHelper
|
|
||||||
.loadEnergyManagementData(
|
|
||||||
context,
|
|
||||||
shouldFetchAnalyticsDevices: false,
|
|
||||||
selectedDate: value,
|
|
||||||
communityId:
|
|
||||||
spaceTreeState.selectedCommunities.firstOrNull ??
|
|
||||||
'',
|
|
||||||
spaceId:
|
|
||||||
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
@ -112,4 +96,73 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onDateChanged(
|
||||||
|
BuildContext context,
|
||||||
|
DateTime date,
|
||||||
|
AnalyticsPageTab selectedTab,
|
||||||
|
) {
|
||||||
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(montlyDate: date),
|
||||||
|
);
|
||||||
|
|
||||||
|
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
||||||
|
final communities = spaceTreeState.selectedCommunities;
|
||||||
|
final spaces = spaceTreeState.selectedSpaces;
|
||||||
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
|
switch (selectedTab) {
|
||||||
|
case AnalyticsPageTab.energyManagement:
|
||||||
|
_onEnergyManagementDateChanged(
|
||||||
|
context,
|
||||||
|
date: date,
|
||||||
|
communityUuid: communities.firstOrNull ?? '',
|
||||||
|
spaceUuid: spaces.firstOrNull ?? '',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case AnalyticsPageTab.airQuality:
|
||||||
|
_onAirQualityDateChanged(
|
||||||
|
context,
|
||||||
|
date: date,
|
||||||
|
communityUuid: communities.firstOrNull ?? '',
|
||||||
|
spaceUuid: spaces.firstOrNull ?? '',
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onEnergyManagementDateChanged(
|
||||||
|
BuildContext context, {
|
||||||
|
required DateTime date,
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
}) {
|
||||||
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(montlyDate: date),
|
||||||
|
);
|
||||||
|
|
||||||
|
FetchEnergyManagementDataHelper.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
shouldFetchAnalyticsDevices: false,
|
||||||
|
selectedDate: date,
|
||||||
|
communityId: communityUuid,
|
||||||
|
spaceId: spaceUuid,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAirQualityDateChanged(
|
||||||
|
BuildContext context, {
|
||||||
|
required DateTime date,
|
||||||
|
required String communityUuid,
|
||||||
|
required String spaceUuid,
|
||||||
|
}) {
|
||||||
|
FetchAirQualityDataHelper.loadAirQualityData(
|
||||||
|
context,
|
||||||
|
date: date,
|
||||||
|
communityUuid: communityUuid,
|
||||||
|
spaceUuid: spaceUuid,
|
||||||
|
shouldFetchAnalyticsDevices: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
class GetAirQualityDistributionParam {
|
||||||
|
final DateTime date;
|
||||||
|
final String spaceUuid;
|
||||||
|
|
||||||
|
const GetAirQualityDistributionParam({
|
||||||
|
required this.date,
|
||||||
|
required this.spaceUuid,
|
||||||
|
});
|
||||||
|
}
|
@ -1,16 +1,12 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
|
|
||||||
class GetRangeOfAqiParam extends Equatable {
|
class GetRangeOfAqiParam extends Equatable {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final String spaceUuid;
|
final String spaceUuid;
|
||||||
final AqiType aqiType;
|
|
||||||
|
|
||||||
const GetRangeOfAqiParam(
|
const GetRangeOfAqiParam({
|
||||||
{
|
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.spaceUuid,
|
required this.spaceUuid,
|
||||||
required this.aqiType,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
|
||||||
|
abstract interface class AirQualityDistributionService {
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
|
||||||
|
class FakeAirQualityDistributionService implements AirQualityDistributionService {
|
||||||
|
final _random = Random();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
) async {
|
||||||
|
return Future.delayed(
|
||||||
|
const Duration(milliseconds: 400),
|
||||||
|
() => List.generate(30, (index) {
|
||||||
|
final date = DateTime(2025, 5, 1).add(Duration(days: index));
|
||||||
|
|
||||||
|
final values = _generateRandomPercentages();
|
||||||
|
final nullMask = List.generate(6, (_) => _shouldBeNull());
|
||||||
|
|
||||||
|
if (nullMask.every((isNull) => isNull)) {
|
||||||
|
nullMask[_random.nextInt(6)] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final nonNullValues = _redistributePercentages(values, nullMask);
|
||||||
|
|
||||||
|
return AirQualityDataModel(
|
||||||
|
date: date,
|
||||||
|
data: [
|
||||||
|
AirQualityPercentageData(
|
||||||
|
type: AqiType.aqi.code,
|
||||||
|
percentage: nonNullValues[0],
|
||||||
|
name: 'good',
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'moderate',
|
||||||
|
type: AqiType.co2.code,
|
||||||
|
percentage: nonNullValues[1],
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'poor',
|
||||||
|
percentage: nonNullValues[2],
|
||||||
|
type: AqiType.hcho.code,
|
||||||
|
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'unhealthy',
|
||||||
|
percentage: nonNullValues[3],
|
||||||
|
type: AqiType.pm10.code,
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'severe',
|
||||||
|
type: AqiType.pm25.code,
|
||||||
|
percentage: nonNullValues[4],
|
||||||
|
),
|
||||||
|
AirQualityPercentageData(
|
||||||
|
name: 'hazardous',
|
||||||
|
percentage: nonNullValues[5],
|
||||||
|
type: AqiType.co2.code,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _redistributePercentages(
|
||||||
|
List<double> originalValues,
|
||||||
|
List<bool> nullMask,
|
||||||
|
) {
|
||||||
|
double nonNullSum = 0;
|
||||||
|
for (int i = 0; i < originalValues.length; i++) {
|
||||||
|
if (!nullMask[i]) {
|
||||||
|
nonNullSum += originalValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.generate(originalValues.length, (i) {
|
||||||
|
if (nullMask[i]) return 0;
|
||||||
|
return (originalValues[i] / nonNullSum * 100).roundToDouble();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldBeNull() => _random.nextDouble() < 0.6;
|
||||||
|
|
||||||
|
List<double> _generateRandomPercentages() {
|
||||||
|
final values = List.generate(6, (_) => _random.nextDouble());
|
||||||
|
|
||||||
|
final sum = values.reduce((a, b) => a + b);
|
||||||
|
|
||||||
|
return values.map((value) => (value / sum * 100).roundToDouble()).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
class RemoteAirQualityDistributionService implements AirQualityDistributionService {
|
||||||
|
RemoteAirQualityDistributionService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||||
|
GetAirQualityDistributionParam param,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
queryParameters: {
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'date': param.date.toIso8601String(),
|
||||||
|
},
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final jsonData = e as Map<String, dynamic>;
|
||||||
|
return AirQualityDataModel.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
|
||||||
@ -18,10 +19,15 @@ class FakeRangeOfAqiService implements RangeOfAqiService {
|
|||||||
final avg = (min + avgDelta).clamp(0.0, 301.0);
|
final avg = (min + avgDelta).clamp(0.0, 301.0);
|
||||||
final max = (avg + maxDelta).clamp(0.0, 301.0);
|
final max = (avg + maxDelta).clamp(0.0, 301.0);
|
||||||
|
|
||||||
return RangeOfAqi(
|
return RangeOfAqi(
|
||||||
min: min,
|
data: [
|
||||||
avg: avg,
|
RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
|
||||||
max: max,
|
RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max),
|
||||||
|
RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max),
|
||||||
|
],
|
||||||
date: date,
|
date: date,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
|
|
||||||
|
final class RemoteRangeOfAqiService implements RangeOfAqiService {
|
||||||
|
const RemoteRangeOfAqiService(this._httpService);
|
||||||
|
|
||||||
|
final HTTPService _httpService;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<RangeOfAqi>> load(GetRangeOfAqiParam param) async {
|
||||||
|
try {
|
||||||
|
final response = await _httpService.get(
|
||||||
|
path: 'endpoint',
|
||||||
|
queryParameters: {
|
||||||
|
'spaceUuid': param.spaceUuid,
|
||||||
|
'date': param.date.toIso8601String(),
|
||||||
|
},
|
||||||
|
expectedResponseModel: (data) {
|
||||||
|
final json = data as Map<String, dynamic>? ?? {};
|
||||||
|
final mappedData = json['data'] as List<dynamic>? ?? [];
|
||||||
|
return mappedData.map((e) {
|
||||||
|
final jsonData = e as Map<String, dynamic>;
|
||||||
|
return RangeOfAqi.fromJson(jsonData);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return response;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load energy consumption per phase: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,6 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
|||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_event.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
|
||||||
import 'package:syncrow_web/services/auth_api.dart';
|
import 'package:syncrow_web/services/auth_api.dart';
|
||||||
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
import 'package:syncrow_web/utils/constants/strings_manager.dart';
|
||||||
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
import 'package:syncrow_web/utils/helpers/shared_preferences_helper.dart';
|
||||||
@ -100,8 +99,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changePassword(
|
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||||
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
|
||||||
emit(LoadingForgetState());
|
emit(LoadingForgetState());
|
||||||
try {
|
try {
|
||||||
var response = await AuthenticationAPI.verifyOtp(
|
var response = await AuthenticationAPI.verifyOtp(
|
||||||
@ -115,14 +113,14 @@ Future<void> changePassword(
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
emit(SuccessForgetState());
|
emit(SuccessForgetState());
|
||||||
}
|
}
|
||||||
} on APIException catch (e) {
|
} on DioException catch (e) {
|
||||||
final errorMessage = e.message;
|
final errorData = e.response!.data;
|
||||||
|
String errorMessage = errorData['error']['message'] ?? 'something went wrong';
|
||||||
validate = errorMessage;
|
validate = errorMessage;
|
||||||
emit(AuthInitialState());
|
emit(AuthInitialState());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String? validateCode(String? value) {
|
String? validateCode(String? value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return 'Code is required';
|
return 'Code is required';
|
||||||
@ -151,7 +149,6 @@ Future<void> changePassword(
|
|||||||
static UserModel? user;
|
static UserModel? user;
|
||||||
bool showValidationMessage = false;
|
bool showValidationMessage = false;
|
||||||
|
|
||||||
|
|
||||||
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
void _login(LoginButtonPressed event, Emitter<AuthState> emit) async {
|
||||||
emit(AuthLoading());
|
emit(AuthLoading());
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
@ -164,24 +161,25 @@ Future<void> changePassword(
|
|||||||
|
|
||||||
token = await AuthenticationAPI.loginWithEmail(
|
token = await AuthenticationAPI.loginWithEmail(
|
||||||
model: LoginWithEmailModel(
|
model: LoginWithEmailModel(
|
||||||
email: event.username.toLowerCase(),
|
email: event.username,
|
||||||
password: event.password,
|
password: event.password,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} on APIException catch (e) {
|
} on DioException catch (e) {
|
||||||
validate = e.message;
|
final errorData = e.response!.data;
|
||||||
emit(LoginInitial());
|
String errorMessage = errorData['error']['message'];
|
||||||
return;
|
if (errorMessage == "Access denied for web platform") {
|
||||||
} catch (e) {
|
validate = errorMessage;
|
||||||
validate = 'Something went wrong';
|
} else {
|
||||||
|
validate = 'Invalid Credentials!';
|
||||||
|
}
|
||||||
emit(LoginInitial());
|
emit(LoginInitial());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.accessTokenIsNotEmpty) {
|
if (token.accessTokenIsNotEmpty) {
|
||||||
FlutterSecureStorage storage = const FlutterSecureStorage();
|
FlutterSecureStorage storage = const FlutterSecureStorage();
|
||||||
await storage.write(
|
await storage.write(key: Token.loginAccessTokenKey, value: token.accessToken);
|
||||||
key: Token.loginAccessTokenKey, value: token.accessToken);
|
|
||||||
const FlutterSecureStorage().write(
|
const FlutterSecureStorage().write(
|
||||||
key: UserModel.userUuidKey,
|
key: UserModel.userUuidKey,
|
||||||
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
value: Token.decodeToken(token.accessToken)['uuid'].toString());
|
||||||
@ -197,7 +195,6 @@ Future<void> changePassword(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
checkBoxToggle(
|
checkBoxToggle(
|
||||||
CheckBoxEvent event,
|
CheckBoxEvent event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
||||||
@ -16,7 +15,6 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
|||||||
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
|
||||||
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
|
||||||
import 'package:syncrow_web/services/devices_mang_api.dart';
|
import 'package:syncrow_web/services/devices_mang_api.dart';
|
||||||
import 'package:syncrow_web/services/routines_api.dart';
|
import 'package:syncrow_web/services/routines_api.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
@ -66,8 +64,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
TriggerSwitchTabsEvent event,
|
TriggerSwitchTabsEvent event,
|
||||||
Emitter<RoutineState> emit,
|
Emitter<RoutineState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
|
||||||
routineTab: event.isRoutineTab, createRoutineView: false));
|
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
if (event.isRoutineTab) {
|
if (event.isRoutineTab) {
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -93,8 +90,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||||
|
|
||||||
// Find the index of the item in teh current itemsList
|
// Find the index of the item in teh current itemsList
|
||||||
int index = updatedIfItems.indexWhere(
|
int index =
|
||||||
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||||
// Replace the map if the index is valid
|
// Replace the map if the index is valid
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
updatedIfItems[index] = event.item;
|
updatedIfItems[index] = event.item;
|
||||||
@ -103,21 +100,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.isTabToRun) {
|
if (event.isTabToRun) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||||
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||||
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddToThenContainer(
|
void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
|
||||||
AddToThenContainer event, Emitter<RoutineState> emit) {
|
|
||||||
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
|
|
||||||
// Find the index of the item in teh current itemsList
|
// Find the index of the item in teh current itemsList
|
||||||
int index = currentItems.indexWhere(
|
int index =
|
||||||
(map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
||||||
// Replace the map if the index is valid
|
// Replace the map if the index is valid
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
currentItems[index] = event.item;
|
currentItems[index] = event.item;
|
||||||
@ -128,8 +122,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
emit(state.copyWith(thenItems: currentItems));
|
emit(state.copyWith(thenItems: currentItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddFunctionsToRoutine(
|
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||||
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
|
||||||
try {
|
try {
|
||||||
if (event.functions.isEmpty) return;
|
if (event.functions.isEmpty) return;
|
||||||
|
|
||||||
@ -164,8 +157,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
currentSelectedFunctions[event.uniqueCustomId] =
|
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
||||||
List.from(event.functions);
|
|
||||||
|
|
||||||
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -173,30 +165,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadScenes(
|
Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
|
||||||
LoadScenes event, Emitter<RoutineState> emit) async {
|
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
List<ScenesModel> scenes = [];
|
List<ScenesModel> scenes = [];
|
||||||
try {
|
try {
|
||||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
||||||
createRoutineBloc.selectedCommunityId == '') {
|
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList =
|
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
scenes.addAll(
|
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||||
await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scenes.addAll(await SceneApi.getScenes(
|
scenes.addAll(await SceneApi.getScenes(
|
||||||
createRoutineBloc.selectedSpaceId,
|
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
|
||||||
createRoutineBloc.selectedCommunityId,
|
|
||||||
projectUuid));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -213,8 +199,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadAutomation(
|
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||||
LoadAutomation event, Emitter<RoutineState> emit) async {
|
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
List<ScenesModel> automations = [];
|
List<ScenesModel> automations = [];
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -222,22 +207,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
BuildContext context = NavigationService.navigatorKey.currentContext!;
|
||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
try {
|
try {
|
||||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
||||||
createRoutineBloc.selectedCommunityId == '') {
|
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList =
|
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
automations.addAll(
|
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
|
||||||
await SceneApi.getAutomation(spaceId, communityId, projectId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
automations.addAll(await SceneApi.getAutomation(
|
automations.addAll(await SceneApi.getAutomation(
|
||||||
createRoutineBloc.selectedSpaceId,
|
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
|
||||||
createRoutineBloc.selectedCommunityId,
|
|
||||||
projectId));
|
|
||||||
}
|
}
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: automations,
|
automations: automations,
|
||||||
@ -253,16 +233,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSearchRoutines(
|
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
|
||||||
SearchRoutines event, Emitter<RoutineState> emit) async {
|
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
emit(state.copyWith(isLoading: false, errorMessage: null));
|
emit(state.copyWith(isLoading: false, errorMessage: null));
|
||||||
emit(state.copyWith(searchText: event.query));
|
emit(state.copyWith(searchText: event.query));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onAddSelectedIcon(
|
FutureOr<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
|
||||||
AddSelectedIcon event, Emitter<RoutineState> emit) {
|
|
||||||
emit(state.copyWith(selectedIcon: event.icon));
|
emit(state.copyWith(selectedIcon: event.icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,8 +254,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
return actions.last['deviceId'] == 'delay';
|
return actions.last['deviceId'] == 'delay';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateScene(
|
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
||||||
CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -290,8 +267,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage:
|
errorMessage: 'A delay condition cannot be the only or the last action',
|
||||||
'A delay condition cannot be the only or the last action',
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -359,18 +335,15 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
errorMessage: 'Something went wrong',
|
errorMessage: 'Something went wrong',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} on APIException catch (e) {
|
} catch (e) {
|
||||||
final errorData = e.message;
|
|
||||||
String errorMessage = errorData;
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: errorMessage,
|
errorMessage: 'Something went wrong',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateAutomation(
|
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
||||||
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
@ -392,8 +365,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage:
|
errorMessage: 'A delay condition cannot be the only or the last action',
|
||||||
'A delay condition cannot be the only or the last action',
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
|
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
|
||||||
@ -484,8 +456,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result =
|
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
||||||
await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadAutomation());
|
add(const LoadAutomation());
|
||||||
@ -497,32 +468,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Something went wrong');
|
CustomSnackBar.redSnackBar('Something went wrong');
|
||||||
}
|
}
|
||||||
} on APIException catch (e) {
|
} catch (e) {
|
||||||
final errorData = e.message;
|
|
||||||
String errorMessage = errorData;
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: errorMessage,
|
errorMessage: 'Something went wrong',
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar(errorMessage);
|
CustomSnackBar.redSnackBar('Something went wrong');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onRemoveDragCard(
|
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||||
RemoveDragCard event, Emitter<RoutineState> emit) {
|
|
||||||
if (event.isFromThen) {
|
if (event.isFromThen) {
|
||||||
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
final selectedFunctions =
|
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
|
||||||
|
|
||||||
thenItems.removeAt(event.index);
|
thenItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
|
||||||
thenItems: thenItems, selectedFunctions: selectedFunctions));
|
|
||||||
} else {
|
} else {
|
||||||
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
final ifItems = List<Map<String, dynamic>>.from(state.ifItems);
|
||||||
final selectedFunctions =
|
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
|
||||||
|
|
||||||
ifItems.removeAt(event.index);
|
ifItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
@ -533,8 +498,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
isAutomation: false,
|
isAutomation: false,
|
||||||
isTabToRun: false));
|
isTabToRun: false));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
|
||||||
ifItems: ifItems, selectedFunctions: selectedFunctions));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -546,13 +510,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onEffectiveTimeEvent(
|
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||||
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
|
||||||
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSetRoutineName(
|
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
|
||||||
SetRoutineName event, Emitter<RoutineState> emit) {
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
routineName: event.name,
|
routineName: event.name,
|
||||||
));
|
));
|
||||||
@ -679,8 +641,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// return (thenItems, ifItems, currentFunctions);
|
// return (thenItems, ifItems, currentFunctions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> _onGetSceneDetails(
|
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
|
||||||
GetSceneDetails event, Emitter<RoutineState> emit) async {
|
|
||||||
try {
|
try {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@ -728,12 +689,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// if (!deviceCards.containsKey(deviceId)) {
|
// if (!deviceCards.containsKey(deviceId)) {
|
||||||
deviceCards[deviceId] = {
|
deviceCards[deviceId] = {
|
||||||
'entityId': action.entityId,
|
'entityId': action.entityId,
|
||||||
'deviceId':
|
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
|
||||||
'uniqueCustomId':
|
? action.entityId
|
||||||
action.type == 'automation' || action.actionExecutor == 'delay'
|
: const Uuid().v4(),
|
||||||
? action.entityId
|
|
||||||
: const Uuid().v4(),
|
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
: action.type == 'automation'
|
: action.type == 'automation'
|
||||||
@ -773,8 +732,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
||||||
} else if (action.executorProperty != null &&
|
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
||||||
action.actionExecutor != 'delay') {
|
|
||||||
final functions = matchingDevice?.functions ?? [];
|
final functions = matchingDevice?.functions ?? [];
|
||||||
final functionCode = action.executorProperty?.functionCode;
|
final functionCode = action.executorProperty?.functionCode;
|
||||||
for (DeviceFunction function in functions) {
|
for (DeviceFunction function in functions) {
|
||||||
@ -840,8 +798,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onResetRoutineState(
|
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
|
||||||
ResetRoutineState event, Emitter<RoutineState> emit) {
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
ifItems: [],
|
ifItems: [],
|
||||||
thenItems: [],
|
thenItems: [],
|
||||||
@ -865,8 +822,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
createRoutineView: false));
|
createRoutineView: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _deleteScene(
|
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
|
||||||
DeleteScene event, Emitter<RoutineState> emit) async {
|
|
||||||
try {
|
try {
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
@ -875,8 +831,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
if (state.isTabToRun) {
|
if (state.isTabToRun) {
|
||||||
await SceneApi.deleteScene(
|
await SceneApi.deleteScene(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
unitUuid: spaceBloc.state.selectedSpaces[0], sceneId: state.sceneId ?? '');
|
||||||
sceneId: state.sceneId ?? '');
|
|
||||||
} else {
|
} else {
|
||||||
await SceneApi.deleteAutomation(
|
await SceneApi.deleteAutomation(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
@ -899,14 +854,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
add(const LoadAutomation());
|
add(const LoadAutomation());
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
emit(state.copyWith(isLoading: false, createRoutineView: false));
|
emit(state.copyWith(isLoading: false, createRoutineView: false));
|
||||||
} on APIException catch (e) {
|
} catch (e) {
|
||||||
final errorData = e.message;
|
|
||||||
String errorMessage = errorData;
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: errorMessage,
|
errorMessage: 'Failed to delete scene',
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -924,8 +876,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
FutureOr<void> _fetchDevices(
|
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
||||||
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -934,21 +885,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
var createRoutineBloc = context.read<CreateRoutineBloc>();
|
||||||
var spaceBloc = context.read<SpaceTreeBloc>();
|
var spaceBloc = context.read<SpaceTreeBloc>();
|
||||||
|
|
||||||
if (createRoutineBloc.selectedSpaceId == '' &&
|
if (createRoutineBloc.selectedSpaceId == '' && createRoutineBloc.selectedCommunityId == '') {
|
||||||
createRoutineBloc.selectedCommunityId == '') {
|
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList =
|
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
devices.addAll(await DevicesManagementApi()
|
devices.addAll(
|
||||||
.fetchDevices(communityId, spaceId, projectUuid));
|
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
createRoutineBloc.selectedCommunityId,
|
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
|
||||||
createRoutineBloc.selectedSpaceId,
|
|
||||||
projectUuid));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isLoading: false, devices: devices));
|
emit(state.copyWith(isLoading: false, devices: devices));
|
||||||
@ -957,8 +904,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateScene(
|
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
|
||||||
UpdateScene event, Emitter<RoutineState> emit) async {
|
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -972,8 +918,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage:
|
errorMessage: 'A delay condition cannot be the only or the last action',
|
||||||
'A delay condition cannot be the only or the last action',
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -1026,8 +971,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result =
|
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
||||||
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -1046,8 +990,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateAutomation(
|
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
|
||||||
UpdateAutomation event, Emitter<RoutineState> emit) async {
|
|
||||||
try {
|
try {
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1171,11 +1114,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
errorMessage: result['message'],
|
errorMessage: result['message'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} on APIException catch (e) {
|
} catch (e) {
|
||||||
final errorData = e.message;
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: errorData,
|
errorMessage: 'Something went wrong',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1272,8 +1214,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// if (!deviceThenCards.containsKey(deviceId)) {
|
// if (!deviceThenCards.containsKey(deviceId)) {
|
||||||
deviceThenCards[deviceId] = {
|
deviceThenCards[deviceId] = {
|
||||||
'entityId': action.entityId,
|
'entityId': action.entityId,
|
||||||
'deviceId':
|
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
|
||||||
'uniqueCustomId': const Uuid().v4(),
|
'uniqueCustomId': const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
@ -1308,8 +1249,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
updatedFunctions[uniqueCustomId] = [];
|
updatedFunctions[uniqueCustomId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.executorProperty != null &&
|
if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
||||||
action.actionExecutor != 'delay') {
|
|
||||||
final functions = matchingDevice.functions;
|
final functions = matchingDevice.functions;
|
||||||
final functionCode = action.executorProperty!.functionCode;
|
final functionCode = action.executorProperty!.functionCode;
|
||||||
for (var function in functions) {
|
for (var function in functions) {
|
||||||
@ -1351,14 +1291,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ifItems = deviceIfCards.values
|
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
||||||
.where((card) => card['type'] == 'condition')
|
|
||||||
.toList();
|
|
||||||
final thenItems = deviceThenCards.values
|
final thenItems = deviceThenCards.values
|
||||||
.where((card) =>
|
.where((card) =>
|
||||||
card['type'] == 'action' ||
|
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
|
||||||
card['type'] == 'automation' ||
|
|
||||||
card['type'] == 'scene')
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1380,8 +1316,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSceneTrigger(
|
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
|
||||||
SceneTrigger event, Emitter<RoutineState> emit) async {
|
|
||||||
emit(state.copyWith(loadingSceneId: event.sceneId));
|
emit(state.copyWith(loadingSceneId: event.sceneId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1423,29 +1358,24 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
final updatedAutomations = await SceneApi.getAutomationByUnitId(
|
final updatedAutomations = await SceneApi.getAutomationByUnitId(
|
||||||
event.automationStatusUpdate.spaceUuid,
|
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
||||||
event.communityId,
|
|
||||||
projectId);
|
|
||||||
|
|
||||||
// Remove from loading set safely
|
// Remove from loading set safely
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
||||||
..remove(event.automationId);
|
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: updatedAutomations,
|
automations: updatedAutomations,
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
||||||
..remove(event.automationId);
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update failed',
|
errorMessage: 'Update failed',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
||||||
..remove(event.automationId);
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update error: ${e.toString()}',
|
errorMessage: 'Update error: ${e.toString()}',
|
||||||
|
@ -12,53 +12,22 @@ class ConditionToggle extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static const _conditions = ["<", "==", ">"];
|
static const _conditions = ["<", "==", ">"];
|
||||||
static const _icons = [
|
|
||||||
Icons.chevron_left,
|
|
||||||
Icons.drag_handle,
|
|
||||||
Icons.chevron_right
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedIndex = _conditions.indexOf(currentCondition ?? "==");
|
return ToggleButtons(
|
||||||
|
onPressed: (index) => onChanged(_conditions[index]),
|
||||||
return Container(
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
height: 30,
|
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
|
||||||
width: MediaQuery.of(context).size.width * 0.1,
|
selectedColor: Colors.white,
|
||||||
decoration: BoxDecoration(
|
fillColor: ColorsManager.primaryColorWithOpacity,
|
||||||
color: ColorsManager.softGray.withOpacity(0.5),
|
color: ColorsManager.primaryColorWithOpacity,
|
||||||
borderRadius: BorderRadius.circular(50),
|
constraints: const BoxConstraints(
|
||||||
),
|
minHeight: 40.0,
|
||||||
clipBehavior: Clip.antiAlias,
|
minWidth: 40.0,
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: List.generate(_conditions.length, (index) {
|
|
||||||
final isSelected = index == selectedIndex;
|
|
||||||
return Expanded(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () => onChanged(_conditions[index]),
|
|
||||||
child: AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 180),
|
|
||||||
curve: Curves.ease,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color:
|
|
||||||
isSelected ? ColorsManager.vividBlue : Colors.transparent,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Icon(
|
|
||||||
_icons[index],
|
|
||||||
size: 20,
|
|
||||||
color: isSelected
|
|
||||||
? ColorsManager.whiteColors
|
|
||||||
: ColorsManager.blackColor,
|
|
||||||
weight: isSelected ? 700 : 500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
|
isSelected: _conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
||||||
|
children: _conditions.map((c) => Text(c)).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,27 +99,7 @@ class _EnergyClampDialogState extends State<EnergyClampDialog> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const DialogHeader('Energy Clamp Conditions'),
|
const DialogHeader('Energy Clamp Conditions'),
|
||||||
Expanded(
|
Expanded(child: _buildMainContent(context, state)),
|
||||||
child: Visibility(
|
|
||||||
visible: _functions.isNotEmpty,
|
|
||||||
replacement: SizedBox(
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Text(
|
|
||||||
'You Cant add\n the Power Clamp to Then Section',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: context.textTheme.bodyMedium!.copyWith(
|
|
||||||
color: ColorsManager.red,
|
|
||||||
fontWeight: FontWeight.w400),
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: _buildMainContent(context, state),
|
|
||||||
)),
|
|
||||||
_buildDialogFooter(context, state),
|
_buildDialogFooter(context, state),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -24,9 +24,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
on<PaginationEvent>(_fetchPaginationSpaces);
|
on<PaginationEvent>(_fetchPaginationSpaces);
|
||||||
on<DebouncedSearchEvent>(_onDebouncedSearch);
|
on<DebouncedSearchEvent>(_onDebouncedSearch);
|
||||||
on<SpaceTreeClearSelectionEvent>(_onSpaceTreeClearSelectionEvent);
|
on<SpaceTreeClearSelectionEvent>(_onSpaceTreeClearSelectionEvent);
|
||||||
on<AnalyticsClearAllSpaceTreeSelectionsEvent>(
|
|
||||||
_onAnalyticsClearAllSpaceTreeSelectionsEvent,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Timer _timer = Timer(const Duration(microseconds: 0), () {});
|
Timer _timer = Timer(const Duration(microseconds: 0), () {});
|
||||||
|
|
||||||
@ -496,20 +493,6 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAnalyticsClearAllSpaceTreeSelectionsEvent(
|
|
||||||
AnalyticsClearAllSpaceTreeSelectionsEvent event,
|
|
||||||
Emitter<SpaceTreeState> emit,
|
|
||||||
) async {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
selectedCommunities: [],
|
|
||||||
selectedCommunityAndSpaces: {},
|
|
||||||
selectedSpaces: [],
|
|
||||||
soldCheck: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
@ -112,7 +112,3 @@ class ClearCachedData extends SpaceTreeEvent {}
|
|||||||
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
|
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
|
||||||
const SpaceTreeClearSelectionEvent();
|
const SpaceTreeClearSelectionEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
final class AnalyticsClearAllSpaceTreeSelectionsEvent extends SpaceTreeEvent {
|
|
||||||
const AnalyticsClearAllSpaceTreeSelectionsEvent();
|
|
||||||
}
|
|
||||||
|
@ -48,8 +48,7 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
|
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
|
||||||
builder: (context, state) {
|
|
||||||
final communities = state.searchQuery.isNotEmpty
|
final communities = state.searchQuery.isNotEmpty
|
||||||
? state.filteredCommunity
|
? state.filteredCommunity
|
||||||
: state.communityList;
|
: state.communityList;
|
||||||
@ -133,118 +132,104 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
CustomSearchBar(
|
CustomSearchBar(
|
||||||
onSearchChanged: (query) =>
|
onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
|
||||||
context.read<SpaceTreeBloc>().add(
|
SearchQueryEvent(query),
|
||||||
SearchQueryEvent(query),
|
),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: state.isSearching
|
child: state.isSearching
|
||||||
? const Center(child: CircularProgressIndicator())
|
? const Center(child: CircularProgressIndicator())
|
||||||
: communities.isEmpty
|
: SidebarCommunitiesList(
|
||||||
? Center(
|
onScrollToEnd: () {
|
||||||
child: Text(
|
if (!state.paginationIsLoading) {
|
||||||
'No communities found',
|
context.read<SpaceTreeBloc>().add(
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
PaginationEvent(
|
||||||
color: ColorsManager.textGray,
|
state.paginationModel,
|
||||||
),
|
state.communityList,
|
||||||
),
|
),
|
||||||
)
|
);
|
||||||
: SidebarCommunitiesList(
|
}
|
||||||
onScrollToEnd: () {
|
},
|
||||||
if (!state.paginationIsLoading) {
|
scrollController: _scrollController,
|
||||||
|
communities: communities,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return CustomExpansionTileSpaceTree(
|
||||||
|
title: communities[index].name,
|
||||||
|
isSelected: state.selectedCommunities
|
||||||
|
.contains(communities[index].uuid),
|
||||||
|
isSoldCheck: state.selectedCommunities
|
||||||
|
.contains(communities[index].uuid),
|
||||||
|
onExpansionChanged: () =>
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
PaginationEvent(
|
OnCommunityExpanded(
|
||||||
state.paginationModel,
|
communities[index].uuid,
|
||||||
state.communityList,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
isExpanded: state.expandedCommunities.contains(
|
||||||
|
communities[index].uuid,
|
||||||
|
),
|
||||||
|
onItemSelected: () {
|
||||||
|
widget.onSelect();
|
||||||
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
OnCommunitySelected(
|
||||||
|
communities[index].uuid,
|
||||||
|
communities[index].spaces,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
scrollController: _scrollController,
|
children: communities[index].spaces.map(
|
||||||
communities: communities,
|
(space) {
|
||||||
itemBuilder: (context, index) {
|
return CustomExpansionTileSpaceTree(
|
||||||
return CustomExpansionTileSpaceTree(
|
title: space.name,
|
||||||
title: communities[index].name,
|
isExpanded:
|
||||||
isSelected: state.selectedCommunities
|
state.expandedSpaces.contains(space.uuid),
|
||||||
.contains(communities[index].uuid),
|
onItemSelected: () {
|
||||||
isSoldCheck: state.selectedCommunities
|
final isParentSelected = _isParentSelected(
|
||||||
.contains(communities[index].uuid),
|
state,
|
||||||
onExpansionChanged: () =>
|
communities[index],
|
||||||
|
space,
|
||||||
|
);
|
||||||
|
if (widget
|
||||||
|
.shouldDisableDeselectingChildrenOfSelectedParent &&
|
||||||
|
isParentSelected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widget.onSelect();
|
||||||
context.read<SpaceTreeBloc>().add(
|
context.read<SpaceTreeBloc>().add(
|
||||||
OnCommunityExpanded(
|
OnSpaceSelected(
|
||||||
communities[index].uuid,
|
communities[index],
|
||||||
|
space.uuid ?? '',
|
||||||
|
space.children,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
isExpanded:
|
|
||||||
state.expandedCommunities.contains(
|
|
||||||
communities[index].uuid,
|
|
||||||
),
|
|
||||||
onItemSelected: () {
|
|
||||||
widget.onSelect();
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnCommunitySelected(
|
|
||||||
communities[index].uuid,
|
|
||||||
communities[index].spaces,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
children: communities[index].spaces.map(
|
|
||||||
(space) {
|
|
||||||
return CustomExpansionTileSpaceTree(
|
|
||||||
title: space.name,
|
|
||||||
isExpanded: state.expandedSpaces
|
|
||||||
.contains(space.uuid),
|
|
||||||
onItemSelected: () {
|
|
||||||
final isParentSelected =
|
|
||||||
_isParentSelected(
|
|
||||||
state,
|
|
||||||
communities[index],
|
|
||||||
space,
|
|
||||||
);
|
);
|
||||||
if (widget
|
|
||||||
.shouldDisableDeselectingChildrenOfSelectedParent &&
|
|
||||||
isParentSelected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
widget.onSelect();
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnSpaceSelected(
|
|
||||||
communities[index],
|
|
||||||
space.uuid ?? '',
|
|
||||||
space.children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onExpansionChanged: () =>
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnSpaceExpanded(
|
|
||||||
communities[index].uuid,
|
|
||||||
space.uuid ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isSelected: state.selectedSpaces
|
|
||||||
.contains(space.uuid) ||
|
|
||||||
state.soldCheck
|
|
||||||
.contains(space.uuid),
|
|
||||||
isSoldCheck: state.soldCheck
|
|
||||||
.contains(space.uuid),
|
|
||||||
children: _buildNestedSpaces(
|
|
||||||
context,
|
|
||||||
state,
|
|
||||||
space,
|
|
||||||
communities[index],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
).toList(),
|
onExpansionChanged: () =>
|
||||||
);
|
context.read<SpaceTreeBloc>().add(
|
||||||
},
|
OnSpaceExpanded(
|
||||||
),
|
communities[index].uuid,
|
||||||
|
space.uuid ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isSelected: state.selectedSpaces
|
||||||
|
.contains(space.uuid) ||
|
||||||
|
state.soldCheck.contains(space.uuid),
|
||||||
|
isSoldCheck:
|
||||||
|
state.soldCheck.contains(space.uuid),
|
||||||
|
children: _buildNestedSpaces(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
|
space,
|
||||||
|
communities[index],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (state.paginationIsLoading)
|
if (state.paginationIsLoading) const CircularProgressIndicator(),
|
||||||
const CircularProgressIndicator(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
class APIException implements Exception {
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
APIException(this.message);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,18 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
import 'package:syncrow_web/pages/auth/model/region_model.dart';
|
||||||
import 'package:syncrow_web/pages/auth/model/token.dart';
|
import 'package:syncrow_web/pages/auth/model/token.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
|
|
||||||
class AuthenticationAPI {
|
class AuthenticationAPI {
|
||||||
static Future<Token> loginWithEmail({required var model}) async {
|
static Future<Token> loginWithEmail({required var model}) async {
|
||||||
try {
|
final response = await HTTPService().post(
|
||||||
final response = await HTTPService().post(
|
path: ApiEndpoints.login,
|
||||||
path: ApiEndpoints.login,
|
body: model.toJson(),
|
||||||
body: model.toJson(),
|
showServerMessage: true,
|
||||||
showServerMessage: true,
|
expectedResponseModel: (json) {
|
||||||
expectedResponseModel: (json) {
|
return Token.fromJson(json['data']);
|
||||||
return Token.fromJson(json['data']);
|
});
|
||||||
});
|
return response;
|
||||||
return response;
|
|
||||||
} on DioException catch (e) {
|
|
||||||
final message = e.response?.data['error']['message'] ??
|
|
||||||
'An error occurred while logging in';
|
|
||||||
throw APIException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future forgetPassword({
|
static Future forgetPassword({
|
||||||
@ -28,18 +20,12 @@ class AuthenticationAPI {
|
|||||||
required var password,
|
required var password,
|
||||||
required var otpCode,
|
required var otpCode,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final response = await HTTPService().post(
|
||||||
final response = await HTTPService().post(
|
path: ApiEndpoints.forgetPassword,
|
||||||
path: ApiEndpoints.forgetPassword,
|
body: {"email": email, "password": password, "otpCode": otpCode},
|
||||||
body: {"email": email, "password": password, "otpCode": otpCode},
|
showServerMessage: true,
|
||||||
showServerMessage: true,
|
expectedResponseModel: (json) {});
|
||||||
expectedResponseModel: (json) {});
|
return response;
|
||||||
return response;
|
|
||||||
} on DioException catch (e) {
|
|
||||||
final message = e.response?.data['error']['message'] ??
|
|
||||||
'An error occurred while resetting the password';
|
|
||||||
throw APIException(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<int?> sendOtp({required String email}) async {
|
static Future<int?> sendOtp({required String email}) async {
|
||||||
@ -53,26 +39,19 @@ class AuthenticationAPI {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future verifyOtp(
|
static Future verifyOtp({required String email, required String otpCode}) async {
|
||||||
{required String email, required String otpCode}) async {
|
final response = await HTTPService().post(
|
||||||
try {
|
path: ApiEndpoints.verifyOtp,
|
||||||
final response = await HTTPService().post(
|
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
|
||||||
path: ApiEndpoints.verifyOtp,
|
showServerMessage: true,
|
||||||
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
|
expectedResponseModel: (json) {
|
||||||
showServerMessage: true,
|
if (json['message'] == 'Otp Verified Successfully') {
|
||||||
expectedResponseModel: (json) {
|
return true;
|
||||||
if (json['message'] == 'Otp Verified Successfully') {
|
} else {
|
||||||
return true;
|
return false;
|
||||||
} else {
|
}
|
||||||
return false;
|
});
|
||||||
}
|
return response;
|
||||||
});
|
|
||||||
return response;
|
|
||||||
} on APIException catch (e) {
|
|
||||||
throw APIException(e.message);
|
|
||||||
} catch (e) {
|
|
||||||
throw APIException('An error occurred while verifying the OTP');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<RegionModel>> fetchRegion() async {
|
static Future<List<RegionModel>> fetchRegion() async {
|
||||||
@ -80,9 +59,7 @@ class AuthenticationAPI {
|
|||||||
path: ApiEndpoints.getRegion,
|
path: ApiEndpoints.getRegion,
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
return (json as List)
|
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
|
||||||
.map((zone) => RegionModel.fromJson(zone))
|
|
||||||
.toList();
|
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
|
import 'package:syncrow_web/pages/routines/bloc/automation_scene_trigger_bloc/automation_status_update.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
|
import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/create_automation_model.dart';
|
||||||
@ -6,7 +5,6 @@ import 'package:syncrow_web/pages/routines/models/create_scene_and_autoamtion/cr
|
|||||||
import 'package:syncrow_web/pages/routines/models/icon_model.dart';
|
import 'package:syncrow_web/pages/routines/models/icon_model.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
||||||
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_model.dart';
|
||||||
import 'package:syncrow_web/services/api/api_exception.dart';
|
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
import 'package:syncrow_web/utils/constants/api_const.dart';
|
import 'package:syncrow_web/utils/constants/api_const.dart';
|
||||||
|
|
||||||
@ -28,10 +26,9 @@ class SceneApi {
|
|||||||
);
|
);
|
||||||
debugPrint('create scene response: $response');
|
debugPrint('create scene response: $response');
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} catch (e) {
|
||||||
String errorMessage =
|
debugPrint(e.toString());
|
||||||
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
rethrow;
|
||||||
throw APIException(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,10 +48,9 @@ class SceneApi {
|
|||||||
);
|
);
|
||||||
debugPrint('create automation response: $response');
|
debugPrint('create automation response: $response');
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} catch (e) {
|
||||||
String errorMessage =
|
debugPrint(e.toString());
|
||||||
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
rethrow;
|
||||||
throw APIException(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,10 +165,8 @@ class SceneApi {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} catch (e) {
|
||||||
String errorMessage =
|
rethrow;
|
||||||
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
|
||||||
throw APIException(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,10 +185,8 @@ class SceneApi {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} catch (e) {
|
||||||
String errorMessage =
|
rethrow;
|
||||||
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
|
||||||
throw APIException(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,10 +217,8 @@ class SceneApi {
|
|||||||
expectedResponseModel: (json) => json['statusCode'] == 200,
|
expectedResponseModel: (json) => json['statusCode'] == 200,
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} catch (e) {
|
||||||
String errorMessage =
|
rethrow;
|
||||||
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
|
||||||
throw APIException(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,10 +236,8 @@ class SceneApi {
|
|||||||
expectedResponseModel: (json) => json['statusCode'] == 200,
|
expectedResponseModel: (json) => json['statusCode'] == 200,
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} catch (e) {
|
||||||
String errorMessage =
|
rethrow;
|
||||||
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
|
||||||
throw APIException(errorMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user