Commit 07deb0ad authored by Administrator's avatar Administrator

power and heart rate widget, including standard deviation, min, max, average

parent 0b60de2c
......@@ -3,10 +3,14 @@ import 'package:fit_parser/fit_parser.dart';
import 'package:encrateia/utils/date_time_utils.dart';
import 'activity.dart';
import 'dart:developer';
import 'package:encrateia/models/lap.dart';
class Event {
DbEvent db;
Activity activity;
int index;
Event.fromDb(this.db);
Event({DataMessage dataMessage, this.activity}) {
if (dataMessage.any('max_heart_rate')) {
......@@ -42,6 +46,7 @@ class Event {
Event.fromRecord({DataMessage dataMessage, this.activity}) {
db = DbEvent()
..activitiesId = activity.db.id
..event = "record"
..timeStamp = dateTimeFromStrava(dataMessage.get('timestamp'))
..positionLat = dataMessage.get('position_lat')
..positionLong = dataMessage.get('position_long')
......@@ -78,4 +83,33 @@ class Event {
..distance = dataMessage.get('total_distance')
..save();
}
static Future<List<Event>> recordsByLap({Lap lap}) async {
int firstRecordId = await lap.firstEventId();
DbEvent lastRecord = await lap.db.getDbEvent();
int lastRecordId = lastRecord.id;
var events = await by(activity: lap.activity);
var records = events.where((event) =>
event.db.id < lastRecordId &&
event.db.id > firstRecordId &&
event.db.event == "record");
return records.toList();
}
static Future<List<Event>> by({Activity activity}) async {
int counter = 1;
List<DbEvent> dbEventList = await activity.db.getDbEvents().toList();
var eventList = dbEventList.map((dbLap) => Event.fromDb(dbLap)).toList();
for (Event event in eventList) {
event.activity = activity;
event.index = counter;
counter = counter + 1;
}
return eventList;
}
}
import 'package:encrateia/model/model.dart';
import 'package:fit_parser/fit_parser.dart';
import 'package:encrateia/utils/date_time_utils.dart';
import 'package:encrateia/utils/list_utils.dart';
import 'package:encrateia/models/event.dart';
import 'activity.dart';
import 'dart:math';
class Lap {
DbLap db;
......@@ -51,19 +54,71 @@ class Lap {
}
Lap.fromDb(this.db);
Future<int> firstEventId() async {
if (index > 1) {
var lapList = await Lap.by(activity: activity);
DbEvent firstEvent = await lapList
.firstWhere((lap) => lap.index == index - 1)
.db
.getDbEvent();
return firstEvent.id;
} else {
return 0;
}
}
static Future<List<Lap>> by({Activity activity}) async {
int counter = 1;
List<DbLap> dbLapList = await activity.db.getDbLaps().toList();
var lapList = dbLapList.map((dbLap) => Lap.fromDb(dbLap)).toList();
await Future.forEach(lapList, (lap) async {
var dbActivity = await lap.db.getDbActivity();
lap.activity = Activity.fromDb(dbActivity);
lap.index = counter;
counter = counter +1;
});
for (Lap lap in lapList) {
lap
..activity = activity
..index = counter;
counter = counter + 1;
}
return lapList;
}
static String averageHeartRate({List<Event> records}) {
var heartRates = records.map((record) => record.db.heartRate);
return heartRates.mean().toStringAsFixed(1);
}
static String sdevHeartRate({List<Event> records}) {
var heartRates = records.map((record) => record.db.heartRate);
return heartRates.sdev().toStringAsFixed(2);
}
static String minHeartRate({List<Event> records}) {
var heartRates = records.map((record) => record.db.heartRate);
return heartRates.reduce(min).toStringAsFixed(1);
}
static String maxHeartRate({List<Event> records}) {
var heartRates = records.map((record) => record.db.heartRate);
return heartRates.reduce(max).toStringAsFixed(1);
}
static String averagePower({List<Event> records}) {
var powers = records.map((record) => record.db.power);
return powers.mean().toStringAsFixed(1);
}
static String sdevPower({List<Event> records}) {
var powers = records.map((record) => record.db.power);
return powers.sdev().toStringAsFixed(2);
}
static String minPower({List<Event> records}) {
var powers = records.map((record) => record.db.power);
return powers.reduce(min).toStringAsFixed(1);
}
static String maxPower({List<Event> records}) {
var powers = records.map((record) => record.db.power);
return powers.reduce(max).toStringAsFixed(1);
}
}
......@@ -48,8 +48,7 @@ class _ListActivitiesScreenState extends State<ListActivitiesScreen> {
leading: Icon(Icons.error),
title: Text("Strava email not provided yet!"),
),
if (snapshot.connectionState == ConnectionState.done &&
snapshot.data.length > 0)
if (snapshot.hasData)
for (Activity activity in snapshot.data)
ListTile(
leading: Icon(Icons.directions_run),
......
import 'package:encrateia/widgets/lap_metadata_widget.dart';
import 'package:encrateia/widgets/lap_overview_widget.dart';
import 'package:encrateia/widgets/lap_heart_rate_widget.dart';
import 'package:encrateia/widgets/lap_power_widget.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
......@@ -14,10 +16,11 @@ class ShowLapScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
length: 4,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
isScrollable: true,
tabs: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
......@@ -26,6 +29,20 @@ class ShowLapScreen extends StatelessWidget {
Text(" Overview"),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Tab(icon: Icon(Icons.spa)),
Text(" Heart Rate"),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Tab(icon: Icon(Icons.ev_station)),
Text(" Power"),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
......@@ -42,6 +59,8 @@ class ShowLapScreen extends StatelessWidget {
),
body: TabBarView(children: [
LapOverviewWidget(lap: lap),
LapHeartRateWidget(lap: lap),
LapPowerWidget(lap: lap),
LapMetadataWidget(lap: lap),
]),
),
......
......@@ -35,8 +35,8 @@ extension DegreeFormatters on double {
if (this != null) {
var totalSeconds = 1000 / this;
var minutes = (totalSeconds / 60).floor();
var seconds = (totalSeconds - minutes * 60).round();
return "${minutes}min ${seconds}s";
var seconds = (totalSeconds - minutes * 60).round().toString().padLeft(2, "0");
return "$minutes:$seconds";
} else {
return "no value";
}
......
import 'dart:math';
extension StatisticFunctions on Iterable {
double mean() {
List<int> records = this.toList();
var sum = records.reduce((a, b) => a + b);
var number = records.length;
return sum / number;
}
double sdev() {
List<int> records = this.toList();
var mean = records.mean();
var sumOfErrorSquares =
records.fold(0.0, (double sum, next) => sum + pow(next - mean, 2));
var variance = sumOfErrorSquares / records.length;
return sqrt(variance);
}
}
import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/models/event.dart';
class LapHeartRateWidget extends StatelessWidget {
final Lap lap;
LapHeartRateWidget({this.lap});
@override
Widget build(context) {
return FutureBuilder<List<Event>>(
future: Event.recordsByLap(lap: lap),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
var records = snapshot.data;
return ListTileTheme(
iconColor: Colors.lightGreen,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
ListTile(
leading: Icon(Icons.pets),
title: Text(Lap.averageHeartRate(records: records)),
subtitle: Text("average heart rate"),
),
ListTile(
leading: Icon(Icons.expand_less),
title: Text(Lap.minHeartRate(records: records)),
subtitle: Text("minimum heart rate"),
),
ListTile(
leading: Icon(Icons.expand_more),
title: Text(Lap.maxHeartRate(records: records)),
subtitle: Text("maximum heart rate"),
),
ListTile(
leading: Icon(Icons.unfold_more),
title: Text(Lap.sdevHeartRate(records: records)),
subtitle: Text("standard deviation heart rate"),
),
],
),
);
} else {
return Center(
child: Text("Loading"),
);
}
},
);
}
}
import 'package:flutter/material.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/models/event.dart';
class LapPowerWidget extends StatelessWidget {
final Lap lap;
LapPowerWidget({this.lap});
@override
Widget build(context) {
return FutureBuilder<List<Event>>(
future: Event.recordsByLap(lap: lap),
builder: (BuildContext context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
var records = snapshot.data;
return ListTileTheme(
iconColor: Colors.lightGreen,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
ListTile(
leading: Icon(Icons.playlist_add),
title: Text(records.length.toString()),
subtitle: Text("number of measurements"),
),
ListTile(
leading: Icon(Icons.pets),
title: Text(Lap.averagePower(records: records) + " W"),
subtitle: Text("average power"),
),
ListTile(
leading: Icon(Icons.expand_less),
title: Text(Lap.minPower(records: records) + " W"),
subtitle: Text("minimum power"),
),
ListTile(
leading: Icon(Icons.expand_more),
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"),
),
],
),
);
} else {
return Center(
child: Text("Loading"),
);
}
},
);
}
}
......@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/lap.dart';
import 'package:encrateia/screens/show_lap_screen.dart';
import 'package:encrateia/utils/date_time_utils.dart';
class LapsListWidget extends StatelessWidget {
final Activity activity;
......@@ -13,10 +14,8 @@ class LapsListWidget extends StatelessWidget {
return FutureBuilder<List<Lap>>(
future: Lap.by(activity: activity),
builder: (BuildContext context, AsyncSnapshot<List<Lap>> snapshot) {
Widget widget;
if (snapshot.hasData) {
widget = DataTable(
return DataTable(
dataRowHeight: kMinInteractiveDimension * 0.60,
columnSpacing: 20,
columns: <DataColumn>[
......@@ -31,8 +30,8 @@ class LapsListWidget extends StatelessWidget {
numeric: true,
),
const DataColumn(
label: Text("km/h"),
tooltip: 'speed',
label: Text("min:ss"),
tooltip: 'pace',
numeric: true,
),
const DataColumn(
......@@ -54,9 +53,7 @@ class LapsListWidget extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShowLapScreen(
lap: lap,
),
builder: (context) => ShowLapScreen(lap: lap),
),
);
}
......@@ -69,10 +66,10 @@ class LapsListWidget extends StatelessWidget {
Text(lap.db.avgHeartRate.toString()),
),
DataCell(
Text((lap.db.avgSpeed * 3.6).toStringAsFixed(2)),
Text(lap.db.avgSpeed.toPace()),
),
DataCell(
Text((lap.db.totalDistance / 1000).toStringAsFixed(3) +
Text((lap.db.totalDistance / 1000).toStringAsFixed(2) +
' km'),
),
DataCell(
......@@ -83,25 +80,11 @@ class LapsListWidget extends StatelessWidget {
}).toList(),
);
} else {
widget = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
const Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Loading...'),
)
],
),
return Center(
child: Text("Loading"),
);
}
return widget;
;
},
);
}
......
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