Skip to content

Commit

Permalink
Allow exporting current view to CSV
Browse files Browse the repository at this point in the history
  • Loading branch information
ThexXTURBOXx committed Sep 1, 2022
1 parent fca351f commit 8736655
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 84 deletions.
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ linter:
- avoid_unnecessary_containers
- avoid_unused_constructor_parameters
- avoid_void_async
- avoid_web_libraries_in_flutter
# - avoid_web_libraries_in_flutter
- await_only_futures
- camel_case_extensions
- camel_case_types
Expand Down
53 changes: 32 additions & 21 deletions lib/bloc/cubits/data_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:powasys_frontend/bloc/repos/data_repo.dart';
import 'package:powasys_frontend/bloc/states.dart';
import 'package:powasys_frontend/data/fl_spot.dart';
import 'package:powasys_frontend/data/trend.dart';
import 'package:powasys_frontend/util/hex_color.dart';
import 'package:tuple/tuple.dart';

class DataCubit extends Cubit<DataState> {
final DataRepo _dataRepo;

DataCubit(this._dataRepo) : super(const DataState(PowaSysState.notFetched));
DataCubit(this._dataRepo) : super(const DataState(DataFetchState.notFetched));

Future<void> fetchData({
required List<int> disabledPowadors,
required Trend currentTrend,
required int minDiv,
}) async {
try {
emit(state.copyWith(state: PowaSysState.fetching));
emit(state.copyWith(state: DataFetchState.fetching));

final powadors = <int, Tuple2<String, Color>>{};
for (final entry in await _dataRepo.getPowas()) {
Expand All @@ -45,47 +45,58 @@ class DataCubit extends Cubit<DataState> {

final averages = <int, Map<Trend, double>>{};
for (final entry in parsed24h['averages']) {
averages[int.parse(entry['powadorId'].toString())] =
Trend.values.asMap().map(
(id, trend) =>
MapEntry(trend, double.parse(entry[trend.id].toString())),
);
averages[int.parse(entry['powadorId'].toString())] = Trend.values
.where((t) => t != Trend.state)
.toList(growable: false)
.asMap()
.map(
(id, trend) =>
MapEntry(trend, double.parse(entry[trend.id].toString())),
);
}

final max = <int, Map<Trend, double>>{};
for (final entry in parsed24h['max']) {
max[int.parse(entry['powadorId'].toString())] =
Trend.values.asMap().map(
(id, trend) =>
MapEntry(trend, double.parse(entry[trend.id].toString())),
);
max[int.parse(entry['powadorId'].toString())] = Trend.values
.where((t) => t != Trend.state)
.toList(growable: false)
.asMap()
.map(
(id, trend) =>
MapEntry(trend, double.parse(entry[trend.id].toString())),
);
}

var minVal = 0.0;
var maxVal = 0.0;
final data = Map<int, List<FlSpot>>.fromEntries(
final data = Map<int, List<PowaSpot>>.fromEntries(
powadors.keys.map((e) => MapEntry(e, [])),
);
for (final entry in parsed24h['data']) {
final powaId = int.parse(entry['powadorId'].toString());
if (!disabledPowadors.contains(powaId)) {
final value = double.parse(entry[currentTrend.id].toString());
final values = {
for (var t in Trend.values)
t: entry[t.id] == null
? null
: double.parse(entry[t.id].toString())
};
final value = values[currentTrend]!;
minVal = value < minVal ? value : minVal;
maxVal = value > maxVal ? value : maxVal;
data[powaId]!.add(
FlSpot(
DateTime.parse(entry['time'].toString())
.millisecondsSinceEpoch
.toDouble(),
PowaSpot.fromDateTime(
DateTime.parse(entry['time'].toString()),
value,
values,
),
);
}
}

emit(
state.copyWith(
state: PowaSysState.fetchedData,
state: DataFetchState.fetchedData,
powadors: powadors,
minVal: minVal,
maxVal: maxVal,
Expand All @@ -96,7 +107,7 @@ class DataCubit extends Cubit<DataState> {
),
);
} catch (e) {
emit(state.copyWith(state: PowaSysState.fetchError));
emit(state.copyWith(state: DataFetchState.fetchError, ex: e));
}
}
}
52 changes: 52 additions & 0 deletions lib/bloc/cubits/export_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// ignore_for_file: missing_whitespace_between_adjacent_strings
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:powasys_frontend/bloc/states.dart';
import 'package:powasys_frontend/constants.dart';
import 'package:powasys_frontend/data/fl_spot.dart';
import 'package:powasys_frontend/data/trend.dart';
import 'package:tuple/tuple.dart';

class ExportCubit extends Cubit<ExportState> {
ExportCubit() : super(const ExportState(ExportGenState.notStarted));

Future<void> exportData(Map<int, List<PowaSpot>> data) async {
try {
emit(state.copyWith(state: ExportGenState.exporting));

var toExport = 'Powador ID;Time;State;Generator Voltage;'
'Generator Current;Generator Power;Net Voltage;Net Current;Net Power;'
'Temperature\n';

final entries = data.entries
.expand((e) => e.value.map((f) => Tuple2(f, e.key)))
.toList(growable: false);
entries.sort((a, b) {
final comp = a.item1.compare(b.item1);
return comp != 0 ? comp : a.item2 - b.item2;
});
toExport += entries
.map(
(e) => '${e.item2};'
'${e.item1.time};'
'${e.item1.values[Trend.state] as int?};'
'${decimalFormatOne.format(e.item1.values[Trend.genVoltage])};'
'${decimalFormatTwo.format(e.item1.values[Trend.genCurrent])};'
'${e.item1.values[Trend.genPower] as int?};'
'${decimalFormatOne.format(e.item1.values[Trend.netVoltage])};'
'${decimalFormatTwo.format(e.item1.values[Trend.netCurrent])};'
'${e.item1.values[Trend.netPower] as int?};'
'${e.item1.values[Trend.temperature] as int?}',
)
.join('\n');

emit(
state.copyWith(
state: ExportGenState.exported,
toExport: toExport,
),
);
} catch (e) {
emit(state.copyWith(state: ExportGenState.exportError, ex: e));
}
}
}
65 changes: 54 additions & 11 deletions lib/bloc/states.dart
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:powasys_frontend/data/fl_spot.dart';
import 'package:powasys_frontend/data/trend.dart';
import 'package:tuple/tuple.dart';

class BlocState {
final PowaSysState state;
class BlocState<State> {
final State state;
final dynamic ex;

const BlocState(this.state);
const BlocState(
this.state, {
this.ex,
});
}

class DataState extends BlocState {
class DataState extends BlocState<DataFetchState> {
final double minVal;
final double maxVal;
final Map<int, Tuple2<String, Color>> powadors;
final Map<int, Tuple2<DateTime, Map<Trend, double>>> latest;
final Map<int, Map<Trend, double>> averages;
final Map<int, Map<Trend, double>> max;
final Map<int, List<FlSpot>> data;
final Map<int, List<PowaSpot>> data;

const DataState(
super.state, {
super.ex,
this.minVal = 0,
this.maxVal = 0,
this.powadors = const {},
Expand All @@ -30,17 +35,19 @@ class DataState extends BlocState {
});

DataState copyWith({
PowaSysState? state,
DataFetchState? state,
dynamic ex,
double? minVal,
double? maxVal,
Map<int, Tuple2<String, Color>>? powadors,
Map<int, Tuple2<DateTime, Map<Trend, double>>>? latest,
Map<int, Map<Trend, double>>? averages,
Map<int, Map<Trend, double>>? max,
Map<int, List<FlSpot>>? data,
Map<int, List<PowaSpot>>? data,
}) =>
DataState(
state ?? this.state,
state ?? super.state,
ex: ex ?? super.ex,
minVal: minVal ?? this.minVal,
maxVal: maxVal ?? this.maxVal,
powadors: powadors ?? this.powadors,
Expand All @@ -51,7 +58,7 @@ class DataState extends BlocState {
);
}

enum PowaSysState {
enum DataFetchState {
notFetched(),
fetching(),
fetchedData(finished: true),
Expand All @@ -60,7 +67,43 @@ enum PowaSysState {
final bool finished;
final bool errored;

const PowaSysState({
const DataFetchState({
this.finished = false,
this.errored = false,
});
}

class ExportState extends BlocState<ExportGenState> {
final String toExport;

const ExportState(
super.state, {
super.ex,
this.toExport = '',
});

ExportState copyWith({
ExportGenState? state,
dynamic ex,
String? toExport,
}) =>
ExportState(
state ?? super.state,
ex: ex ?? super.ex,
toExport: toExport ?? this.toExport,
);
}

enum ExportGenState {
notStarted(),
exporting(),
exported(finished: true),
exportError(finished: true, errored: true);

final bool finished;
final bool errored;

const ExportGenState({
this.finished = false,
this.errored = false,
});
Expand Down
3 changes: 3 additions & 0 deletions lib/config/locale_config.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart';
import 'package:intl/intl.dart';
import 'package:powasys_frontend/l10n/lang_chooser.dart';

const currentLocaleKey = 'locale.current';
Expand All @@ -12,6 +13,7 @@ class LocaleSettings extends ChangeNotifier {
LocaleSettings(this._box) {
if (_box.containsKey(currentLocaleKey)) {
_locale = _box.get(currentLocaleKey) as String;
Intl.defaultLocale = _locale;
} else {
_box.put(currentLocaleKey, _locale);
}
Expand All @@ -26,6 +28,7 @@ class LocaleSettings extends ChangeNotifier {
void setLocale(String locale) {
_locale = locale;
_box.put(currentLocaleKey, _locale);
Intl.defaultLocale = _locale;
notifyListeners();
}
}
5 changes: 5 additions & 0 deletions lib/constants.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:powasys_frontend/data/trend.dart';

Expand All @@ -10,6 +11,10 @@ String get apiEndpoint24h => dotenv.env['APIEndpoint24h']!;

late PackageInfo packageInfo;

final decimalFormatOne = NumberFormat('##0.0');

final decimalFormatTwo = NumberFormat('##0.00');

// Cached Settings which should be gone after reloading
List<int> disabledPowadors = [];
Trend currentTrend = Trend.netPower;
Expand Down
18 changes: 18 additions & 0 deletions lib/data/fl_spot.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:powasys_frontend/data/trend.dart';
import 'package:powasys_frontend/util/math.dart';

class PowaSpot extends FlSpot {
final DateTime time;
final Map<Trend, double?> values;

PowaSpot(super.x, super.y, this.time, this.values);

PowaSpot.fromDateTime(DateTime time, double y, Map<Trend, double?> values)
: this(time.millisecondsSinceEpoch.toDouble(), y, time, values);

int compare(PowaSpot other) {
final comp = signum(x - other.x);
return comp != 0 ? comp : signum(y - other.y);
}
}
5 changes: 5 additions & 0 deletions lib/data/trend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
import 'package:powasys_frontend/generated/l10n.dart';

enum Trend {
state(id: 'state'),
genVoltage(id: 'genVoltage'),
genCurrent(id: 'genCurrent'),
genPower(id: 'genPower'),
Expand All @@ -17,6 +18,8 @@ enum Trend {

String name(BuildContext context) {
switch (this) {
case Trend.state:
return 'State'; // TODO(Nico): I18n!
case Trend.genVoltage:
return S.of(context).genVoltage;
case Trend.genCurrent:
Expand All @@ -36,6 +39,8 @@ enum Trend {

String unit(BuildContext context) {
switch (this) {
case Trend.state:
return '';
case Trend.genVoltage:
return S.of(context).voltage_unit;
case Trend.genCurrent:
Expand Down
4 changes: 4 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:powasys_frontend/bloc/cubits/data_cubit.dart';
import 'package:powasys_frontend/bloc/cubits/export_cubit.dart';
import 'package:powasys_frontend/bloc/repos/data_repo.dart';
import 'package:powasys_frontend/config/config.dart';
import 'package:powasys_frontend/constants.dart';
Expand Down Expand Up @@ -49,6 +50,9 @@ class _PowaSysFrontendWidgetState extends State<PowaSysFrontendWidget> {
BlocProvider<DataCubit>(
create: (context) => DataCubit(context.read<DataRepo>()),
),
BlocProvider<ExportCubit>(
create: (context) => ExportCubit(),
),
],
child: MaterialApp(
onGenerateTitle: (context) => S.of(context).app_name,
Expand Down
Loading

0 comments on commit 8736655

Please sign in to comment.