generated from moevm/nsql-clean-tempate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from moevm/front/features
Front/features
- Loading branch information
Showing
71 changed files
with
5,650 additions
and
0 deletions.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
flutter_front/lib/features/charts/domain/entities/chart_config.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
flutter_front/lib/features/charts/domain/usecases/get_charts_data_use_case.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
254
flutter_front/lib/features/charts/presentation/bloc/charts_bloc.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
Oops, something went wrong.