Commit 6b94ab54 authored by Administrator's avatar Administrator

moving calculations into it's own job instead of doing it on demand

parent 54e4ea34
......@@ -146,199 +146,34 @@ class Activity extends ChangeNotifier {
return _laps;
}
Future<double> get avgPower async {
if (db.avgPower == null) {
List<Event> records = await this.records;
db.avgPower = Lap.calculateAveragePower(records: records);
await db.save();
notifyListeners();
}
return db.avgPower;
}
Future<double> get sdevPower async {
if (db.sdevPower == null) {
List<Event> records = await this.records;
db.sdevPower = Lap.calculateSdevPower(records: records);
await db.save();
notifyListeners();
}
return db.sdevPower;
}
Future<int> get minPower async {
if (db.minPower == null) {
List<Event> records = await this.records;
db.minPower = Lap.calculateMinPower(records: records);
await db.save();
notifyListeners();
}
return db.minPower;
}
Future<int> get maxPower async {
if (db.maxPower == null) {
List<Event> records = await this.records;
db.maxPower = Lap.calculateMaxPower(records: records);
await db.save();
notifyListeners();
}
return db.maxPower;
}
Future<double> get avgSpeed async {
if (db.avgSpeed == null || db.avgSpeed == 0) {
List<Event> records = await this.records;
db.avgSpeed = Lap.calculateAverageSpeed(records: records);
await db.save();
notifyListeners();
}
return db.avgGroundTime;
}
recalculateAverages() async{
var records = await this.records;
db.avgPower = Lap.calculateAveragePower(records: records);
db.sdevPower = Lap.calculateSdevPower(records: records);
db.minPower = Lap.calculateMinPower(records: records);
db.maxPower = Lap.calculateMaxPower(records: records);
db.avgSpeed = Lap.calculateAverageSpeed(records: records);
db.avgGroundTime = Lap.calculateAverageGroundTime(records: records);
db.sdevGroundTime = Lap.calculateSdevGroundTime(records: records);
db.avgVerticalOscillation =
Lap.calculateAverageVerticalOscillation(records: records);
db.sdevVerticalOscillation =
Lap.calculateSdevVerticalOscillation(records: records);
db.avgStrydCadence = Lap.calculateAverageStrydCadence(records: records);
db.sdevStrydCadence = Lap.calculateSdevStrydCadence(records: records);
db.avgLegSpringStiffness =
Lap.calculateAverageLegSpringStiffness(records: records);
db.sdevLegSpringStiffness =
Lap.calculateSdevLegSpringStiffness(records: records);
db.avgFormPower = Lap.calculateAverageFormPower(records: records);
db.sdevFormPower = Lap.calculateSdevFormPower(records: records);
db.avgPowerRatio = Lap.calculateAveragePowerRatio(records: records);
db.sdevPowerRatio = Lap.calculateSdevPowerRatio(records: records);
db.avgStrideRatio = Lap.calculateAverageStrideRatio(records: records);
db.sdevStrideRatio = Lap.calculateSdevStrideRatio(records: records);
Future<double> get avgGroundTime async {
if (db.avgGroundTime == null) {
List<Event> records = await this.records;
db.avgGroundTime = Lap.calculateAverageGroundTime(records: records);
await db.save();
notifyListeners();
}
return db.avgGroundTime;
}
Future<double> get sdevGroundTime async {
if (db.sdevGroundTime == null) {
List<Event> records = await this.records;
db.sdevGroundTime = Lap.calculateSdevGroundTime(records: records);
await db.save();
notifyListeners();
}
return db.sdevGroundTime;
}
Future<double> get avgVerticalOscillation async {
if (db.avgVerticalOscillation == null ||
db.avgVerticalOscillation == 6553.5) {
List<Event> records = await this.records;
db.avgVerticalOscillation =
Lap.calculateAverageVerticalOscillation(records: records);
await db.save();
notifyListeners();
}
return db.avgVerticalOscillation;
}
Future<double> get sdevVerticalOscillation async {
if (db.sdevVerticalOscillation == null) {
List<Event> records = await this.records;
db.sdevVerticalOscillation =
Lap.calculateSdevVerticalOscillation(records: records);
await db.save();
notifyListeners();
}
return db.sdevVerticalOscillation;
}
Future<double> get avgStrydCadence async {
if (db.avgStrydCadence == null) {
List<Event> records = await this.records;
db.avgStrydCadence = Lap.calculateAverageStrydCadence(records: records);
await db.save();
notifyListeners();
}
return db.avgStrydCadence;
}
Future<double> get sdevStrydCadence async {
if (db.sdevStrydCadence == null) {
List<Event> records = await this.records;
db.sdevStrydCadence = Lap.calculateSdevStrydCadence(records: records);
await db.save();
notifyListeners();
}
return db.sdevStrydCadence;
}
Future<double> get avgLegSpringStiffness async {
if (db.avgLegSpringStiffness == null) {
List<Event> records = await this.records;
db.avgLegSpringStiffness =
Lap.calculateAverageLegSpringStiffness(records: records);
await db.save();
notifyListeners();
}
return db.avgLegSpringStiffness;
}
Future<double> get sdevLegSpringStiffness async {
if (db.sdevLegSpringStiffness == null) {
List<Event> records = await this.records;
db.sdevLegSpringStiffness =
Lap.calculateSdevLegSpringStiffness(records: records);
await db.save();
notifyListeners();
}
return db.sdevLegSpringStiffness;
}
Future<double> get avgFormPower async {
if (db.avgFormPower == null) {
List<Event> records = await this.records;
db.avgFormPower = Lap.calculateAverageFormPower(records: records);
await db.save();
notifyListeners();
}
return db.avgFormPower;
}
Future<double> get sdevFormPower async {
if (db.sdevFormPower == null) {
List<Event> records = await this.records;
db.sdevFormPower = Lap.calculateSdevFormPower(records: records);
await db.save();
notifyListeners();
}
return db.sdevFormPower;
}
Future<double> get avgPowerRatio async {
if (db.avgPowerRatio == null) {
List<Event> records = await this.records;
db.avgPowerRatio = Lap.calculateAveragePowerRatio(records: records);
await db.save();
notifyListeners();
}
return db.avgPowerRatio;
}
Future<double> get sdevPowerRatio async {
if (db.sdevPowerRatio == null) {
List<Event> records = await this.records;
db.sdevPowerRatio = Lap.calculateSdevPowerRatio(records: records);
await db.save();
notifyListeners();
}
return db.sdevPowerRatio;
}
Future<double> get avgStrideRatio async {
if (db.avgStrideRatio == null) {
List<Event> records = await this.records;
db.avgStrideRatio = Lap.calculateAverageStrideRatio(records: records);
await db.save();
notifyListeners();
}
return db.avgStrideRatio;
}
Future<double> get sdevStrideRatio async {
if (db.sdevStrideRatio == null) {
List<Event> records = await this.records;
db.sdevStrideRatio = Lap.calculateSdevStrideRatio(records: records);
await db.save();
notifyListeners();
}
return db.sdevStrideRatio;
await db.save();
return true;
}
Stream<int> parse({@required Athlete athlete}) async* {
......@@ -382,6 +217,7 @@ class Activity extends ChangeNotifier {
}
db.state = "persisted";
await recalculateAverages();
await db.save();
notifyListeners();
print("Activity data for »${db.name}« stored in database.");
......
......@@ -189,7 +189,7 @@ class Event {
sum = sum + (record.db.power / record.db.heartRate);
break;
case LapDoubleAttr.speedPerHeartRate:
sum = sum + (record.db.speed / record.db.heartRate);
sum = sum + 100 * (record.db.speed / record.db.heartRate);
break;
case LapDoubleAttr.groundTime:
sum = sum + record.db.groundTime;
......
......@@ -240,12 +240,18 @@ class Lap {
static int calculateMinPower({List<Event> records}) {
var powers = records.map((record) => record.db.power).nonZeroInts();
return powers.min();
if (powers.length > 0)
return powers.min();
else
return 0;
}
static int calculateMaxPower({List<Event> records}) {
var powers = records.map((record) => record.db.power).nonZeroInts();
return powers.max();
if (powers.length > 0)
return powers.max();
else
return 0;
}
static double calculateAverageSpeed({List<Event> records}) {
......@@ -272,8 +278,9 @@ class Lap {
}
static double calculateAverageStrydCadence({List<Event> records}) {
var strydCadences =
records.map((record) => 2 * record.db.strydCadence).nonZeroDoubles();
var strydCadences = records
.map((record) => record.db.strydCadence ?? 0.0 * 2)
.nonZeroDoubles();
if (strydCadences.length > 0) {
return strydCadences.mean();
} else
......@@ -281,8 +288,9 @@ class Lap {
}
static double calculateSdevStrydCadence({List<Event> records}) {
var strydCadences =
records.map((record) => 2 * record.db.strydCadence).nonZeroDoubles();
var strydCadences = records
.map((record) => record.db.strydCadence ?? 0.0 * 2)
.nonZeroDoubles();
return strydCadences.sdev();
}
......@@ -330,8 +338,9 @@ class Lap {
}
static double calculateAveragePowerRatio({List<Event> records}) {
var powerRatios = records.where((record) => record.db.power != 0).map(
(record) =>
var powerRatios = records
.where((record) => record.db.power != null && record.db.power != 0)
.map((record) =>
(record.db.power - record.db.formPower) / record.db.power * 100);
if (powerRatios.length > 0) {
return powerRatios.mean();
......@@ -340,8 +349,9 @@ class Lap {
}
static double calculateSdevPowerRatio({List<Event> records}) {
var powerRatios = records.where((record) => record.db.power != 0).map(
(record) =>
var powerRatios = records
.where((record) => record.db.power != null && record.db.power != 0)
.map((record) =>
(record.db.power - record.db.formPower) / record.db.power * 100);
return powerRatios.sdev();
}
......
import 'package:encrateia/widgets/activity_widgets/activity_metadata_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_overview_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_power_ratio_widget.dart';
import 'package:encrateia/widgets/activity_widgets/speed_per_heart_rate_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_speed_per_heart_rate_widget.dart';
import 'package:encrateia/widgets/laps_list_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_heart_rate_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_power_widget.dart';
......
......@@ -4,6 +4,7 @@ import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/activities_list_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_power_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_stride_ratio_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_settings_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_power_per_heart_rate_widget.dart';
import 'package:encrateia/widgets/athlete_widgets/athlete_speed_per_heart_rate_widget.dart';
......@@ -89,6 +90,22 @@ class _ShowAthleteScreenState extends State<ShowAthleteScreen> {
nextWidget: AthleteSpeedPerHeartRateWidget(athlete: widget.athlete),
),
]),
TableRow(children: [
detailTile(
title: "Stride Ratio",
icon: MyIcon.strideRatio,
nextWidget: AthleteStrideRatioWidget(athlete: widget.athlete),
),
Card(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
child: ListTile(
leading: MyIcon.settings,
title: Text("Recalculate Averages"),
onTap: () => recalculate(),
),
),
]),
]),
);
}
......@@ -117,6 +134,40 @@ class _ShowAthleteScreenState extends State<ShowAthleteScreen> {
);
}
recalculate() async {
print("Start");
List<Activity> activities;
activities = await Activity.all(athlete: widget.athlete);
int index = 0;
int percent;
flushbar = Flushbar(
message: "Calculating...",
duration: Duration(seconds: 5),
icon: MyIcon.finishedWhite,
)..show(context);
for (Activity activity in activities) {
index += 1;
await activity.recalculateAverages();
flushbar.dismiss();
percent = 100 * index ~/ activities.length ;
flushbar = Flushbar(
titleText: LinearProgressIndicator(value: percent / 100),
message: "$percent% done (recalculating »${activity.db.name}« )",
duration: Duration(seconds: 1),
animationDuration: Duration(milliseconds: 1),
)..show(context);
}
flushbar.dismiss();
Flushbar(
message: "Averages are now up to date.",
duration: Duration(seconds: 5),
icon: MyIcon.finishedWhite,
)..show(context);
}
updateJob() async {
List<Activity> activities;
setState(() => floatingActionButtonVisible = false);
......@@ -179,7 +230,6 @@ class _ShowAthleteScreenState extends State<ShowAthleteScreen> {
duration: Duration(seconds: 1),
icon: MyIcon.finishedWhite,
)..show(context);
setState(() {});
}
......@@ -202,7 +252,6 @@ class _ShowAthleteScreenState extends State<ShowAthleteScreen> {
animationDuration: Duration(milliseconds: 1),
)..show(context);
}
setState(() {});
}
}
......@@ -2,7 +2,7 @@ import 'dart:math' as math;
import 'package:encrateia/models/event.dart';
extension StatisticFunctions on Iterable {
double mean(){
double mean() {
var sum = this.fold(0, (a, b) => a + b);
var number = this.length;
return sum / number;
......@@ -39,7 +39,6 @@ extension StatisticFunctions on Iterable {
return nonZeroValues.toList();
}
Iterable<Event> everyNth(int n) sync* {
List<Event> values = this.toList();
int i = 0;
......
......@@ -67,19 +67,10 @@ class _ActivitiesListWidgetState extends State<ActivitiesListWidget> {
children: <Widget>[
Text(activity.dateString() + "\n" + activity.distanceString()),
Text(activity.timeString() + "\n" + activity.paceString()),
FutureBuilder<double>(
future: activity.avgPower,
builder:
(BuildContext context, AsyncSnapshot<double> snapshot) {
if (snapshot.hasData) {
return Text(activity.heartRateString() +
"\n" +
snapshot.data.toStringOrDashes(1) +
" W");
} else {
return Text(activity.heartRateString() + "\n ...");
}
}),
Text(activity.heartRateString() +
"\n" +
activity.db.avgPower?.toStringOrDashes(1) +
" W"),
],
),
trailing: ChangeNotifierProvider.value(
......
......@@ -12,7 +12,8 @@ class ActivityFormPowerWidget extends StatefulWidget {
ActivityFormPowerWidget({this.activity});
@override
_ActivityFormPowerWidgetState createState() => _ActivityFormPowerWidgetState();
_ActivityFormPowerWidgetState createState() =>
_ActivityFormPowerWidgetState();
}
class _ActivityFormPowerWidgetState extends State<ActivityFormPowerWidget> {
......@@ -29,14 +30,16 @@ class _ActivityFormPowerWidgetState extends State<ActivityFormPowerWidget> {
@override
Widget build(context) {
if (records.length > 0) {
var formPowerValues = records.map((value) => value.db.formPower).nonZeroInts();
var formPowerValues =
records.map((value) => value.db.formPower).nonZeroInts();
if (formPowerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityFormPowerChart(records: records, activity: widget.activity),
ActivityFormPowerChart(
records: records, activity: widget.activity),
ListTile(
leading: MyIcon.formPower,
title: Text(avgFormPowerString),
......@@ -70,15 +73,8 @@ class _ActivityFormPowerWidgetState extends State<ActivityFormPowerWidget> {
getData() async {
Activity activity = widget.activity;
records = await activity.records;
double avg = await activity.avgFormPower;
setState(() {
avgFormPowerString = avg.toStringOrDashes(1) + " W";
});
double sdev = await activity.sdevFormPower;
setState(() {
sdevFormPowerString = sdev.toStringOrDashes(2) + " W";
});
avgFormPowerString = activity.db.avgFormPower.toStringOrDashes(1) + " W";
sdevFormPowerString = activity.db.sdevFormPower.toStringOrDashes(2) + " W";
setState(() {});
}
}
......@@ -70,15 +70,8 @@ class _ActivityGroundTimeWidgetState extends State<ActivityGroundTimeWidget> {
getData() async {
Activity activity = widget.activity;
records = await activity.records;
double avg = await activity.avgGroundTime;
setState(() {
avgGroundTimeString = avg.toStringOrDashes(1) + " ms";
});
double sdev = await activity.sdevGroundTime;
setState(() {
sdevGroundTimeString = sdev.toStringOrDashes(2) + " ms";
});
avgGroundTimeString = activity.db.avgGroundTime.toStringOrDashes(1) + " ms";
sdevGroundTimeString = activity.db.sdevGroundTime.toStringOrDashes(2) + " ms";
setState(() {});
}
}
......@@ -12,10 +12,12 @@ class ActivityLegSpringStiffnessWidget extends StatefulWidget {
ActivityLegSpringStiffnessWidget({this.activity});
@override
_ActivityLegSpringStiffnessWidgetState createState() => _ActivityLegSpringStiffnessWidgetState();
_ActivityLegSpringStiffnessWidgetState createState() =>
_ActivityLegSpringStiffnessWidgetState();
}
class _ActivityLegSpringStiffnessWidgetState extends State<ActivityLegSpringStiffnessWidget> {
class _ActivityLegSpringStiffnessWidgetState
extends State<ActivityLegSpringStiffnessWidget> {
List<Event> records = [];
String avgLegSpringStiffnessString = "Loading ...";
String sdevLegSpringStiffnessString = "Loading ...";
......@@ -29,14 +31,16 @@ class _ActivityLegSpringStiffnessWidgetState extends State<ActivityLegSpringStif
@override
Widget build(context) {
if (records.length > 0) {
var powerValues = records.map((value) => value.db.groundTime).nonZeroDoubles();
var powerValues =
records.map((value) => value.db.groundTime).nonZeroDoubles();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityLegSpringStiffnessChart(records: records, activity: widget.activity),
ActivityLegSpringStiffnessChart(
records: records, activity: widget.activity),
ListTile(
leading: MyIcon.average,
title: Text(avgLegSpringStiffnessString),
......@@ -70,15 +74,11 @@ class _ActivityLegSpringStiffnessWidgetState extends State<ActivityLegSpringStif
getData() async {
Activity activity = widget.activity;
records = await activity.records;
avgLegSpringStiffnessString =
activity.db.avgLegSpringStiffness.toStringOrDashes(1) + " ms";
double avg = await activity.avgLegSpringStiffness;
setState(() {
avgLegSpringStiffnessString = avg.toStringOrDashes(1) + " ms";
});
double sdev = await activity.sdevLegSpringStiffness;
setState(() {
sdevLegSpringStiffnessString = sdev.toStringOrDashes(2) + " ms";
});
sdevLegSpringStiffnessString =
activity.db.sdevLegSpringStiffness.toStringOrDashes(2) + " ms";
setState(() {});
}
}
......@@ -64,10 +64,8 @@ class _ActivityPowerPerHeartRateWidgetState
getData() async {
Activity activity = widget.activity;
records = await activity.records;
double avg = await activity.avgPower / activity.db.avgHeartRate;
setState(() {
avgPowerPerHeartRateString = avg.toStringOrDashes(1) + " W / bpm";
});
double avg = activity.db.avgPower / activity.db.avgHeartRate;
avgPowerPerHeartRateString = avg.toStringOrDashes(1) + " W / bpm";
setState(() {});
}
}
......@@ -12,7 +12,8 @@ class ActivityPowerRatioWidget extends StatefulWidget {
ActivityPowerRatioWidget({this.activity});
@override
_ActivityPowerRatioWidgetState createState() => _ActivityPowerRatioWidgetState();
_ActivityPowerRatioWidgetState createState() =>
_ActivityPowerRatioWidgetState();
}
class _ActivityPowerRatioWidgetState extends State<ActivityPowerRatioWidget> {
......@@ -36,7 +37,8 @@ class _ActivityPowerRatioWidgetState extends State<ActivityPowerRatioWidget> {
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityPowerRatioChart(records: records, activity: widget.activity),
ActivityPowerRatioChart(
records: records, activity: widget.activity),
Text("power ratio (%) = (power - form power) / power * 100"),
ListTile(
leading: MyIcon.formPower,
......@@ -71,15 +73,9 @@ class _ActivityPowerRatioWidgetState extends State<ActivityPowerRatioWidget> {
getData() async {
Activity activity = widget.activity;
records = await activity.records;
double avg = await activity.avgPowerRatio;
setState(() {
avgPowerRatioString = avg.toStringOrDashes(1) + " %";
});
double sdev = await activity.sdevPowerRatio;
setState(() {
sdevPowerRatioStri