Skip to content

Commit

Permalink
Merge pull request #5 from moevm/front/features
Browse files Browse the repository at this point in the history
Front/features
  • Loading branch information
lenanaidyonova authored Nov 10, 2024
2 parents eec177e + df6a77b commit 59179bf
Show file tree
Hide file tree
Showing 71 changed files with 5,650 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// lib/features/charts/domain/entities/chart_config.dart

enum AggregationType {
min,
max,
mean,
}

enum TimeRange {
day,
halfDay,
hour,
fifteenMinutes,
custom,
}

String getInfluxString(TimeRange range) {
switch (range) {
case TimeRange.day:
return '-1d';
case TimeRange.halfDay:
return '-12h';
case TimeRange.hour:
return '-1h';
case TimeRange.fifteenMinutes:
return '-15m';
case TimeRange.custom:
return '-5s'; // значение по умолчанию
}
}


class ChartConfiguration {
final bool showWarnings;
final bool showAnomalies;
final Set<AggregationType> selectedAggregations;
final TimeRange timeRange;
final DateTime customStartDate;
final DateTime customEndDate;
final Duration pointsDistance;
final bool realTime;

ChartConfiguration({
this.showWarnings = false,
this.showAnomalies = false,
this.selectedAggregations = const {AggregationType.mean},
this.timeRange = TimeRange.hour,
DateTime? customStartDate,
DateTime? customEndDate,
this.pointsDistance = const Duration(minutes: 1),
this.realTime = false,
}) :
customStartDate = customStartDate ?? DateTime.now().subtract(const Duration(days: 7)),
customEndDate = customEndDate ?? DateTime.now();

ChartConfiguration copyWith({
bool? showWarnings,
bool? showAnomalies,
Set<AggregationType>? selectedAggregations,
TimeRange? timeRange,
DateTime? customStartDate,
DateTime? customEndDate,
Duration? pointsDistance,
bool? realTime,
}) {
return ChartConfiguration(
showWarnings: showWarnings ?? this.showWarnings,
showAnomalies: showAnomalies ?? this.showAnomalies,
selectedAggregations: selectedAggregations ?? this.selectedAggregations,
timeRange: timeRange ?? this.timeRange,
customStartDate: customStartDate ?? this.customStartDate,
customEndDate: customEndDate ?? this.customEndDate,
pointsDistance: pointsDistance ?? this.pointsDistance,
realTime: realTime ?? this.realTime,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import 'package:clean_architecture/features/charts/domain/entities/chart_config.dart';
import 'package:clean_architecture/shared/data/models/minichart_data_model.dart';
import 'package:dartz/dartz.dart';

import '../../../../core/error/failure.dart';
import '../../../../core/types/influx_formater.dart';
import '../../../../core/usecases/usecase.dart';
import '../../../../shared/domain/repositories/influxdb_repository.dart';

class GetChartsDataUseCase implements UseCase<MiniChartDataModel, GetChartsDataUseCaseParams> {
final InfluxdbRepository influxRepository;

GetChartsDataUseCase(this.influxRepository);

@override
Future<Either<Failure, MiniChartDataModel>> call(GetChartsDataUseCaseParams params) async {
final res = await influxRepository.getLiveChartsData(
params.topics,
params.getInterval(),
params.getStart(),
params.getEnd());
return res.fold((f) => Left(f),
(data) => Right(data));
}
}

class GetChartsDataUseCaseParams{
final TimeRange period;
final DateTime start;
final DateTime end;
final Duration interval;

final Map<String, dynamic> topics;
GetChartsDataUseCaseParams({
required this.period,
required this.start,
required this.end,
required this.interval,
required this.topics
});

String getStart() {
if(period == TimeRange.custom) return formatDateTimeForInflux(start);
return getInfluxString(period);
}

String getEnd() {
if(period == TimeRange.custom) return formatDateTimeForInflux(end);
return "now()";
}

String getInterval() {
return '${interval.inSeconds}s';
}
}
254 changes: 254 additions & 0 deletions flutter_front/lib/features/charts/presentation/bloc/charts_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import 'dart:async';

import 'package:clean_architecture/features/charts/domain/usecases/get_charts_data_use_case.dart';
import 'package:clean_architecture/shared/domain/entities/equipment/equipment_entity.dart';
import 'package:clean_architecture/shared/domain/usecases/get_multiple_chosen_equipment.dart';
import 'package:clean_architecture/shared/domain/usecases/set_multiple_chosen_equipment.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../../../core/types/optional.dart';
import '../../../../shared/data/models/minichart_data_model.dart';
import '../../../../shared/domain/entities/equipment/equipment_list_entity.dart';
import '../../../../shared/domain/usecases/get_equipment_usecase.dart';
import '../../../../shared/domain/usecases/no_params.dart';
import '../../../settings/presentation/widgets/settings_message.dart';
import '../../domain/entities/chart_config.dart';

part 'charts_event.dart';
part 'charts_state.dart';

class ChartsBloc extends Bloc<ChartsEvent, ChartsState> {
final GetEquipmentUseCase getEquipmentList;
final GetMultipleChosenEquipmentUseCase getSelectedEquipment;
final SetMultipleChosenEquipmentUseCase saveSelectedEquipment;
final GetChartsDataUseCase getChartsDataUseCase;
Timer? _realTimeTimer;

ChartsBloc({
required this.getEquipmentList,
required this.getSelectedEquipment,
required this.saveSelectedEquipment,
required this.getChartsDataUseCase,
}) : super(ChartsInitial()) {
on<InitializeCharts>(_onInitializeCharts);
on<SaveSelectedEquipmentEvent>(_onSaveSelectedEquipment);
on<SaveSelectedParametersEvent>(_onSaveSelectedParameters);
on<UpdateChartConfigurationEvent>(_onUpdateConfiguration);
on<FetchChartsDataEvent>(_onFetchChartsData);
on<FetchRealTimeDataEvent>(_onFetchRealTimeData);
}

@override
Future<void> close() {
_realTimeTimer?.cancel();
return super.close();
}


Future<void> _onInitializeCharts(
InitializeCharts event,
Emitter<ChartsState> emit,
) async {
emit(ChartsLoading());

final selectedEquipmentResult = await getSelectedEquipment(NoParams());
final equipmentListResult = await getEquipmentList(NoParams());

selectedEquipmentResult.fold(
(failure) => emit(ChartsError("Не удалось загрузить выбранное оборудование")),
(selectedEquipment) {
equipmentListResult.fold(
(failure) =>
emit(ChartsError("Не удалось загрузить список оборудования")),
(equipmentList) => emit(ChartsLoaded(
equipmentList: equipmentList,
selectedEquipmentKeys: selectedEquipment,
selectedParameterKeys: null, configuration: ChartConfiguration(),
)),
);
},
);
}

Future<void> _onSaveSelectedEquipment(
SaveSelectedEquipmentEvent event,
Emitter<ChartsState> emit,
) async {
if (state is ChartsLoaded) {
final currentState = state as ChartsLoaded;
emit(ChartsSavingEquipment(currentState));


final result = await saveSelectedEquipment(event.equipmentKeys);

result.fold(
(failure) => emit(currentState.copyWith(
message: BottomMessage(
"Не удалось сохранить выбранное оборудование",
isError: true,
),
)),
(_) => emit(currentState.copyWith(
selectedEquipmentKeys: Optional(event.equipmentKeys),
selectedParameterKeys: const Optional(null), // Сбрасываем параметры при смене оборудования
)),
);
}
}

void _onSaveSelectedParameters(
SaveSelectedParametersEvent event,
Emitter<ChartsState> emit,
) {
if (state is ChartsLoaded) {
final currentState = state as ChartsLoaded;
emit(currentState.copyWith(
selectedParameterKeys: Optional(event.parameterKeys),
));
}
}

void _onUpdateConfiguration(
UpdateChartConfigurationEvent event,
Emitter<ChartsState> emit,
) {
if (state is ChartsLoaded) {
final currentState = state as ChartsLoaded;

_realTimeTimer?.cancel();

if (event.configuration.realTime) {
_realTimeTimer = Timer.periodic(
event.configuration.pointsDistance,
(_) => add(FetchRealTimeDataEvent()),
);
}

emit(currentState.copyWith(configuration: event.configuration));
}
}


Future<void> _onFetchChartsData(
FetchChartsDataEvent event,
Emitter<ChartsState> emit,
) async {
if (state is ChartsLoaded) {
final currentState = state as ChartsLoaded;
emit(ChartsFetchingData(currentState));

final topics = _getFetchTopics(currentState);
final now = DateTime.now();
final params = GetChartsDataUseCaseParams(
period: currentState.configuration.timeRange,
start: currentState.configuration.customStartDate ?? now,
end: currentState.configuration.customEndDate ?? now,
interval: currentState.configuration.pointsDistance,
topics: topics,
);

final result = await getChartsDataUseCase(params);

result.fold(
(failure) => emit(currentState.copyWith(
message: BottomMessage(
"Не удалось получить данные графиков",
isError: true,
),
)),
(data) {
emit(currentState.copyWith(
chartData: data,
message: BottomMessage("Данные успешно получены"),
));
},
);
}
}

Future<void> _onFetchRealTimeData(
FetchRealTimeDataEvent event,
Emitter<ChartsState> emit,
) async {
if (state is ChartsLoaded) {
final currentState = state as ChartsLoaded;

final topics = _getFetchTopics(currentState);
final now = DateTime.now();
final params = GetChartsDataUseCaseParams(
period: TimeRange.custom,
start: now.subtract(currentState.configuration.pointsDistance),
end: now,
interval: currentState.configuration.pointsDistance,
topics: topics,
);

final result = await getChartsDataUseCase(params);

result.fold(
(failure) => emit(currentState.copyWith(
message: BottomMessage(
"Не удалось получить данные в реальном времени",
isError: true,
),
)),
(newData) {
final updatedChartData = _updateRealTimeData(currentState.chartData, newData);
emit(currentState.copyWith(chartData: updatedChartData));
},
);
}
}

MiniChartDataModel? _updateRealTimeData(
MiniChartDataModel? currentData,
MiniChartDataModel newData,
) {
if (currentData == null) return newData;

Map<String, Map<String, Map<String, List<ChartDataPoint>>>> updatedData = {};

currentData.data.forEach((equipKey, equipValue) {
updatedData[equipKey] = {};

equipValue.forEach((paramKey, paramValue) {
updatedData[equipKey]![paramKey] = {};

paramValue.forEach((subParamKey, points) {
if (points != null) {
var newPoints = List<ChartDataPoint>.from(points);

// Добавляем новую точку и удаляем первую
if (newData.data[equipKey]?[paramKey]?[subParamKey]?.isNotEmpty ?? false) {
newPoints.removeAt(0);
newPoints.add(newData.data[equipKey]![paramKey]![subParamKey]!.first);
}

updatedData[equipKey]![paramKey]![subParamKey] = newPoints;
}
});
});
});

return MiniChartDataModel(updatedData);
}

Map<String, dynamic> _getFetchTopics(ChartsLoaded state){
Map<String, dynamic> topics = {};
if(state.selectedEquipmentKeys != null && state.selectedParameterKeys != null) {
for (String equipKey in state.selectedEquipmentKeys!) {
EquipmentEntity equipment = state.equipmentList.equipment.firstWhere((e) => e.key==equipKey);
topics[equipKey] = {};
for(String paramKey in state.selectedParameterKeys!){
topics[equipKey][paramKey] = [];
if(equipment.parameters[paramKey] != null) {
for (var topic in equipment.parameters[paramKey]!.subparameters.entries) {
topics[equipKey][paramKey].add(topic.key);
}
}
}
}
}
return topics;
}
}
Loading

0 comments on commit 59179bf

Please sign in to comment.