Commit 5759062f authored by Administrator's avatar Administrator

show selected activity in athlete diagrams, navigate to activity

parent ff13fdbd
......@@ -22,12 +22,7 @@ class Activity extends ChangeNotifier {
DbActivity db;
List<Event> _records;
List<Lap> _laps;
double glidingAvgPower;
double glidingAvgPowerPerHeartRate;
double glidingAvgSpeedPerHeartRate;
double glidingAvgPowerRatio;
double glidingAvgStrideRatio;
double glidingEcor;
double glidingMeasureAttribute;
double weight;
// intermediate data structures used for parsing
......@@ -60,8 +55,8 @@ class Activity extends ChangeNotifier {
String toString() => '$db.name $db.startTime';
Duration movingDuration() => Duration(seconds: db.movingTime ?? 0);
get({ActivityAttr quantity}) {
switch (quantity) {
get({ActivityAttr activityAttr}) {
switch (activityAttr) {
case ActivityAttr.avgPower:
return db.avgPower;
case ActivityAttr.ecor:
......@@ -81,29 +76,6 @@ class Activity extends ChangeNotifier {
}
}
setGliding({ActivityAttr quantity, double value}) {
switch (quantity) {
case ActivityAttr.avgPower:
glidingAvgPower = value;
break;
case ActivityAttr.ecor:
glidingEcor = value;
break;
case ActivityAttr.avgPowerPerHeartRate:
glidingAvgPowerPerHeartRate = value;
break;
case ActivityAttr.avgSpeedPerHeartRate:
glidingAvgSpeedPerHeartRate = value;
break;
case ActivityAttr.avgPowerRatio:
glidingAvgPowerRatio = value;
break;
case ActivityAttr.avgStrideRatio:
glidingAvgStrideRatio = value;
break;
}
}
download({@required Athlete athlete}) async {
await StravaFitDownload.byId(id: db.stravaId.toString(), athlete: athlete);
setState("downloaded");
......
......@@ -9,10 +9,10 @@ class ActivityList {
enrichGlidingAverage({
@required int fullDecay,
@required ActivityAttr quantity,
@required ActivityAttr activityAttr,
}) {
activities.asMap().forEach((index, activity) {
double sumOfAvg = activity.get(quantity: quantity) * fullDecay;
double sumOfAvg = activity.get(activityAttr: activityAttr) * fullDecay;
double sumOfWeightings = fullDecay * 1.0;
for (var olderIndex = index + 1;
olderIndex < activities.length;
......@@ -23,14 +23,11 @@ class ActivityList {
24;
if (daysAgo > fullDecay) break;
sumOfAvg += (fullDecay - daysAgo) *
activities[olderIndex].get(quantity: quantity);
activities[olderIndex].get(activityAttr: activityAttr);
sumOfWeightings += (fullDecay - daysAgo);
}
activity.setGliding(
quantity: quantity,
value: sumOfAvg / sumOfWeightings,
);
activity.glidingMeasureAttribute = sumOfAvg / sumOfWeightings;
});
}
}
import 'dart:math';
import 'package:charts_flutter/flutter.dart';
import 'package:encrateia/models/activity_list.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/screens/show_activity_screen.dart';
import 'package:encrateia/utils/my_button.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:intl/intl.dart';
import 'package:encrateia/utils/date_time_utils.dart';
class AthleteTimeSeriesChart extends StatefulWidget {
final Athlete athlete;
final List<Activity> activities;
final ActivityAttr activityAttr;
final String chartTitleText;
AthleteTimeSeriesChart({
@required this.athlete,
@required this.activities,
@required this.activityAttr,
@required this.chartTitleText,
});
@override
_AthleteTimeSeriesChartState createState() => _AthleteTimeSeriesChartState();
}
class _AthleteTimeSeriesChartState extends State<AthleteTimeSeriesChart> {
Activity selectedActivity;
_onSelectionChanged(SelectionModel model) {
final selectedDatum = model.selectedDatum;
if (selectedDatum.isNotEmpty) {
setState(() => selectedActivity = selectedDatum[1].datum);
}
}
@override
Widget build(BuildContext context) {
int xAxesDays = 60;
ActivityList(activities: widget.activities).enrichGlidingAverage(
activityAttr: widget.activityAttr,
fullDecay: 30,
);
var recentActivities = widget.activities
.where((activity) =>
DateTime.now().difference(activity.db.timeCreated).inDays <
xAxesDays)
.toList();
if (recentActivities.length < 40) {
int amount = min(widget.activities.length, 40);
recentActivities = widget.activities.sublist(0, amount);
}
var data = [
Series<Activity, DateTime>(
id: widget.activityAttr.toString(),
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (Activity activity, _) => activity.db.timeCreated,
measureFn: (Activity activity, _) =>
activity.get(activityAttr: widget.activityAttr),
data: recentActivities,
),
Series<Activity, DateTime>(
id: 'gliding_' + widget.activityAttr.toString(),
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (Activity activity, _) => activity.db.timeCreated,
measureFn: (Activity activity, _) => activity.glidingMeasureAttribute,
data: recentActivities,
)..setAttribute(rendererIdKey, 'glidingAverageRenderer'),
];
return new Column(children: [
Container(
height: 300,
child: TimeSeriesChart(
data,
animate: true,
selectionModels: [
SelectionModelConfig(
type: SelectionModelType.info,
changedListener: _onSelectionChanged,
)
],
defaultRenderer: LineRendererConfig(
includePoints: true,
includeLine: false,
),
customSeriesRenderers: [
LineRendererConfig(
customRendererId: 'glidingAverageRenderer',
dashPattern: [1, 2],
),
],
primaryMeasureAxis: NumericAxisSpec(
tickProviderSpec: BasicNumericTickProviderSpec(
zeroBound: false,
dataIsInWholeNumbers: false,
desiredTickCount: 6,
),
),
behaviors: [
ChartTitle(
widget.chartTitleText,
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.start,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'Date',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.bottom,
titleOutsideJustification: OutsideJustification.end,
),
],
),
),
if (selectedActivity != null)
Container(
height: 200,
child: new OrientationBuilder(
builder: (context, orientation) {
return GridView.count(
padding: EdgeInsets.all(5),
crossAxisCount: orientation == Orientation.portrait ? 2 : 4,
childAspectRatio: 3,
crossAxisSpacing: 3,
mainAxisSpacing: 3,
children: [
MyButton.activity(
child: Text(selectedActivity.db.name),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ShowActivityScreen(
activity: selectedActivity,
athlete: widget.athlete,
),
),
),
),
ListTile(
title: Text(DateFormat("dd MMM yyyy, h:mm:ss")
.format(selectedActivity.db.timeCreated)),
subtitle: Text("Time created"),
),
ListTile(
title:
Text(selectedActivity.db.distance.toString() + " m"),
subtitle: Text('Distance')),
ListTile(
title: Text(
selectedActivity.db.avgSpeed.toPace() + " min/km"),
subtitle: Text('Average speed')),
ListTile(
title: Text(
selectedActivity.db.avgPower.toStringAsFixed(1) +
" W"),
subtitle: Text('Average power')),
ListTile(
title: Text(
selectedActivity.db.avgHeartRate.toString() + " bpm"),
subtitle: Text('Average heart rate')),
],
);
},
),
)
]);
}
}
......@@ -49,4 +49,12 @@ class MyButton extends RaisedButton {
child: child ?? Text('Detail'),
onPressed: onPressed,
);
MyButton.activity({Widget child, VoidCallback onPressed})
: super(
color: MyColor.activity,
textColor: Colors.white,
child: child ?? Text('Activity'),
onPressed: onPressed,
);
}
import 'package:encrateia/models/weight.dart';
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/charts/athlete_charts/athlete_ecor_chart.dart';
class AthleteEcorWidget extends StatefulWidget {
final Athlete athlete;
......@@ -49,7 +50,12 @@ class _AthleteEcorWidgetState extends State<AthleteEcorWidget> {
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
AthleteEcorChart(activities: ecorActivities),
AthleteTimeSeriesChart(
activities: ecorActivities,
chartTitleText: 'Ecor (W s/kg m)',
activityAttr: ActivityAttr.ecor,
athlete: widget.athlete,
)
],
),
);
......
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/charts/athlete_charts/athlete_power_per_heart_rate_chart.dart';
class AthletePowerPerHeartRateWidget extends StatefulWidget {
final Athlete athlete;
......@@ -41,8 +42,11 @@ class _AthletePowerPerHeartRateWidgetState
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
AthletePowerPerHeartRateChart(
AthleteTimeSeriesChart(
activities: powerPerHeartRateActivities,
activityAttr: ActivityAttr.avgPowerPerHeartRate,
chartTitleText: 'Average power per heart rate',
athlete: widget.athlete,
),
],
),
......
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/charts/athlete_charts/athlete_power_ratio_chart.dart';
class AthletePowerRatioWidget extends StatefulWidget {
final Athlete athlete;
......@@ -39,7 +40,12 @@ class _AthletePowerRatioWidgetState extends State<AthletePowerRatioWidget> {
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
AthletePowerRatioChart(activities: powerRatioActivities),
AthleteTimeSeriesChart(
activities: powerRatioActivities,
chartTitleText: 'Power Ratio (%)',
activityAttr: ActivityAttr.avgPowerRatio,
athlete: widget.athlete,
),
],
),
);
......
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/charts/athlete_charts/athlete_power_chart.dart';
class AthletePowerWidget extends StatefulWidget {
final Athlete athlete;
......@@ -34,7 +35,12 @@ class _AthletePowerWidgetState extends State<AthletePowerWidget> {
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
AthletePowerChart(activities: powerActivities),
AthleteTimeSeriesChart(
activities: powerActivities,
chartTitleText: 'Power (W)',
activityAttr: ActivityAttr.avgPower,
athlete: widget.athlete,
),
],
),
);
......
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/charts/athlete_charts/athlete_speed_per_heart_rate_chart.dart';
class AthleteSpeedPerHeartRateWidget extends StatefulWidget {
final Athlete athlete;
......@@ -41,8 +42,11 @@ class _AthleteSpeedPerHeartRateWidgetState
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
AthleteSpeedPerHeartRateChart(
AthleteTimeSeriesChart(
activities: speedPerHeartRateActivities,
activityAttr: ActivityAttr.avgSpeedPerHeartRate,
chartTitleText: 'Average speed per heart rate',
athlete: widget.athlete,
),
],
),
......
import 'package:encrateia/utils/athlete_time_series_chart.dart';
import 'package:encrateia/utils/enums.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/widgets/charts/athlete_charts/athlete_stride_ratio_chart.dart';
class AthleteStrideRatioWidget extends StatefulWidget {
final Athlete athlete;
......@@ -36,7 +37,12 @@ class _AthleteStrideRatioWidgetState extends State<AthleteStrideRatioWidget> {
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
AthleteStrideRatioChart(activities: strideRatioActivities),
AthleteTimeSeriesChart(
activities: strideRatioActivities,
activityAttr: ActivityAttr.avgStrideRatio,
chartTitleText: 'Stride Ratio',
athlete: widget.athlete,
),
],
),
);
......
import 'dart:math';
import 'package:charts_flutter/flutter.dart';
import 'package:encrateia/models/activity_list.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/utils/enums.dart';
class AthleteEcorChart extends StatelessWidget {
final List<Activity> activities;
AthleteEcorChart({@required this.activities});
@override
Widget build(BuildContext context) {
int xAxesDays = 60;
ActivityList(activities: activities).enrichGlidingAverage(
quantity: ActivityAttr.ecor,
fullDecay: 30,
);
var recentActivities = activities
.where((activity) =>
DateTime.now().difference(activity.db.timeCreated).inDays <
xAxesDays)
.toList();
if (recentActivities.length < 40) {
int amount = min(activities.length, 40);
recentActivities = activities.sublist(0, amount);
}
var data = [
Series<Activity, DateTime>(
id: 'ecor',
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (Activity activity, _) => activity.db.timeCreated,
measureFn: (Activity activity, _) =>
activity.db.avgPower / activity.db.avgSpeed / activity.weight,
data: recentActivities,
),
Series<Activity, DateTime>(
id: 'gliding_ecor',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (Activity activity, _) => activity.db.timeCreated,
measureFn: (Activity activity, _) => activity.glidingEcor,
data: recentActivities,
)..setAttribute(rendererIdKey, 'glidingAverageRenderer'),
];
return new Container(
height: 300,
child: TimeSeriesChart(
data,
animate: false,
defaultRenderer: LineRendererConfig(
includePoints: true,
includeLine: false,
),
customSeriesRenderers: [
LineRendererConfig(
customRendererId: 'glidingAverageRenderer',
dashPattern: [1, 2],
),
],
primaryMeasureAxis: NumericAxisSpec(
tickProviderSpec: BasicNumericTickProviderSpec(
zeroBound: false,
dataIsInWholeNumbers: false,
desiredTickCount: 6,
),
),
behaviors: [
ChartTitle(
'Ecor (W s/kg m)',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.start,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'Date',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.bottom,
titleOutsideJustification: OutsideJustification.end,
),
],
),
);
}
}
import 'dart:math';
import 'package:charts_flutter/flutter.dart';
import 'package:encrateia/models/activity_list.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/utils/enums.dart';
class AthletePowerChart extends StatelessWidget {
final List<Activity> activities;
AthletePowerChart({@required this.activities});
@override
Widget build(BuildContext context) {
int xAxesDays = 60;
ActivityList(activities: activities).enrichGlidingAverage(
quantity: ActivityAttr.avgPower,
fullDecay: 30,
);
var recentActivities = activities
.where((activity) =>
DateTime.now().difference(activity.db.timeCreated).inDays <
xAxesDays)
.toList();
if (recentActivities.length < 40) {
int amount = min(activities.length, 40);
recentActivities = activities.sublist(0, amount);
}
var data = [
Series<Activity, DateTime>(
id: 'average power',
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (Activity activity, _) => activity.db.timeCreated,
measureFn: (Activity activity, _) => activity.db.avgPower,
data: recentActivities,
),
Series<Activity, DateTime>(
id: 'Gliding average power',
colorFn: (_, __) => MaterialPalette.green.shadeDefault,
domainFn: (Activity activity, _) => activity.db.timeCreated,
measureFn: (Activity activity, _) => activity.glidingAvgPower,
data: recentActivities,
)..setAttribute(rendererIdKey, 'glidingAverageRenderer'),
];
return new Container(
height: 300,
child: TimeSeriesChart(
data,
animate: false,
defaultRenderer: LineRendererConfig(
includePoints: true,
includeLine: false,
),
customSeriesRenderers: [
LineRendererConfig(
customRendererId: 'glidingAverageRenderer',
dashPattern: [1, 2],
),
],
primaryMeasureAxis: NumericAxisSpec(
tickProviderSpec: BasicNumericTickProviderSpec(
zeroBound: false,
dataIsInWholeNumbers: false,
desiredTickCount: 6,
),
),
behaviors: [
ChartTitle(
'Power (W)',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.start,
titleOutsideJustification: OutsideJustification.end,
),
ChartTitle(
'Date',
titleStyleSpec: TextStyleSpec(fontSize: 13),
behaviorPosition: BehaviorPosition.bottom,
titleOutsideJustification: OutsideJustification.end,
),
],
),
);
}
}