Commit ce12246b authored by Administrator's avatar Administrator

cache average, min, max, sdev power in db on activity and lap level

parent 351e134c
......@@ -83,6 +83,11 @@ const tableActivity = SqfEntityTable(
SqfEntityField('numLaps', DbType.integer),
SqfEntityField('numSessions', DbType.integer),
SqfEntityField('localTimestamp', DbType.datetime),
// Cached calculated values:
SqfEntityField('avgPower', DbType.real),
SqfEntityField('minPower', DbType.integer),
SqfEntityField('maxPower', DbType.integer),
SqfEntityField('sdevPower', DbType.real),
SqfEntityFieldRelationship(
parentTable: tableAthlete,
......@@ -167,6 +172,12 @@ const tableLap = SqfEntityTable(
SqfEntityField('avgFractionalCadence', DbType.real),
SqfEntityField('maxFractionalCadence', DbType.real),
SqfEntityField('totalFractionalCycles', DbType.real),
// Cached calculated values:
SqfEntityField('avgPower', DbType.real),
SqfEntityField('minPower', DbType.integer),
SqfEntityField('maxPower', DbType.integer),
SqfEntityField('sdevPower', DbType.real),
SqfEntityFieldRelationship(
parentTable: tableEvent,
......
This diff is collapsed.
......@@ -65,16 +65,14 @@ class Activity extends ChangeNotifier {
timeString() {
if (db.avgHeartRate != null) {
return DateFormat("H:mm")
.format(db.timeCreated);
return DateFormat("H:mm").format(db.timeCreated);
} else
return "";
}
dateString() {
if (db.avgHeartRate != null) {
return DateFormat("d MMM yy")
.format(db.timeCreated);
return DateFormat("d MMM yy").format(db.timeCreated);
} else
return "";
}
......@@ -86,11 +84,45 @@ class Activity extends ChangeNotifier {
return "";
}
Future<String> get averagePower async{
List<Event> records = await Event.recordsByActivity(activity: this);
String averagePower = Lap.averagePower(records: records);
notifyListeners();
return averagePower;
Future<double> get avgPower async {
if (db.avgPower == null) {
List<Event> records = await Event.recordsByActivity(activity: this);
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 Event.recordsByActivity(activity: this);
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 Event.recordsByActivity(activity: this);
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 Event.recordsByActivity(activity: this);
db.maxPower = Lap.calculateMaxPower(records: records);
await db.save();
notifyListeners();
}
return db.maxPower;
}
parse({@required Athlete athlete}) async {
......
......@@ -53,6 +53,43 @@ class Lap {
}
Lap.fromDb(this.db);
Future<double> get avgPower async {
if (db.avgPower == null) {
List<Event> records = await Event.recordsByLap(lap: this);
db.avgPower = calculateAveragePower(records: records);
await db.save();
}
return db.avgPower;
}
Future<double> get sdevPower async {
if (db.sdevPower == null) {
List<Event> records = await Event.recordsByLap(lap: this);
db.sdevPower = calculateSdevPower(records: records);
await db.save();
}
return db.sdevPower;
}
Future<int> get minPower async {
if (db.minPower == null){
List<Event> records = await Event.recordsByLap(lap: this);
db.minPower = calculateMinPower(records: records);
await db.save();
}
return db.minPower;
}
Future<int> get maxPower async {
if (db.maxPower == null){
List<Event> records = await Event.recordsByLap(lap: this);
db.maxPower = calculateMaxPower(records: records);
await db.save();
}
return db.maxPower;
}
Future<int> firstEventId() async {
if (index > 1) {
var lapList = await Lap.by(activity: activity);
......@@ -102,26 +139,26 @@ class Lap {
return heartRates.max().toStringAsFixed(1);
}
static String averagePower({List<Event> records}) {
static double calculateAveragePower({List<Event> records}) {
var powers = records.map((record) => record.db.power).nonZero();
if (powers.length > 0) {
return powers.mean().toStringAsFixed(1) + " W";
return powers.mean();
} else
return "";
return -1;
}
static String sdevPower({List<Event> records}) {
static double calculateSdevPower({List<Event> records}) {
var powers = records.map((record) => record.db.power).nonZero();
return powers.sdev().toStringAsFixed(2);
return powers.sdev();
}
static String minPower({List<Event> records}) {
static int calculateMinPower({List<Event> records}) {
var powers = records.map((record) => record.db.power).nonZero();
return powers.min().toStringAsFixed(1);
return powers.min();
}
static String maxPower({List<Event> records}) {
static int calculateMaxPower({List<Event> records}) {
var powers = records.map((record) => record.db.power).nonZero();
return powers.max().toStringAsFixed(1);
return powers.max();
}
}
......@@ -3,6 +3,7 @@ import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'show_activity_screen.dart';
import 'package:encrateia/utils/num_utils.dart';
enum Action { show, parse, download, delete, state }
......@@ -23,8 +24,8 @@ class _ListActivitiesScreenState extends State<ListActivitiesScreen> {
@override
initState() {
super.initState();
getActivities();
super.initState();
}
@override
......@@ -71,14 +72,14 @@ class _ListActivitiesScreenState extends State<ListActivitiesScreen> {
"\n" +
activity.distanceString()),
Text(activity.timeString() + "\n" + activity.paceString()),
FutureBuilder<String>(
future: activity.averagePower,
FutureBuilder<double>(
future: activity.avgPower,
builder: (BuildContext context,
AsyncSnapshot<String> snapshot) {
AsyncSnapshot<double> snapshot) {
if (snapshot.hasData) {
return Text(activity.heartRateString() +
"\n" +
snapshot.data);
snapshot.data.toStringOrDashes(1) + " W");
} else {
return Text(activity.heartRateString() + "\n ...");
}
......
extension DurationFormatters on num {
toStringOrDashes(int precision) {
if (this == -1) {
return "- - -";
} else {
return this.toStringAsFixed(precision);
}
}
}
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/utils/list_utils.dart';
import 'package:encrateia/utils/num_utils.dart';
import 'activity_power_chart.dart';
class ActivityPowerWidget extends StatelessWidget {
class ActivityPowerWidget extends StatefulWidget {
final Activity activity;
ActivityPowerWidget({this.activity});
@override
_ActivityPowerWidgetState createState() => _ActivityPowerWidgetState();
}
class _ActivityPowerWidgetState extends State<ActivityPowerWidget> {
List<Event> records = [];
String avgPowerString = "Loading ...";
String minPowerString = "Loading ...";
String maxPowerString = "Loading ...";
String sdevPowerString = "Loading ...";
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(context) {
return FutureBuilder<List<Event>>(
future: Event.recordsByActivity(activity: activity),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
var powerValues =
snapshot.data.map((value) => value.db.power).nonZero();
if (powerValues.length > 0) {
var records = snapshot.data;
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityPowerChart(records: records, activity: activity),
ListTile(
leading: Icon(Icons.ev_station),
title:
Text(Lap.averagePower(records: records) + " W"),
subtitle: Text("average power"),
),
ListTile(
leading: Icon(Icons.expand_more),
title: Text(Lap.minPower(records: records) + " W"),
subtitle: Text("minimum power"),
),
ListTile(
leading: Icon(Icons.expand_less),
title: Text(Lap.maxPower(records: records) + " W"),
subtitle: Text("maximum power"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(Lap.sdevPower(records: records) + " W"),
subtitle: Text("standard deviation power"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
if (records.length > 0) {
var powerValues = records.map((value) => value.db.power).nonZero();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ActivityPowerChart(records: records, activity: widget.activity),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(avgPowerString),
subtitle: Text("average power"),
),
ListTile(
leading: Icon(Icons.expand_more),
title: Text(minPowerString),
subtitle: Text("minimum power"),
),
ListTile(
leading: Icon(Icons.expand_less),
title: Text(maxPowerString),
subtitle: Text("maximum power"),
),
);
} else {
return Center(
child: Text("No power data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
},
);
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(sdevPowerString),
subtitle: Text("standard deviation power"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
),
);
} else {
return Center(
child: Text("No power data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
}
getData() async {
Activity activity = widget.activity;
records = await Event.recordsByActivity(activity: activity);
double avg = await activity.avgPower;
setState(() {
avgPowerString = avg.toStringOrDashes(1) + " W";
});
int min = await activity.minPower;
setState(() {
minPowerString = min.toString() + " W";
});
int max = await activity.maxPower;
setState(() {
maxPowerString = max.toString() + " W";
});
double sdev = await activity.sdevPower;
setState(() {
sdevPowerString = sdev.toStringOrDashes(2) + " W";
});
}
}
......@@ -2,68 +2,106 @@ import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/models/event.dart';
import 'package:encrateia/utils/list_utils.dart';
import 'package:encrateia/utils/num_utils.dart';
import 'lap_power_chart.dart';
class LapPowerWidget extends StatelessWidget {
class LapPowerWidget extends StatefulWidget {
final Lap lap;
LapPowerWidget({this.lap});
@override
_LapPowerWidgetState createState() => _LapPowerWidgetState();
}
class _LapPowerWidgetState extends State<LapPowerWidget> {
List<Event> records = [];
String avgPowerString = "Loading ...";
String minPowerString = "Loading ...";
String maxPowerString = "Loading ...";
String sdevPowerString = "Loading ...";
@override
void initState() {
getData();
super.initState();
}
@override
Widget build(context) {
return FutureBuilder<List<Event>>(
future: Event.recordsByLap(lap: lap),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
var powerValues =
snapshot.data.map((value) => value.db.power).nonZero();
if (powerValues.length > 0) {
var records = snapshot.data;
return ListTileTheme(
iconColor: Colors.lightGreen,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
LapPowerChart(records: records),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(Lap.averagePower(records: records) + " W"),
subtitle: Text("average power"),
),
ListTile(
leading: Icon(Icons.expand_more),
title: Text(Lap.minPower(records: records) + " W"),
subtitle: Text("minimum power"),
),
ListTile(
leading: Icon(Icons.expand_less),
title: Text(Lap.maxPower(records: records) + " W"),
subtitle: Text("maximum power"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(Lap.sdevPower(records: records) + " W"),
subtitle: Text("standard deviation power"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
if (records.length > 0) {
var powerValues =
records.map((value) => value.db.power).nonZero();
if (powerValues.length > 0) {
return ListTileTheme(
iconColor: Colors.lightGreen,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
LapPowerChart(records: records),
ListTile(
leading: Icon(Icons.ev_station),
title: Text(avgPowerString),
subtitle: Text("average power"),
),
ListTile(
leading: Icon(Icons.expand_more),
title: Text(
minPowerString),
subtitle: Text("minimum power"),
),
ListTile(
leading: Icon(Icons.expand_less),
title: Text(maxPowerString),
subtitle: Text("maximum power"),
),
);
} else {
return Center(
child: Text("No power data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
},
);
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(sdevPowerString),
subtitle: Text("standard deviation power"),
),
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
],
),
);
} else {
return Center(
child: Text("No power data available."),
);
}
} else {
return Center(
child: Text("Loading"),
);
}
}
getData() async {
Lap lap = widget.lap;
records = await Event.recordsByLap(lap: lap);
double avg = await lap.avgPower;
setState(() {
avgPowerString = avg.toStringOrDashes(1) + " W";
});
int min = await lap.minPower;
setState(() {
minPowerString = min.toString() + " W";
});
int max = await lap.maxPower;
setState(() {
maxPowerString = max.toString() + " W";
});
double sdev = await lap.sdevPower;
setState(() {
sdevPowerString = sdev.toStringOrDashes(2) + " W";
});
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment