mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 01:35:25 +00:00
Compare commits
15 Commits
SP-1593-FE
...
SP-1175-FE
Author | SHA1 | Date | |
---|---|---|---|
3c98365338 | |||
f07dbad1ea | |||
87df8e4091 | |||
2d68fc23a3 | |||
15ea1b4c5a | |||
17f6985dbf | |||
d1ddf75a42 | |||
393a5361f0 | |||
a56e93d0d7 | |||
94847fa936 | |||
78f42dacf6 | |||
fdabfe5d95 | |||
8916000696 | |||
305d695358 | |||
0a9d53e5bd |
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
@ -16,6 +16,7 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_dev.dart",
|
"lib/main_dev.dart",
|
||||||
|
"--web-experimental-hot-reload",
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
@ -35,6 +36,7 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main_staging.dart",
|
"lib/main_staging.dart",
|
||||||
|
"--web-experimental-hot-reload",
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
@ -54,6 +56,7 @@
|
|||||||
"3000",
|
"3000",
|
||||||
"-t",
|
"-t",
|
||||||
"lib/main.dart",
|
"lib/main.dart",
|
||||||
|
"--web-experimental-hot-reload",
|
||||||
],
|
],
|
||||||
|
|
||||||
"flutterMode": "debug"
|
"flutterMode": "debug"
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
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,49 +1,18 @@
|
|||||||
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.data,
|
required this.min,
|
||||||
|
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 => [data, date];
|
List<Object?> get props => [min, avg, max, 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];
|
|
||||||
}
|
}
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
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,7 +1,6 @@
|
|||||||
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';
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ 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;
|
||||||
@ -22,55 +20,19 @@ class RangeOfAqiBloc extends Bloc<RangeOfAqiEvent, RangeOfAqiState> {
|
|||||||
Emitter<RangeOfAqiState> emit,
|
Emitter<RangeOfAqiState> emit,
|
||||||
) async {
|
) async {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(status: RangeOfAqiStatus.loading),
|
RangeOfAqiState(
|
||||||
|
status: RangeOfAqiStatus.loading,
|
||||||
|
rangeOfAqi: state.rangeOfAqi,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
final rangeOfAqi = await _rangeOfAqiService.load(event.param);
|
||||||
emit(
|
emit(RangeOfAqiState(status: RangeOfAqiStatus.loaded, rangeOfAqi: rangeOfAqi));
|
||||||
state.copyWith(
|
|
||||||
status: RangeOfAqiStatus.loaded,
|
|
||||||
rangeOfAqi: rangeOfAqi,
|
|
||||||
filteredRangeOfAqi: _arrangeChartDataByType(
|
|
||||||
rangeOfAqi,
|
|
||||||
state.selectedAqiType,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
emit(
|
emit(RangeOfAqiState(status: RangeOfAqiStatus.failure, errorMessage: '$e'));
|
||||||
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,15 +16,6 @@ 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,35 +5,14 @@ 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 =>
|
List<Object?> get props => [status, rangeOfAqi, errorMessage];
|
||||||
[status, rangeOfAqi, filteredRangeOfAqi, errorMessage, selectedAqiType];
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
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';
|
||||||
|
|
||||||
@ -14,10 +13,8 @@ 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(
|
||||||
@ -29,11 +26,7 @@ abstract final class FetchAirQualityDataHelper {
|
|||||||
context,
|
context,
|
||||||
spaceUuid: spaceUuid,
|
spaceUuid: spaceUuid,
|
||||||
date: date,
|
date: date,
|
||||||
);
|
aqiType: AqiType.aqi,
|
||||||
loadAirQualityDistribution(
|
|
||||||
context,
|
|
||||||
spaceUuid: spaceUuid,
|
|
||||||
date: date,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +37,7 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,26 +67,16 @@ 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,6 +1,5 @@
|
|||||||
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 {
|
||||||
@ -24,14 +23,8 @@ class AirQualityView extends StatelessWidget {
|
|||||||
height: height * 1.2,
|
height: height * 1.2,
|
||||||
child: const AirQualityEndSideWidget(),
|
child: const AirQualityEndSideWidget(),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(height: height * 0.5, child: const RangeOfAqiChartBox()),
|
||||||
height: height * 0.5,
|
SizedBox(height: height * 0.5, child: const Placeholder()),
|
||||||
child: const RangeOfAqiChartBox(),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: height * 0.5,
|
|
||||||
child: const AqiDistributionChartBox(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -53,7 +46,7 @@ class AirQualityView extends StatelessWidget {
|
|||||||
spacing: 20,
|
spacing: 20,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: RangeOfAqiChartBox()),
|
Expanded(child: RangeOfAqiChartBox()),
|
||||||
Expanded(child: AqiDistributionChartBox()),
|
Expanded(child: Placeholder()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,174 +0,0 @@
|
|||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
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,18 +3,17 @@ 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'),
|
pm25('PM2.5', 'µg/m³'),
|
||||||
pm10('PM10', 'µg/m³', 'pm10'),
|
pm10('PM10', 'µg/m³'),
|
||||||
hcho('HCHO', 'mg/m³', 'hcho'),
|
hcho('HCHO', 'mg/m³'),
|
||||||
tvoc('TVOC', 'µg/m³', 'tvoc'),
|
tvoc('TVOC', 'µg/m³'),
|
||||||
co2('CO2', 'ppm', 'co2');
|
co2('CO2', 'ppm');
|
||||||
|
|
||||||
const AqiType(this.value, this.unit, this.code);
|
const AqiType(this.value, this.unit);
|
||||||
|
|
||||||
final String value;
|
final String value;
|
||||||
final String unit;
|
final String unit;
|
||||||
final String code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AqiTypeDropdown extends StatefulWidget {
|
class AqiTypeDropdown extends StatefulWidget {
|
||||||
|
@ -13,37 +13,23 @@ class RangeOfAqiChart extends StatelessWidget {
|
|||||||
required this.chartData,
|
required this.chartData,
|
||||||
});
|
});
|
||||||
|
|
||||||
List<(List<double> values, Color color, Color? dotColor)> get _lines {
|
List<(List<double> values, Color color, Color? dotColor)> get _lines => [
|
||||||
final sortedData = List<RangeOfAqi>.from(chartData)
|
(
|
||||||
..sort((a, b) => a.date.compareTo(b.date));
|
chartData.map((e) => e.max).toList(),
|
||||||
|
ColorsManager.maxPurple,
|
||||||
return [
|
ColorsManager.maxPurpleDot,
|
||||||
(
|
),
|
||||||
sortedData.map((e) {
|
(
|
||||||
final value = e.data.firstOrNull;
|
chartData.map((e) => e.avg).toList(),
|
||||||
return value?.max ?? 0;
|
Colors.white,
|
||||||
}).toList(),
|
null,
|
||||||
ColorsManager.maxPurple,
|
),
|
||||||
ColorsManager.maxPurpleDot,
|
(
|
||||||
),
|
chartData.map((e) => e.min).toList(),
|
||||||
(
|
ColorsManager.minBlue,
|
||||||
sortedData.map((e) {
|
ColorsManager.minBlueDot,
|
||||||
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.filteredRangeOfAqi)),
|
Expanded(child: RangeOfAqiChart(chartData: state.rangeOfAqi)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
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/range_of_aqi/range_of_aqi_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/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({
|
const RangeOfAqiChartTitle({required this.isLoading, super.key});
|
||||||
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 = [
|
||||||
@ -69,9 +66,12 @@ class RangeOfAqiChartTitle extends StatelessWidget {
|
|||||||
|
|
||||||
if (spaceUuid == null) return;
|
if (spaceUuid == null) return;
|
||||||
|
|
||||||
if (value != null) {
|
FetchAirQualityDataHelper.loadRangeOfAqi(
|
||||||
context.read<RangeOfAqiBloc>().add(UpdateAqiTypeEvent(value));
|
context,
|
||||||
}
|
spaceUuid: spaceUuid,
|
||||||
|
date: context.read<AnalyticsDatePickerBloc>().state.monthlyDate,
|
||||||
|
aqiType: value ?? AqiType.aqi,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,7 +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/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';
|
||||||
@ -27,10 +26,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);
|
||||||
|
|
||||||
if (isSpaceSelected) {
|
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
||||||
clearData(context);
|
if (hasSelectedSpaces) clearData(context);
|
||||||
return;
|
|
||||||
}
|
if (isSpaceSelected) return;
|
||||||
|
|
||||||
spaceTreeBloc
|
spaceTreeBloc
|
||||||
..add(const SpaceTreeClearSelectionEvent())
|
..add(const SpaceTreeClearSelectionEvent())
|
||||||
@ -40,22 +39,14 @@ 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(const SpaceTreeClearSelectionEvent());
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
||||||
|
);
|
||||||
FetchAirQualityDataHelper.clearAllData(context);
|
FetchAirQualityDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,5 @@ 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,24 +14,14 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
List<SpaceModel> spaces,
|
List<SpaceModel> spaces,
|
||||||
) {
|
) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
OnCommunitySelected(
|
final isCommunitySelected =
|
||||||
community.uuid,
|
spaceTreeBloc.state.selectedCommunities.contains(community.uuid);
|
||||||
spaces,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
if (isCommunitySelected) {
|
||||||
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
|
||||||
@ -40,21 +30,31 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
|
|||||||
CommunityModel community,
|
CommunityModel community,
|
||||||
SpaceModel space,
|
SpaceModel space,
|
||||||
) {
|
) {
|
||||||
context.read<SpaceTreeBloc>().add(
|
final spaceTreeBloc = context.read<SpaceTreeBloc>();
|
||||||
OnSpaceSelected(
|
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
|
||||||
community,
|
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
||||||
space.uuid ?? '',
|
|
||||||
space.children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final spaceTreeState = context.read<SpaceTreeBloc>().state;
|
if (isSpaceSelected) {
|
||||||
if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
|
final firstSelectedSpace = spaceTreeBloc.state.selectedSpaces.first;
|
||||||
spaceTreeState.selectedSpaces.contains(space.uuid)) {
|
final isTheFirstSelectedSpace = firstSelectedSpace == space.uuid;
|
||||||
clearData(context);
|
if (isTheFirstSelectedSpace) {
|
||||||
|
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,18 +62,11 @@ 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(const SpaceTreeClearSelectionEvent());
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
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);
|
||||||
|
|
||||||
if (isSpaceSelected) {
|
final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
|
||||||
clearData(context);
|
if (hasSelectedSpaces) clearData(context);
|
||||||
return;
|
|
||||||
}
|
if (isSpaceSelected) return;
|
||||||
|
|
||||||
spaceTreeBloc
|
spaceTreeBloc
|
||||||
..add(const SpaceTreeClearSelectionEvent())
|
..add(const SpaceTreeClearSelectionEvent())
|
||||||
@ -42,18 +42,11 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void onChildSpaceSelected(
|
|
||||||
BuildContext context,
|
|
||||||
CommunityModel community,
|
|
||||||
SpaceModel child,
|
|
||||||
) {
|
|
||||||
return onSpaceSelected(context, community, child);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void clearData(BuildContext context) {
|
void clearData(BuildContext context) {
|
||||||
context.read<SpaceTreeBloc>().add(const SpaceTreeClearSelectionEvent());
|
context.read<SpaceTreeBloc>().add(
|
||||||
|
const AnalyticsClearAllSpaceTreeSelectionsEvent(),
|
||||||
|
);
|
||||||
FetchOccupancyDataHelper.clearAllData(context);
|
FetchOccupancyDataHelper.clearAllData(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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';
|
||||||
@ -14,7 +13,6 @@ 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';
|
||||||
@ -103,11 +101,6 @@ 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.onChildSpaceSelected(context, community, child);
|
strategy.onSpaceSelected(context, community, child);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
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';
|
||||||
@ -57,16 +56,33 @@ 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: (value) {
|
onDateSelected: (DateTime value) {
|
||||||
_onDateChanged(context, value, selectedTab);
|
context.read<AnalyticsDatePickerBloc>().add(
|
||||||
|
UpdateAnalyticsDatePickerEvent(montlyDate: value),
|
||||||
|
);
|
||||||
|
|
||||||
|
final spaceTreeState =
|
||||||
|
context.read<SpaceTreeBloc>().state;
|
||||||
|
if (spaceTreeState.selectedSpaces.isNotEmpty) {
|
||||||
|
FetchEnergyManagementDataHelper
|
||||||
|
.loadEnergyManagementData(
|
||||||
|
context,
|
||||||
|
shouldFetchAnalyticsDevices: false,
|
||||||
|
selectedDate: value,
|
||||||
|
communityId:
|
||||||
|
spaceTreeState.selectedCommunities.firstOrNull ??
|
||||||
|
'',
|
||||||
|
spaceId:
|
||||||
|
spaceTreeState.selectedSpaces.firstOrNull ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
selectedDate: context
|
selectedDate: context
|
||||||
.watch<AnalyticsDatePickerBloc>()
|
.watch<AnalyticsDatePickerBloc>()
|
||||||
@ -96,73 +112,4 @@ 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
class GetAirQualityDistributionParam {
|
|
||||||
final DateTime date;
|
|
||||||
final String spaceUuid;
|
|
||||||
|
|
||||||
const GetAirQualityDistributionParam({
|
|
||||||
required this.date,
|
|
||||||
required this.spaceUuid,
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,12 +1,16 @@
|
|||||||
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
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
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,5 +1,4 @@
|
|||||||
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
|
||||||
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
|
||||||
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart';
|
||||||
|
|
||||||
@ -19,15 +18,10 @@ 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(
|
||||||
data: [
|
min: min,
|
||||||
RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max),
|
avg: avg,
|
||||||
RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max),
|
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,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
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,6 +13,7 @@ import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
|
|||||||
import 'package:syncrow_web/pages/home/bloc/home_bloc.dart';
|
import 'package:syncrow_web/pages/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';
|
||||||
@ -99,7 +100,8 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changePassword(ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
Future<void> changePassword(
|
||||||
|
ChangePasswordEvent event, Emitter<AuthState> emit) async {
|
||||||
emit(LoadingForgetState());
|
emit(LoadingForgetState());
|
||||||
try {
|
try {
|
||||||
var response = await AuthenticationAPI.verifyOtp(
|
var response = await AuthenticationAPI.verifyOtp(
|
||||||
@ -113,14 +115,14 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
emit(const TimerState(isButtonEnabled: true, remainingTime: 0));
|
||||||
emit(SuccessForgetState());
|
emit(SuccessForgetState());
|
||||||
}
|
}
|
||||||
} on DioException catch (e) {
|
} on APIException catch (e) {
|
||||||
final errorData = e.response!.data;
|
final errorMessage = e.message;
|
||||||
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';
|
||||||
@ -149,6 +151,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
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) {
|
||||||
@ -161,25 +164,24 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
|
|
||||||
token = await AuthenticationAPI.loginWithEmail(
|
token = await AuthenticationAPI.loginWithEmail(
|
||||||
model: LoginWithEmailModel(
|
model: LoginWithEmailModel(
|
||||||
email: event.username,
|
email: event.username.toLowerCase(),
|
||||||
password: event.password,
|
password: event.password,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} on DioException catch (e) {
|
} on APIException catch (e) {
|
||||||
final errorData = e.response!.data;
|
validate = e.message;
|
||||||
String errorMessage = errorData['error']['message'];
|
emit(LoginInitial());
|
||||||
if (errorMessage == "Access denied for web platform") {
|
return;
|
||||||
validate = errorMessage;
|
} catch (e) {
|
||||||
} else {
|
validate = 'Something went wrong';
|
||||||
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(key: Token.loginAccessTokenKey, value: token.accessToken);
|
await storage.write(
|
||||||
|
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());
|
||||||
@ -195,6 +197,7 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
checkBoxToggle(
|
checkBoxToggle(
|
||||||
CheckBoxEvent event,
|
CheckBoxEvent event,
|
||||||
Emitter<AuthState> emit,
|
Emitter<AuthState> emit,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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';
|
||||||
@ -15,6 +16,7 @@ import 'package:syncrow_web/pages/routines/models/device_functions.dart';
|
|||||||
import 'package:syncrow_web/pages/routines/models/routine_details_model.dart';
|
import 'package:syncrow_web/pages/routines/models/routine_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';
|
||||||
@ -64,7 +66,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
TriggerSwitchTabsEvent event,
|
TriggerSwitchTabsEvent event,
|
||||||
Emitter<RoutineState> emit,
|
Emitter<RoutineState> emit,
|
||||||
) {
|
) {
|
||||||
emit(state.copyWith(routineTab: event.isRoutineTab, createRoutineView: false));
|
emit(state.copyWith(
|
||||||
|
routineTab: event.isRoutineTab, createRoutineView: false));
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
if (event.isRoutineTab) {
|
if (event.isRoutineTab) {
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -90,8 +93,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 =
|
int index = updatedIfItems.indexWhere(
|
||||||
updatedIfItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
(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;
|
||||||
@ -100,18 +103,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.isTabToRun) {
|
if (event.isTabToRun) {
|
||||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
emit(state.copyWith(
|
||||||
|
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
emit(state.copyWith(
|
||||||
|
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
|
void _onAddToThenContainer(
|
||||||
|
AddToThenContainer event, Emitter<RoutineState> emit) {
|
||||||
final currentItems = List<Map<String, dynamic>>.from(state.thenItems);
|
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 =
|
int index = currentItems.indexWhere(
|
||||||
currentItems.indexWhere((map) => map['uniqueCustomId'] == event.item['uniqueCustomId']);
|
(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;
|
||||||
@ -122,7 +128,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
emit(state.copyWith(thenItems: currentItems));
|
emit(state.copyWith(thenItems: currentItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
void _onAddFunctionsToRoutine(
|
||||||
|
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||||
try {
|
try {
|
||||||
if (event.functions.isEmpty) return;
|
if (event.functions.isEmpty) return;
|
||||||
|
|
||||||
@ -157,7 +164,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
// currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
currentSelectedFunctions[event.uniqueCustomId] =
|
||||||
|
List.from(event.functions);
|
||||||
|
|
||||||
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -165,24 +173,30 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
|
Future<void> _onLoadScenes(
|
||||||
|
LoadScenes event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
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 == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
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 = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
scenes.addAll(await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
scenes.addAll(
|
||||||
|
await SceneApi.getScenes(spaceId, communityId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
scenes.addAll(await SceneApi.getScenes(
|
scenes.addAll(await SceneApi.getScenes(
|
||||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectUuid));
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -199,7 +213,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
|
Future<void> _onLoadAutomation(
|
||||||
|
LoadAutomation event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
List<ScenesModel> automations = [];
|
List<ScenesModel> automations = [];
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -207,17 +222,22 @@ 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 == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
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 = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
automations.addAll(await SceneApi.getAutomation(spaceId, communityId, projectId));
|
automations.addAll(
|
||||||
|
await SceneApi.getAutomation(spaceId, communityId, projectId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
automations.addAll(await SceneApi.getAutomation(
|
automations.addAll(await SceneApi.getAutomation(
|
||||||
createRoutineBloc.selectedSpaceId, createRoutineBloc.selectedCommunityId, projectId));
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
projectId));
|
||||||
}
|
}
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: automations,
|
automations: automations,
|
||||||
@ -233,14 +253,16 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onSearchRoutines(
|
||||||
|
SearchRoutines event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true, errorMessage: null));
|
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(AddSelectedIcon event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onAddSelectedIcon(
|
||||||
|
AddSelectedIcon event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(selectedIcon: event.icon));
|
emit(state.copyWith(selectedIcon: event.icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +276,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
return actions.last['deviceId'] == 'delay';
|
return actions.last['deviceId'] == 'delay';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateScene(
|
||||||
|
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)) {
|
||||||
@ -267,7 +290,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage: 'A delay condition cannot be the only or the last action',
|
errorMessage:
|
||||||
|
'A delay condition cannot be the only or the last action',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -335,15 +359,18 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
errorMessage: 'Something went wrong',
|
errorMessage: 'Something went wrong',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
|
String errorMessage = errorData;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Something went wrong',
|
errorMessage: errorMessage,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateAutomation(
|
||||||
|
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
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) {
|
||||||
@ -365,7 +392,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage: 'A delay condition cannot be the only or the last action',
|
errorMessage:
|
||||||
|
'A delay condition cannot be the only or the last action',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
|
CustomSnackBar.redSnackBar('Cannot have delay as the last action');
|
||||||
@ -456,7 +484,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
final result =
|
||||||
|
await SceneApi.createAutomation(createAutomationModel, projectUuid);
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadAutomation());
|
add(const LoadAutomation());
|
||||||
@ -468,26 +497,32 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Something went wrong');
|
CustomSnackBar.redSnackBar('Something went wrong');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
|
String errorMessage = errorData;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Something went wrong',
|
errorMessage: errorMessage,
|
||||||
));
|
));
|
||||||
CustomSnackBar.redSnackBar('Something went wrong');
|
CustomSnackBar.redSnackBar(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onRemoveDragCard(
|
||||||
|
RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||||
if (event.isFromThen) {
|
if (event.isFromThen) {
|
||||||
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
final selectedFunctions =
|
||||||
|
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
|
|
||||||
thenItems.removeAt(event.index);
|
thenItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
emit(state.copyWith(thenItems: thenItems, selectedFunctions: selectedFunctions));
|
emit(state.copyWith(
|
||||||
|
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 = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
final selectedFunctions =
|
||||||
|
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
|
||||||
|
|
||||||
ifItems.removeAt(event.index);
|
ifItems.removeAt(event.index);
|
||||||
selectedFunctions.remove(event.key);
|
selectedFunctions.remove(event.key);
|
||||||
@ -498,7 +533,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
isAutomation: false,
|
isAutomation: false,
|
||||||
isTabToRun: false));
|
isTabToRun: false));
|
||||||
} else {
|
} else {
|
||||||
emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
|
emit(state.copyWith(
|
||||||
|
ifItems: ifItems, selectedFunctions: selectedFunctions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -510,11 +546,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onEffectiveTimeEvent(
|
||||||
|
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onSetRoutineName(
|
||||||
|
SetRoutineName event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
routineName: event.name,
|
routineName: event.name,
|
||||||
));
|
));
|
||||||
@ -641,7 +679,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// return (thenItems, ifItems, currentFunctions);
|
// return (thenItems, ifItems, currentFunctions);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
|
Future<void> _onGetSceneDetails(
|
||||||
|
GetSceneDetails event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
@ -689,10 +728,12 @@ 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': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'deviceId':
|
||||||
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
|
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
? action.entityId
|
'uniqueCustomId':
|
||||||
: const Uuid().v4(),
|
action.type == 'automation' || action.actionExecutor == 'delay'
|
||||||
|
? action.entityId
|
||||||
|
: const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
: action.type == 'automation'
|
: action.type == 'automation'
|
||||||
@ -732,7 +773,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
|
||||||
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
} else if (action.executorProperty != null &&
|
||||||
|
action.actionExecutor != 'delay') {
|
||||||
final functions = matchingDevice?.functions ?? [];
|
final functions = matchingDevice?.functions ?? [];
|
||||||
final functionCode = action.executorProperty?.functionCode;
|
final functionCode = action.executorProperty?.functionCode;
|
||||||
for (DeviceFunction function in functions) {
|
for (DeviceFunction function in functions) {
|
||||||
@ -798,7 +840,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
|
FutureOr<void> _onResetRoutineState(
|
||||||
|
ResetRoutineState event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
ifItems: [],
|
ifItems: [],
|
||||||
thenItems: [],
|
thenItems: [],
|
||||||
@ -822,7 +865,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
createRoutineView: false));
|
createRoutineView: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _deleteScene(DeleteScene event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _deleteScene(
|
||||||
|
DeleteScene event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
final projectId = await ProjectManager.getProjectUUID() ?? '';
|
||||||
|
|
||||||
@ -831,7 +875,8 @@ 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], sceneId: state.sceneId ?? '');
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
|
sceneId: state.sceneId ?? '');
|
||||||
} else {
|
} else {
|
||||||
await SceneApi.deleteAutomation(
|
await SceneApi.deleteAutomation(
|
||||||
unitUuid: spaceBloc.state.selectedSpaces[0],
|
unitUuid: spaceBloc.state.selectedSpaces[0],
|
||||||
@ -854,11 +899,14 @@ 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));
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
|
String errorMessage = errorData;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Failed to delete scene',
|
errorMessage: errorMessage,
|
||||||
));
|
));
|
||||||
|
CustomSnackBar.redSnackBar(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -876,7 +924,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _fetchDevices(
|
||||||
|
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(isLoading: true));
|
emit(state.copyWith(isLoading: true));
|
||||||
try {
|
try {
|
||||||
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
final projectUuid = await ProjectManager.getProjectUUID() ?? '';
|
||||||
@ -885,17 +934,21 @@ 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 == '' && createRoutineBloc.selectedCommunityId == '') {
|
if (createRoutineBloc.selectedSpaceId == '' &&
|
||||||
|
createRoutineBloc.selectedCommunityId == '') {
|
||||||
for (var communityId in spaceBloc.state.selectedCommunities) {
|
for (var communityId in spaceBloc.state.selectedCommunities) {
|
||||||
List<String> spacesList = spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
List<String> spacesList =
|
||||||
|
spaceBloc.state.selectedCommunityAndSpaces[communityId] ?? [];
|
||||||
for (var spaceId in spacesList) {
|
for (var spaceId in spacesList) {
|
||||||
devices.addAll(
|
devices.addAll(await DevicesManagementApi()
|
||||||
await DevicesManagementApi().fetchDevices(communityId, spaceId, projectUuid));
|
.fetchDevices(communityId, spaceId, projectUuid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devices.addAll(await DevicesManagementApi().fetchDevices(
|
devices.addAll(await DevicesManagementApi().fetchDevices(
|
||||||
createRoutineBloc.selectedCommunityId, createRoutineBloc.selectedSpaceId, projectUuid));
|
createRoutineBloc.selectedCommunityId,
|
||||||
|
createRoutineBloc.selectedSpaceId,
|
||||||
|
projectUuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(isLoading: false, devices: devices));
|
emit(state.copyWith(isLoading: false, devices: devices));
|
||||||
@ -904,7 +957,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onUpdateScene(
|
||||||
|
UpdateScene event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
// if (_isFirstActionDelay(state.thenItems)) {
|
// if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -918,7 +972,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (_isLastActionDelay(state.thenItems)) {
|
if (_isLastActionDelay(state.thenItems)) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
errorMessage: 'A delay condition cannot be the only or the last action',
|
errorMessage:
|
||||||
|
'A delay condition cannot be the only or the last action',
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
@ -971,7 +1026,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
final result = await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
final result =
|
||||||
|
await SceneApi.updateScene(createSceneModel, state.sceneId ?? '');
|
||||||
if (result['success']) {
|
if (result['success']) {
|
||||||
add(ResetRoutineState());
|
add(ResetRoutineState());
|
||||||
add(const LoadScenes());
|
add(const LoadScenes());
|
||||||
@ -990,7 +1046,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
|
FutureOr<void> _onUpdateAutomation(
|
||||||
|
UpdateAutomation event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
if (state.routineName == null || state.routineName!.isEmpty) {
|
if (state.routineName == null || state.routineName!.isEmpty) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1114,10 +1171,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
errorMessage: result['message'],
|
errorMessage: result['message'],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on APIException catch (e) {
|
||||||
|
final errorData = e.message;
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
errorMessage: 'Something went wrong',
|
errorMessage: errorData,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1214,7 +1272,8 @@ 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': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
'deviceId':
|
||||||
|
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
|
||||||
'uniqueCustomId': const Uuid().v4(),
|
'uniqueCustomId': const Uuid().v4(),
|
||||||
'title': action.actionExecutor == 'delay'
|
'title': action.actionExecutor == 'delay'
|
||||||
? 'Delay'
|
? 'Delay'
|
||||||
@ -1249,7 +1308,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
updatedFunctions[uniqueCustomId] = [];
|
updatedFunctions[uniqueCustomId] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.executorProperty != null && action.actionExecutor != 'delay') {
|
if (action.executorProperty != null &&
|
||||||
|
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) {
|
||||||
@ -1291,10 +1351,14 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final ifItems = deviceIfCards.values.where((card) => card['type'] == 'condition').toList();
|
final ifItems = deviceIfCards.values
|
||||||
|
.where((card) => card['type'] == 'condition')
|
||||||
|
.toList();
|
||||||
final thenItems = deviceThenCards.values
|
final thenItems = deviceThenCards.values
|
||||||
.where((card) =>
|
.where((card) =>
|
||||||
card['type'] == 'action' || card['type'] == 'automation' || card['type'] == 'scene')
|
card['type'] == 'action' ||
|
||||||
|
card['type'] == 'automation' ||
|
||||||
|
card['type'] == 'scene')
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
@ -1316,7 +1380,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<RoutineState> emit) async {
|
Future<void> _onSceneTrigger(
|
||||||
|
SceneTrigger event, Emitter<RoutineState> emit) async {
|
||||||
emit(state.copyWith(loadingSceneId: event.sceneId));
|
emit(state.copyWith(loadingSceneId: event.sceneId));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -1358,24 +1423,29 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
final updatedAutomations = await SceneApi.getAutomationByUnitId(
|
final updatedAutomations = await SceneApi.getAutomationByUnitId(
|
||||||
event.automationStatusUpdate.spaceUuid, event.communityId, projectId);
|
event.automationStatusUpdate.spaceUuid,
|
||||||
|
event.communityId,
|
||||||
|
projectId);
|
||||||
|
|
||||||
// Remove from loading set safely
|
// Remove from loading set safely
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
automations: updatedAutomations,
|
automations: updatedAutomations,
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
final updatedLoadingIds = {...state.loadingAutomationIds!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..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!}..remove(event.automationId);
|
final updatedLoadingIds = {...state.loadingAutomationIds!}
|
||||||
|
..remove(event.automationId);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
loadingAutomationIds: updatedLoadingIds,
|
loadingAutomationIds: updatedLoadingIds,
|
||||||
errorMessage: 'Update error: ${e.toString()}',
|
errorMessage: 'Update error: ${e.toString()}',
|
||||||
|
@ -12,22 +12,53 @@ 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) {
|
||||||
return ToggleButtons(
|
final selectedIndex = _conditions.indexOf(currentCondition ?? "==");
|
||||||
onPressed: (index) => onChanged(_conditions[index]),
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
return Container(
|
||||||
selectedBorderColor: ColorsManager.primaryColorWithOpacity,
|
height: 30,
|
||||||
selectedColor: Colors.white,
|
width: MediaQuery.of(context).size.width * 0.1,
|
||||||
fillColor: ColorsManager.primaryColorWithOpacity,
|
decoration: BoxDecoration(
|
||||||
color: ColorsManager.primaryColorWithOpacity,
|
color: ColorsManager.softGray.withOpacity(0.5),
|
||||||
constraints: const BoxConstraints(
|
borderRadius: BorderRadius.circular(50),
|
||||||
minHeight: 40.0,
|
),
|
||||||
minWidth: 40.0,
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: List.generate(_conditions.length, (index) {
|
||||||
|
final isSelected = index == selectedIndex;
|
||||||
|
return Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => onChanged(_conditions[index]),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 180),
|
||||||
|
curve: Curves.ease,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isSelected ? ColorsManager.vividBlue : Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
_icons[index],
|
||||||
|
size: 20,
|
||||||
|
color: isSelected
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.blackColor,
|
||||||
|
weight: isSelected ? 700 : 500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
isSelected: _conditions.map((c) => c == (currentCondition ?? "==")).toList(),
|
|
||||||
children: _conditions.map((c) => Text(c)).toList(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,27 @@ class _EnergyClampDialogState extends State<EnergyClampDialog> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const DialogHeader('Energy Clamp Conditions'),
|
const DialogHeader('Energy Clamp Conditions'),
|
||||||
Expanded(child: _buildMainContent(context, state)),
|
Expanded(
|
||||||
|
child: Visibility(
|
||||||
|
visible: _functions.isNotEmpty,
|
||||||
|
replacement: SizedBox(
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'You Cant add\n the Power Clamp to Then Section',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: context.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: ColorsManager.red,
|
||||||
|
fontWeight: FontWeight.w400),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: _buildMainContent(context, state),
|
||||||
|
)),
|
||||||
_buildDialogFooter(context, state),
|
_buildDialogFooter(context, state),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -24,6 +24,9 @@ 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), () {});
|
||||||
|
|
||||||
@ -493,6 +496,20 @@ class SpaceTreeBloc extends Bloc<SpaceTreeEvent, SpaceTreeState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onAnalyticsClearAllSpaceTreeSelectionsEvent(
|
||||||
|
AnalyticsClearAllSpaceTreeSelectionsEvent event,
|
||||||
|
Emitter<SpaceTreeState> emit,
|
||||||
|
) async {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
selectedCommunities: [],
|
||||||
|
selectedCommunityAndSpaces: {},
|
||||||
|
selectedSpaces: [],
|
||||||
|
soldCheck: [],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
_timer.cancel();
|
_timer.cancel();
|
||||||
|
@ -112,3 +112,7 @@ class ClearCachedData extends SpaceTreeEvent {}
|
|||||||
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
|
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
|
||||||
const SpaceTreeClearSelectionEvent();
|
const SpaceTreeClearSelectionEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class AnalyticsClearAllSpaceTreeSelectionsEvent extends SpaceTreeEvent {
|
||||||
|
const AnalyticsClearAllSpaceTreeSelectionsEvent();
|
||||||
|
}
|
||||||
|
@ -48,7 +48,8 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(builder: (context, state) {
|
return BlocBuilder<SpaceTreeBloc, SpaceTreeState>(
|
||||||
|
builder: (context, state) {
|
||||||
final communities = state.searchQuery.isNotEmpty
|
final communities = state.searchQuery.isNotEmpty
|
||||||
? state.filteredCommunity
|
? state.filteredCommunity
|
||||||
: state.communityList;
|
: state.communityList;
|
||||||
@ -132,104 +133,118 @@ class _SpaceTreeViewState extends State<SpaceTreeView> {
|
|||||||
)
|
)
|
||||||
else
|
else
|
||||||
CustomSearchBar(
|
CustomSearchBar(
|
||||||
onSearchChanged: (query) => context.read<SpaceTreeBloc>().add(
|
onSearchChanged: (query) =>
|
||||||
SearchQueryEvent(query),
|
context.read<SpaceTreeBloc>().add(
|
||||||
),
|
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())
|
||||||
: SidebarCommunitiesList(
|
: communities.isEmpty
|
||||||
onScrollToEnd: () {
|
? Center(
|
||||||
if (!state.paginationIsLoading) {
|
child: Text(
|
||||||
context.read<SpaceTreeBloc>().add(
|
'No communities found',
|
||||||
PaginationEvent(
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
state.paginationModel,
|
color: ColorsManager.textGray,
|
||||||
state.communityList,
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollController: _scrollController,
|
|
||||||
communities: communities,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return CustomExpansionTileSpaceTree(
|
|
||||||
title: communities[index].name,
|
|
||||||
isSelected: state.selectedCommunities
|
|
||||||
.contains(communities[index].uuid),
|
|
||||||
isSoldCheck: state.selectedCommunities
|
|
||||||
.contains(communities[index].uuid),
|
|
||||||
onExpansionChanged: () =>
|
|
||||||
context.read<SpaceTreeBloc>().add(
|
|
||||||
OnCommunityExpanded(
|
|
||||||
communities[index].uuid,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isExpanded: state.expandedCommunities.contains(
|
|
||||||
communities[index].uuid,
|
|
||||||
),
|
),
|
||||||
onItemSelected: () {
|
)
|
||||||
widget.onSelect();
|
: SidebarCommunitiesList(
|
||||||
context.read<SpaceTreeBloc>().add(
|
onScrollToEnd: () {
|
||||||
OnCommunitySelected(
|
if (!state.paginationIsLoading) {
|
||||||
communities[index].uuid,
|
context.read<SpaceTreeBloc>().add(
|
||||||
communities[index].spaces,
|
PaginationEvent(
|
||||||
),
|
state.paginationModel,
|
||||||
);
|
state.communityList,
|
||||||
},
|
),
|
||||||
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) {
|
scrollController: _scrollController,
|
||||||
return;
|
communities: communities,
|
||||||
}
|
itemBuilder: (context, index) {
|
||||||
widget.onSelect();
|
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(
|
||||||
OnSpaceSelected(
|
OnCommunityExpanded(
|
||||||
communities[index],
|
communities[index].uuid,
|
||||||
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],
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onExpansionChanged: () =>
|
).toList(),
|
||||||
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) const CircularProgressIndicator(),
|
if (state.paginationIsLoading)
|
||||||
|
const CircularProgressIndicator(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
10
lib/services/api/api_exception.dart
Normal file
10
lib/services/api/api_exception.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class APIException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
APIException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,26 @@
|
|||||||
|
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 {
|
||||||
final response = await HTTPService().post(
|
try {
|
||||||
path: ApiEndpoints.login,
|
final response = await HTTPService().post(
|
||||||
body: model.toJson(),
|
path: ApiEndpoints.login,
|
||||||
showServerMessage: true,
|
body: model.toJson(),
|
||||||
expectedResponseModel: (json) {
|
showServerMessage: true,
|
||||||
return Token.fromJson(json['data']);
|
expectedResponseModel: (json) {
|
||||||
});
|
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({
|
||||||
@ -20,12 +28,18 @@ class AuthenticationAPI {
|
|||||||
required var password,
|
required var password,
|
||||||
required var otpCode,
|
required var otpCode,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await HTTPService().post(
|
try {
|
||||||
path: ApiEndpoints.forgetPassword,
|
final response = await HTTPService().post(
|
||||||
body: {"email": email, "password": password, "otpCode": otpCode},
|
path: ApiEndpoints.forgetPassword,
|
||||||
showServerMessage: true,
|
body: {"email": email, "password": password, "otpCode": otpCode},
|
||||||
expectedResponseModel: (json) {});
|
showServerMessage: true,
|
||||||
return response;
|
expectedResponseModel: (json) {});
|
||||||
|
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 {
|
||||||
@ -39,19 +53,26 @@ class AuthenticationAPI {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future verifyOtp({required String email, required String otpCode}) async {
|
static Future verifyOtp(
|
||||||
final response = await HTTPService().post(
|
{required String email, required String otpCode}) async {
|
||||||
path: ApiEndpoints.verifyOtp,
|
try {
|
||||||
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
|
final response = await HTTPService().post(
|
||||||
showServerMessage: true,
|
path: ApiEndpoints.verifyOtp,
|
||||||
expectedResponseModel: (json) {
|
body: {"email": email, "type": "PASSWORD", "otpCode": otpCode},
|
||||||
if (json['message'] == 'Otp Verified Successfully') {
|
showServerMessage: true,
|
||||||
return true;
|
expectedResponseModel: (json) {
|
||||||
} else {
|
if (json['message'] == 'Otp Verified Successfully') {
|
||||||
return false;
|
return true;
|
||||||
}
|
} 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 {
|
||||||
@ -59,7 +80,9 @@ class AuthenticationAPI {
|
|||||||
path: ApiEndpoints.getRegion,
|
path: ApiEndpoints.getRegion,
|
||||||
showServerMessage: true,
|
showServerMessage: true,
|
||||||
expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
return (json as List).map((zone) => RegionModel.fromJson(zone)).toList();
|
return (json as List)
|
||||||
|
.map((zone) => RegionModel.fromJson(zone))
|
||||||
|
.toList();
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@ -5,6 +6,7 @@ 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';
|
||||||
|
|
||||||
@ -26,9 +28,10 @@ class SceneApi {
|
|||||||
);
|
);
|
||||||
debugPrint('create scene response: $response');
|
debugPrint('create scene response: $response');
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
debugPrint(e.toString());
|
String errorMessage =
|
||||||
rethrow;
|
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
||||||
|
throw APIException(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,9 +51,10 @@ class SceneApi {
|
|||||||
);
|
);
|
||||||
debugPrint('create automation response: $response');
|
debugPrint('create automation response: $response');
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
debugPrint(e.toString());
|
String errorMessage =
|
||||||
rethrow;
|
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
||||||
|
throw APIException(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,8 +169,10 @@ class SceneApi {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
rethrow;
|
String errorMessage =
|
||||||
|
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
||||||
|
throw APIException(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,8 +191,10 @@ class SceneApi {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
rethrow;
|
String errorMessage =
|
||||||
|
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
||||||
|
throw APIException(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,8 +225,10 @@ class SceneApi {
|
|||||||
expectedResponseModel: (json) => json['statusCode'] == 200,
|
expectedResponseModel: (json) => json['statusCode'] == 200,
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
rethrow;
|
String errorMessage =
|
||||||
|
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
||||||
|
throw APIException(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,8 +246,10 @@ class SceneApi {
|
|||||||
expectedResponseModel: (json) => json['statusCode'] == 200,
|
expectedResponseModel: (json) => json['statusCode'] == 200,
|
||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
rethrow;
|
String errorMessage =
|
||||||
|
e.response?.data['error']['message'][0] ?? 'something went wrong';
|
||||||
|
throw APIException(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user