Commit 524f429a authored by Administrator's avatar Administrator

record aggregation configurable, disclosure of record filtering on activity level

parent 28c4c14f
......@@ -24,6 +24,7 @@ const tableAthlete = SqfEntityTable(
SqfEntityField('stravaId', DbType.integer),
SqfEntityField('geoState', DbType.text),
SqfEntityField('downloadInterval', DbType.integer),
SqfEntityField('recordAggregationCount', DbType.integer),
],
);
......
......@@ -39,6 +39,7 @@ class TableDbAthlete extends SqfEntityTableBase {
SqfEntityFieldBase('stravaId', DbType.integer),
SqfEntityFieldBase('geoState', DbType.text),
SqfEntityFieldBase('downloadInterval', DbType.integer),
SqfEntityFieldBase('recordAggregationCount', DbType.integer),
];
super.init();
}
......@@ -439,7 +440,8 @@ class DbAthlete {
this.photoPath,
this.stravaId,
this.geoState,
this.downloadInterval}) {
this.downloadInterval,
this.recordAggregationCount}) {
_setDefaultValues();
}
DbAthlete.withFields(
......@@ -450,7 +452,8 @@ class DbAthlete {
this.photoPath,
this.stravaId,
this.geoState,
this.downloadInterval) {
this.downloadInterval,
this.recordAggregationCount) {
_setDefaultValues();
}
DbAthlete.withId(
......@@ -462,7 +465,8 @@ class DbAthlete {
this.photoPath,
this.stravaId,
this.geoState,
this.downloadInterval) {
this.downloadInterval,
this.recordAggregationCount) {
_setDefaultValues();
}
DbAthlete.fromMap(Map<String, dynamic> o) {
......@@ -478,6 +482,8 @@ class DbAthlete {
if (o['geoState'] != null) geoState = o['geoState'] as String;
if (o['downloadInterval'] != null)
downloadInterval = o['downloadInterval'] as int;
if (o['recordAggregationCount'] != null)
recordAggregationCount = o['recordAggregationCount'] as int;
}
// FIELDS (DbAthlete)
int id;
......@@ -489,6 +495,7 @@ class DbAthlete {
int stravaId;
String geoState;
int downloadInterval;
int recordAggregationCount;
BoolResult saveResult;
// end FIELDS (DbAthlete)
......@@ -592,6 +599,10 @@ class DbAthlete {
map['downloadInterval'] = downloadInterval;
}
if (recordAggregationCount != null) {
map['recordAggregationCount'] = recordAggregationCount;
}
return map;
}
......@@ -633,6 +644,10 @@ class DbAthlete {
map['downloadInterval'] = downloadInterval;
}
if (recordAggregationCount != null) {
map['recordAggregationCount'] = recordAggregationCount;
}
// COLLECTIONS (DbAthlete)
if (!forQuery) {
map['DbActivities'] = await getDbActivities().toMapList();
......@@ -672,7 +687,8 @@ class DbAthlete {
photoPath,
stravaId,
geoState,
downloadInterval
downloadInterval,
recordAggregationCount
];
}
......@@ -787,7 +803,7 @@ class DbAthlete {
/// Returns a <List<BoolResult>>
Future<List<BoolResult>> saveAll(List<DbAthlete> dbathletes) async {
final results = _mnDbAthlete.saveAll(
'INSERT OR REPLACE INTO athletes (id, state, firstName, lastName, stravaUsername, photoPath, stravaId, geoState, downloadInterval) VALUES (?,?,?,?,?,?,?,?,?)',
'INSERT OR REPLACE INTO athletes (id, state, firstName, lastName, stravaUsername, photoPath, stravaId, geoState, downloadInterval, recordAggregationCount) VALUES (?,?,?,?,?,?,?,?,?,?)',
dbathletes);
return results;
}
......@@ -798,7 +814,7 @@ class DbAthlete {
Future<int> _upsert() async {
try {
if (await _mnDbAthlete.rawInsert(
'INSERT OR REPLACE INTO athletes (id, state, firstName, lastName, stravaUsername, photoPath, stravaId, geoState, downloadInterval) VALUES (?,?,?,?,?,?,?,?,?)',
'INSERT OR REPLACE INTO athletes (id, state, firstName, lastName, stravaUsername, photoPath, stravaId, geoState, downloadInterval, recordAggregationCount) VALUES (?,?,?,?,?,?,?,?,?,?)',
[
id,
state,
......@@ -808,7 +824,8 @@ class DbAthlete {
photoPath,
stravaId,
geoState,
downloadInterval
downloadInterval,
recordAggregationCount
]) ==
1) {
saveResult = BoolResult(
......@@ -834,7 +851,7 @@ class DbAthlete {
/// Returns a <List<BoolResult>>
Future<List<BoolResult>> upsertAll(List<DbAthlete> dbathletes) async {
final results = await _mnDbAthlete.rawInsertAll(
'INSERT OR REPLACE INTO athletes (id, state, firstName, lastName, stravaUsername, photoPath, stravaId, geoState, downloadInterval) VALUES (?,?,?,?,?,?,?,?,?)',
'INSERT OR REPLACE INTO athletes (id, state, firstName, lastName, stravaUsername, photoPath, stravaId, geoState, downloadInterval, recordAggregationCount) VALUES (?,?,?,?,?,?,?,?,?,?)',
dbathletes);
return results;
}
......@@ -1327,6 +1344,12 @@ class DbAthleteFilterBuilder extends SearchCriteria {
setField(_downloadInterval, 'downloadInterval', DbType.integer);
}
DbAthleteField _recordAggregationCount;
DbAthleteField get recordAggregationCount {
return _recordAggregationCount = setField(
_recordAggregationCount, 'recordAggregationCount', DbType.integer);
}
bool _getIsDeleted;
void _buildParameters() {
......@@ -1705,6 +1728,13 @@ class DbAthleteFields {
SqlSyntax.setField(
_fDownloadInterval, 'downloadInterval', DbType.integer);
}
static TableField _fRecordAggregationCount;
static TableField get recordAggregationCount {
return _fRecordAggregationCount = _fRecordAggregationCount ??
SqlSyntax.setField(
_fRecordAggregationCount, 'recordAggregationCount', DbType.integer);
}
}
// endregion DbAthleteFields
......
import 'package:encrateia/models/strava_fit_download.dart';
import 'package:encrateia/models/weight.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/model/model.dart';
import 'package:strava_flutter/strava.dart';
......@@ -527,12 +526,4 @@ class Activity extends ChangeNotifier {
);
return heartRateZoneSchema;
}
getWeight() async {
var weight = await Weight.getBy(
athletesId: db.athletesId,
date: db.timeCreated,
);
return weight;
}
}
......@@ -31,7 +31,8 @@ class Athlete extends ChangeNotifier {
..photoPath = athlete.profile
..geoState = athlete.state
..state = "fromStrava"
..downloadInterval = 21;
..downloadInterval = 21
..recordAggregationCount = 16;
notifyListeners();
}
......@@ -40,7 +41,8 @@ class Athlete extends ChangeNotifier {
..state = "standalone"
..firstName = "Jane"
..lastName = "Doe"
..downloadInterval = 21;
..downloadInterval = 21
..recordAggregationCount = 16;
await db.save();
notifyListeners();
}
......
......@@ -38,7 +38,7 @@ class AddWeightScreen extends StatelessWidget {
TextFormField(
decoration: InputDecoration(labelText: "Weight in kg"),
initialValue: weight.db.value.toString(),
keyboardType: TextInputType.number,
keyboardType: TextInputType.numberWithOptions(decimal: true),
onChanged: (value) => weight.db.value = double.parse(value),
),
SizedBox(height: 20),
......
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/utils/my_color.dart';
import 'package:encrateia/widgets/activity_widgets/activity_metadata_widget.dart';
import 'package:encrateia/widgets/activity_widgets/activity_overview_widget.dart';
......@@ -23,10 +24,12 @@ import 'package:flutter/widgets.dart';
class ShowActivityScreen extends StatelessWidget {
final Activity activity;
final Athlete athlete;
const ShowActivityScreen({
Key key,
this.activity,
@required this.activity,
@required this.athlete,
}) : super(key: key);
@override
......@@ -51,7 +54,10 @@ class ShowActivityScreen extends StatelessWidget {
title: "Overview",
icon: MyIcon.overView,
context: context,
nextWidget: ActivityOverviewWidget(activity: activity),
nextWidget: ActivityOverviewWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: 'Laps',
......@@ -65,98 +71,140 @@ class ShowActivityScreen extends StatelessWidget {
color: MyColor.navigate,
icon: MyIcon.heartRate,
context: context,
nextWidget: ActivityHeartRateWidget(activity: activity),
nextWidget: ActivityHeartRateWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Power",
color: MyColor.navigate,
icon: MyIcon.power,
context: context,
nextWidget: ActivityPowerWidget(activity: activity),
nextWidget: ActivityPowerWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Power\nDuration",
color: MyColor.navigate,
icon: MyIcon.powerDuration,
context: context,
nextWidget: ActivityPowerDurationWidget(activity: activity),
nextWidget: ActivityPowerDurationWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Power /\nHeart Rate",
color: MyColor.navigate,
icon: MyIcon.power,
context: context,
nextWidget: ActivityPowerPerHeartRateWidget(activity: activity),
nextWidget: ActivityPowerPerHeartRateWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Ecor",
color: MyColor.navigate,
icon: MyIcon.power,
context: context,
nextWidget: ActivityEcorWidget(activity: activity),
nextWidget: ActivityEcorWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Speed /\nHeart Rate",
color: MyColor.navigate,
icon: MyIcon.speed,
context: context,
nextWidget: ActivitySpeedPerHeartRateWidget(activity: activity),
nextWidget: ActivitySpeedPerHeartRateWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Ground Time",
color: MyColor.navigate,
icon: MyIcon.groundTime,
context: context,
nextWidget: ActivityGroundTimeWidget(activity: activity),
nextWidget: ActivityGroundTimeWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Form Power",
color: MyColor.navigate,
icon: MyIcon.formPower,
context: context,
nextWidget: ActivityFormPowerWidget(activity: activity),
nextWidget: ActivityFormPowerWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Power Ratio",
color: MyColor.navigate,
icon: MyIcon.formPower,
context: context,
nextWidget: ActivityPowerRatioWidget(activity: activity),
nextWidget: ActivityPowerRatioWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Vertical\nOscillation",
color: MyColor.navigate,
icon: MyIcon.verticalOscillation,
context: context,
nextWidget: ActivityVerticalOscillationWidget(activity: activity),
nextWidget: ActivityVerticalOscillationWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Stride Ratio",
color: MyColor.navigate,
icon: MyIcon.strideRatio,
context: context,
nextWidget: ActivityStrideRatioWidget(activity: activity),
nextWidget: ActivityStrideRatioWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Cadence",
color: MyColor.navigate,
icon: MyIcon.cadence,
context: context,
nextWidget: ActivityStrydCadenceWidget(activity: activity),
nextWidget: ActivityStrydCadenceWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Leg Spring\nStiffness",
color: MyColor.navigate,
icon: MyIcon.legSpringStiffness,
context: context,
nextWidget: ActivityLegSpringStiffnessWidget(activity: activity),
nextWidget: ActivityLegSpringStiffnessWidget(
activity: activity,
athlete: athlete,
),
),
navigationButton(
title: "Metadata",
color: MyColor.settings,
icon: MyIcon.metaData,
context: context,
nextWidget: ActivityMetadataWidget(activity: activity),
nextWidget: ActivityMetadataWidget(
activity: activity,
athlete: athlete,
),
),
],
);
......
......@@ -22,8 +22,7 @@ class _ActivitiesListWidgetState extends State<ActivitiesListWidget> {
@override
initState() {
getActivities();
WidgetsBinding.instance
.addPostFrameCallback((_) => showMyFlushbar());
WidgetsBinding.instance.addPostFrameCallback((_) => showMyFlushbar());
super.initState();
}
......@@ -71,6 +70,7 @@ class _ActivitiesListWidgetState extends State<ActivitiesListWidget> {
MaterialPageRoute(
builder: (context) => ShowActivityScreen(
activity: activity,
athlete: widget.athlete,
),
),
);
......@@ -217,8 +217,7 @@ class _ActivitiesListWidgetState extends State<ActivitiesListWidget> {
duration: Duration(seconds: 3),
backgroundColor: Colors.yellow[900],
)..show(context);
}
else if(widget.athlete.password == null) {
} else if (widget.athlete.password == null) {
Flushbar(
message: "Strava password not provided yet!",
duration: Duration(seconds: 3),
......
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/weight.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
......@@ -8,8 +9,12 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityEcorWidget extends StatefulWidget {
final Activity activity;
final Athlete athlete;
ActivityEcorWidget({this.activity});
ActivityEcorWidget({
@required this.activity,
@required this.athlete,
});
@override
_ActivityEcorWidgetState createState() => _ActivityEcorWidgetState();
......@@ -34,7 +39,7 @@ class _ActivityEcorWidgetState extends State<ActivityEcorWidget> {
value.db.power != null &&
value.db.power > 100 &&
value.db.speed != null &&
value.db.speed != 0)
value.db.speed >= 1)
.toList();
if (ecorRecords.length > 0 && weight != null) {
......@@ -42,12 +47,17 @@ class _ActivityEcorWidgetState extends State<ActivityEcorWidget> {
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
children: <Widget>[
children: [
ActivityEcorChart(
records: ecorRecords,
activity: widget.activity,
athlete: widget.athlete,
weight: weight.db.value,
),
Text('${widget.athlete.db.recordAggregationCount} records are '
'aggregated into one point in the plot. Only records where '
'power > 0 W and speed > 1 m/s are shown.'),
Divider(),
ListTile(
leading: MyIcon.weight,
title: Text(weightString),
......@@ -74,9 +84,11 @@ class _ActivityEcorWidgetState extends State<ActivityEcorWidget> {
}
getData() async {
Activity activity = widget.activity;
records = await activity.records;
weight = await activity.getWeight();
records = await widget.activity.records;
weight = await Weight.getBy(
athletesId: widget.athlete.db.id,
date: widget.activity.db.timeCreated,
);
weightString = weight.db.value.toStringOrDashes(2) + " kg";
setState(() {});
}
......
import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/event.dart';
......@@ -7,8 +8,12 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityFormPowerWidget extends StatefulWidget {
final Activity activity;
final Athlete athlete;
ActivityFormPowerWidget({this.activity});
ActivityFormPowerWidget({
@required this.activity,
@required this.athlete,
});
@override
_ActivityFormPowerWidgetState createState() =>
......@@ -45,7 +50,12 @@ class _ActivityFormPowerWidgetState extends State<ActivityFormPowerWidget> {
ActivityFormPowerChart(
records: formPowerRecords,
activity: widget.activity,
athlete: widget.athlete,
),
Text('${widget.athlete.db.recordAggregationCount} records are '
'aggregated into one point in the plot. Only records where '
'0 W < form power < 200 W are shown.'),
Divider(),
ListTile(
leading: MyIcon.formPower,
title: Text(avgFormPowerString),
......
import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/event.dart';
......@@ -7,8 +8,12 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityGroundTimeWidget extends StatefulWidget {
final Activity activity;
final Athlete athlete;
ActivityGroundTimeWidget({this.activity});
ActivityGroundTimeWidget({
@required this.activity,
@required this.athlete,
});
@override
_ActivityGroundTimeWidgetState createState() =>
......@@ -43,7 +48,12 @@ class _ActivityGroundTimeWidgetState extends State<ActivityGroundTimeWidget> {
ActivityGroundTimeChart(
records: groundTimeRecords,
activity: widget.activity,
athlete: widget.athlete,
),
Text('${widget.athlete.db.recordAggregationCount} records are '
'aggregated into one point in the plot. Only records where '
'ground time > 0 ms are shown.'),
Divider(),
ListTile(
leading: MyIcon.average,
title: Text(avgGroundTimeString),
......
import 'package:encrateia/models/athlete.dart';
import 'package:encrateia/models/heart_rate_zone.dart';
import 'package:encrateia/models/heart_rate_zone_schema.dart';
import 'package:encrateia/widgets/charts/actitvity_charts/activity_heart_rate_chart.dart';
......@@ -9,8 +10,12 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityHeartRateWidget extends StatefulWidget {
final Activity activity;
final Athlete athlete;
ActivityHeartRateWidget({this.activity});
ActivityHeartRateWidget({
@required this.activity,
@required this.athlete,
});
@override
_ActivityHeartRateWidgetState createState() =>
......@@ -46,7 +51,12 @@ class _ActivityHeartRateWidgetState extends State<ActivityHeartRateWidget> {
records: heartRateRecords,
activity: widget.activity,
heartRateZones: heartRateZones,
athlete: widget.athlete,
),
Text('${widget.athlete.db.recordAggregationCount} records are '
'aggregated into one point in the plot. Only records where '
'heart rate > 10 bpm are shown.'),
Divider(),
ListTile(
leading: MyIcon.average,
title: Text(widget.activity.db.avgHeartRate.toString()),
......
import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/models/activity.dart';
import 'package:encrateia/models/event.dart';
......@@ -7,8 +8,12 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityLegSpringStiffnessWidget extends StatefulWidget {
final Activity activity;
final Athlete athlete;
ActivityLegSpringStiffnessWidget({this.activity});
ActivityLegSpringStiffnessWidget({
@required this.activity,
@required this.athlete,
});
@override
_ActivityLegSpringStiffnessWidgetState createState() =>
......@@ -45,7 +50,12 @@ class _ActivityLegSpringStiffnessWidgetState
ActivityLegSpringStiffnessChart(
records: legSpringStiffnessRecords,
activity: widget.activity,
athlete: widget.athlete,
),
Text('${widget.athlete.db.recordAggregationCount} records are '
'aggregated into one point in the plot. Only records where '
'leg spring stiffness > 0 kN/m are shown.'),
Divider(),
ListTile(
leading: MyIcon.average,
title: Text(avgLegSpringStiffnessString),
......
import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/utils/date_time_utils.dart';
import 'package:intl/intl.dart';
......@@ -6,12 +7,16 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityMetadataWidget extends StatelessWidget {
final Activity activity;
final Athlete athlete;
ActivityMetadataWidget({this.activity});
ActivityMetadataWidget({
@required this.activity,
@required this.athlete,
});
@override
Widget build(context) {
return ListTileTheme(
return ListTileTheme(
iconColor: Colors.deepOrange,
child: ListView(
padding: EdgeInsets.only(left: 25),
......
import 'package:encrateia/models/athlete.dart';
import 'package:flutter/material.dart';
import 'package:encrateia/utils/date_time_utils.dart';
import 'package:intl/intl.dart';
......@@ -6,19 +7,24 @@ import 'package:encrateia/utils/icon_utils.dart';
class ActivityOverviewWidget extends StatelessWidget {
final Activity activity;
final Athlete athlete;
ActivityOverviewWidget({this.activity});