This repository has been archived on 2024-03-27. You can view files and clone it, but cannot push or open issues/pull-requests.
encrateia/lib/models/activity.dart

862 lines
30 KiB
Dart

import 'dart:developer';
import 'dart:io';
import 'package:fit_parser/fit_parser.dart';
// ignore: implementation_imports
import 'package:fit_parser/src/value.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqfentity_gen/sqfentity_gen.dart';
import 'package:strava_flutter/domain/model/model_authentication_scopes.dart';
import 'package:strava_flutter/domain/model/model_summary_activity.dart';
import 'package:strava_flutter/strava_client.dart';
import '/model/model.dart'
show DbActivity, DbEvent, DbHeartRateZone, DbInterval, DbLap, DbPowerZone;
import '/secrets/secrets.dart';
import '/utils/date_time_utils.dart';
import '/utils/enums.dart';
import 'activity_tagging.dart';
import 'athlete.dart';
import 'bar_zone.dart';
import 'event.dart';
import 'heart_rate_zone.dart';
import 'heart_rate_zone_schema.dart';
import 'interval.dart' as encrateia;
import 'lap.dart';
import 'power_zone.dart';
import 'power_zone_schema.dart';
import 'record_list.dart';
import 'strava_fit_download.dart';
import 'tag.dart';
import 'weight.dart';
class Activity {
Activity();
Activity._fromDb(this._db);
Activity.fromStrava({
@required SummaryActivity summaryActivity,
@required Athlete athlete,
}) {
_db = DbActivity()
..athletesId = athlete.id
..name = summaryActivity.name
..type = summaryActivity.type
..distance = summaryActivity.distance.toInt()
..stravaId = summaryActivity.id;
}
Activity.fromLocalDirectory({@required Athlete athlete}) {
_db = DbActivity()
..athletesId = athlete.id
..stravaId = DateTime.now().millisecondsSinceEpoch
..name = 'new activity';
}
Activity.manual({@required Athlete athlete}) {
_db = DbActivity()
..athletesId = athlete.id
..stravaId = DateTime.now().millisecondsSinceEpoch
..movingTime = 0
..totalAscent = 0
..totalDescent = 0
..totalDistance = 0
..avgHeartRate = 0
..avgPower = 0
..timeCreated = DateTime.now();
}
DbActivity _db;
List<Event> cachedRecords = <Event>[];
List<Lap> cachedLaps = <Lap>[];
List<encrateia.Interval> cachedIntervals = <encrateia.Interval>[];
List<Tag> cachedTags = <Tag>[];
double glidingMeasureAttribute;
double cachedWeight;
double cachedEcor;
PowerZoneSchema _powerZoneSchema;
PowerZone _powerZone;
HeartRateZone _heartRateZone;
HeartRateZoneSchema _heartRateZoneSchema;
int distanceSoFar;
int movingTimeSoFar;
// Getter
int get id => _db?.id;
DateTime get startTime => _db.startTime;
DateTime get timeCreated => _db.timeCreated;
DateTime get timeStamp => _db.timeStamp;
String get event => _db.event;
String get eventType => _db.eventType;
String get name => _db.name;
String get sport => _db.sport;
String get state => _db.state;
String get subSport => _db.subSport;
String get trigger => _db.trigger;
String get type => _db.type;
bool get excluded => _db.excluded;
bool get manual => _db.manual;
bool get nonParsable => _db.nonParsable;
double get avgFormPower => _db.avgFormPower;
double get avgFractionalCadence => _db.avgFractionalCadence;
double get avgGroundTime => _db.avgGroundTime;
double get avgLegSpringStiffness => _db.avgLegSpringStiffness;
double get avgPower => _db.avgPower;
double get avgPowerRatio => _db.avgPowerRatio;
double get avgRunningCadence => _db.avgRunningCadence;
double get avgSpeed => _db.avgSpeed;
double get avgSpeedByDistance => _db.avgSpeedByDistance;
double get avgSpeedByMeasurements => _db.avgSpeedByMeasurements;
double get avgSpeedBySpeed => _db.avgSpeedBySpeed;
double get avgStanceTime => _db.avgStanceTime;
double get avgStanceTimePercent => _db.avgStanceTimePercent;
double get avgStrideRatio => _db.avgStrideRatio;
double get avgStrydCadence => _db.avgStrydCadence;
double get avgVerticalOscillation => _db.avgVerticalOscillation;
double get cp => _db.cp;
double get ftp => _db.ftp;
double get maxFractionalCadence => _db.maxFractionalCadence;
double get maxSpeed => _db.maxSpeed;
double get minSpeed => _db.minSpeed;
double get necLat => _db.necLat;
double get necLong => _db.necLong;
double get sdevFormPower => _db.sdevFormPower;
double get sdevGroundTime => _db.sdevGroundTime;
double get sdevHeartRate => _db.sdevHeartRate;
double get sdevLegSpringStiffness => _db.sdevLegSpringStiffness;
double get sdevPace => _db.sdevPace;
double get sdevPower => _db.sdevPower;
double get sdevPowerRatio => _db.sdevPowerRatio;
double get sdevSpeed => _db.sdevSpeed;
double get sdevStrideRatio => _db.sdevStrideRatio;
double get sdevStrydCadence => _db.sdevStrydCadence;
double get sdevVerticalOscillation => _db.sdevVerticalOscillation;
double get startPositionLat => _db.startPositionLat;
double get startPositionLong => _db.startPositionLong;
double get swcLat => _db.swcLat;
double get swcLong => _db.swcLong;
double get totalFractionalCycles => _db.totalFractionalCycles;
int get athletesId => _db.athletesId;
int get avgHeartRate => _db.avgHeartRate;
int get avgTemperature => _db.avgTemperature;
int get distance => _db.distance;
int get maxHeartRate => _db.maxHeartRate;
int get maxPower => _db.maxPower;
int get maxRunningCadence => _db.maxRunningCadence;
int get maxTemperature => _db.maxTemperature;
int get minHeartRate => _db.minHeartRate;
int get minPower => _db.minPower;
int get movingTime => _db.movingTime;
int get numLaps => _db.numLaps;
int get numSessions => _db.numSessions;
int get serialNumber => _db.serialNumber;
int get stravaId => _db.stravaId;
int get totalAscent => _db.totalAscent;
int get totalCalories => _db.totalCalories;
int get totalDescent => _db.totalDescent;
int get totalDistance => _db.totalDistance;
int get totalElapsedTime => _db.totalElapsedTime;
int get totalStrides => _db.totalStrides;
int get totalTimerTime => _db.totalTimerTime;
int get totalTrainingEffect => _db.totalTrainingEffect;
// calculated from other attributes:
double get avgPace {
if (avgSpeed != null && avgSpeed != 0) {
return 50 / 3 / avgSpeed;
} else {
return null;
}
}
double get avgSpeedPerHeartRate {
if (avgSpeed != null && heartRateAvailable) {
return 60 * avgSpeed / avgHeartRate;
} else {
return null;
}
}
double get avgPowerPerHeartRate {
if (powerAvailable && heartRateAvailable) {
return avgPower / avgHeartRate;
} else {
return null;
}
}
int get elevationDifference =>
ascentAvailable ? totalAscent - totalDescent : null;
double get avgDoubleStrydCadence =>
cadenceAvailable ? avgStrydCadence * 2 : null;
// easier check for data availability
bool get powerAvailable => !<num>[null, -1].contains(avgPower);
bool get powerRatioAvailable => !<num>[null, -1].contains(avgPowerRatio);
bool get heartRateAvailable => !<num>[null, -1].contains(avgHeartRate);
bool get ascentAvailable => totalAscent != null && totalDescent != null;
bool get cadenceAvailable => !<num>[null, -1].contains(avgStrydCadence);
bool get speedAvailable => !<num>[null, 0, -1].contains(avgSpeedByDistance);
bool get paceAvailable => !<num>[null, -1].contains(avgPace);
bool get ecorAvailable => powerAvailable && speedAvailable;
bool get groundTimeAvailable => !<num>[null, -1].contains(avgGroundTime);
bool get formPowerAvailable => !<num>[null, -1].contains(avgFormPower);
bool get verticalOscillationAvailable =>
!<num>[null, -1].contains(avgVerticalOscillation);
bool get strideRatioAvailable => !<num>[null, -1].contains(avgStrideRatio);
bool get strideCadenceAvailable =>
!<num>[null, -1].contains(avgDoubleStrydCadence);
bool get legSpringStiffnessAvailable =>
!<num>[null, -1].contains(avgLegSpringStiffness);
// Setter
set excluded(bool value) => _db.excluded = value;
set ftp(double value) => _db.ftp = value;
set manual(bool value) => _db.manual = value;
set maxHeartRate(int value) => _db.maxHeartRate = value;
set name(String value) => _db.name = value;
set nonParsable(bool value) => _db.nonParsable = value;
set state(String value) => _db.state = value;
set timeStamp(DateTime value) => _db.timeStamp = value;
set timeCreated(DateTime value) => _db.timeCreated = value;
set totalDistance(int value) => _db.totalDistance = value;
set totalAscent(int value) => _db.totalAscent = value;
set totalDescent(int value) => _db.totalDescent = value;
set avgHeartRate(int value) => _db.avgHeartRate = value;
set avgPower(double value) => _db.avgPower = value;
set sport(String value) => _db.sport = value;
set subSport(String value) => _db.subSport = value;
set movingTime(int value) => _db.movingTime = value;
Future<BoolResult> delete() async => await _db.delete();
Future<int> save() async => await _db.save();
// intermediate data structures used for parsing
Lap currentLap;
List<Event> eventsForCurrentLap;
@override
String toString() => '< Activity | $name | $startTime >';
dynamic getAttribute(ActivityAttr activityAttr) {
switch (activityAttr) {
case ActivityAttr.avgPower:
return avgPower;
case ActivityAttr.ecor:
return cachedEcor;
case ActivityAttr.avgPowerPerHeartRate:
return avgPowerPerHeartRate;
case ActivityAttr.avgSpeedPerHeartRate:
return avgSpeedPerHeartRate;
case ActivityAttr.avgPowerRatio:
return avgPowerRatio;
case ActivityAttr.avgStrideRatio:
return avgStrideRatio;
case ActivityAttr.avgPace:
return avgPace;
case ActivityAttr.avgHeartRate:
return avgHeartRate.toDouble();
case ActivityAttr.avgDoubleStrydCadence:
return avgDoubleStrydCadence;
case ActivityAttr.ftp:
return ftp;
case ActivityAttr.distanceThisYear:
return distanceSoFar / 1000;
case ActivityAttr.movingTimeThisYear:
return movingTimeSoFar / 3600;
case ActivityAttr.distance:
return distance / 1000;
}
}
Future<void> download({@required Athlete athlete}) async {
await StravaFitDownload.byId(id: stravaId.toString(), athlete: athlete);
setState('downloaded');
}
static Future<void> importFromLocalDirectory({Athlete athlete}) async {
if (Platform.isAndroid) {
final List<Directory> directories = await getExternalStorageDirectories();
final Directory localDir = directories[0];
final Directory appDocDir = await getApplicationDocumentsDirectory();
final Stream<FileSystemEntity> entityStream = localDir.list(
recursive: false,
followLinks: false,
);
await for (final FileSystemEntity entity in entityStream) {
final Activity activity = Activity.fromLocalDirectory(athlete: athlete);
// ignore: avoid_slow_async_io
final bool isFile = await FileSystemEntity.isFile(entity.path);
if (isFile == true && entity.path.toLowerCase().endsWith('.fit')) {
final File sourceFile = File(entity.path);
await sourceFile.copy(
appDocDir.path + '/' + activity.stravaId.toString() + '.fit');
sourceFile.delete();
await activity.setState('downloaded');
}
}
} else {
final Directory appDocDir = await getApplicationDocumentsDirectory();
final Stream<FileSystemEntity> entityStream = appDocDir.list(
recursive: false,
followLinks: false,
);
await for (final FileSystemEntity entity in entityStream) {
final Activity activity = Activity.fromLocalDirectory(athlete: athlete);
// ignore: avoid_slow_async_io
final bool isFile = await FileSystemEntity.isFile(entity.path);
if (isFile == true && entity.path.endsWith('.fit')) {
final File sourceFile = File(entity.path);
await sourceFile.rename(
appDocDir.path + '/' + activity.stravaId.toString() + '.fit');
await activity.setState('downloaded');
}
}
}
}
Future<void> setState(String state) async {
_db.state = state;
await save();
}
Future<List<Event>> get records async {
if (cachedRecords.isEmpty) {
final List<DbEvent> dbEventList = await _db.getDbEvents().toList();
final Iterable<DbEvent> dbRecordList =
dbEventList.where((DbEvent dbEvent) => dbEvent.event == 'record');
cachedRecords = dbRecordList.map(Event.exDb).toList();
}
return cachedRecords;
}
Future<List<Event>> get events async {
final List<DbEvent> dbEventList = await _db.getDbEvents().toList();
return dbEventList.map(Event.exDb).toList();
}
Future<List<Tag>> get tags async {
if (cachedTags.isEmpty) {
cachedTags = await Tag.allByActivity(activity: this);
}
return cachedTags;
}
Future<double> get weight async {
if (cachedWeight == null) {
final Weight weight = await Weight.getBy(
athletesId: athletesId,
date: timeCreated,
);
cachedWeight = weight.value;
}
return cachedWeight;
}
Future<double> get ecor async {
if (cachedEcor == null) {
final double weightValue = await weight;
cachedEcor = (powerAvailable && speedAvailable && weightValue != null)
? avgPower / avgSpeed / weightValue
: -1;
}
return cachedEcor;
}
Future<bool> setAverages() async {
final RecordList<Event> recordList = RecordList<Event>(await records);
final RecordList<Event> eventList = RecordList<Event>(await events);
_db
..avgPower = recordList.avgPower()
..sdevPower = recordList.sdevPower()
..minPower = recordList.minPower()
..maxPower = recordList.maxPower()
..avgHeartRate = recordList.avgHeartRate()
..sdevHeartRate = recordList.sdevHeartRate()
..minHeartRate = recordList.minHeartRate()
..maxHeartRate = recordList.maxHeartRate()
..avgSpeedByMeasurements = recordList.avgSpeedByMeasurements()
..avgSpeedBySpeed = recordList.avgSpeedBySpeed()
..avgSpeedByDistance = recordList.avgSpeedByDistance()
..sdevSpeed = recordList.sdevSpeed()
..sdevPace = recordList.sdevPace()
..minSpeed = recordList.minSpeed()
..maxSpeed = recordList.maxSpeed()
..avgGroundTime = recordList.avgGroundTime()
..sdevGroundTime = recordList.sdevGroundTime()
..avgStrydCadence = recordList.avgStrydCadence()
..sdevStrydCadence = recordList.sdevStrydCadence()
..avgLegSpringStiffness = recordList.avgLegSpringStiffness()
..sdevLegSpringStiffness = recordList.sdevLegSpringStiffness()
..avgVerticalOscillation = recordList.avgVerticalOscillation()
..sdevVerticalOscillation = recordList.sdevVerticalOscillation()
..avgFormPower = recordList.avgFormPower()
..sdevFormPower = recordList.sdevFormPower()
..avgPowerRatio = recordList.avgPowerRatio()
..sdevPowerRatio = recordList.sdevPowerRatio()
..avgStrideRatio = recordList.avgStrideRatio()
..sdevStrideRatio = recordList.sdevStrideRatio()
..movingTime = eventList.movingTime();
final List<Lap> laps = await this.laps;
for (final Lap lap in laps) {
await lap.setAverages();
}
await save();
return true;
}
Stream<int> parse({@required Athlete athlete}) async* {
int counter = 0;
int percentage;
final Directory appDocDir = await getApplicationDocumentsDirectory();
final FitFile fitFile =
FitFile(path: appDocDir.path + '/$stravaId.fit').parse();
print('Parsing .fit-File for »$name« done.');
// delete left overs from prior runs:
await _db.getDbEvents().delete();
await _db.getDbLaps().delete();
_db
..avgPower = null
..minPower = null
..maxPower = null
..sdevPower = null
..avgGroundTime = null
..sdevGroundTime = null
..avgLegSpringStiffness = null
..sdevLegSpringStiffness = null
..avgFormPower = null
..sdevFormPower = null
..avgStrydCadence = null
..sdevStrydCadence = null
..sdevVerticalOscillation = null;
await save();
final int numberOfMessages = fitFile.dataMessages.length;
await resetCurrentLap();
for (final DataMessage dataMessage in fitFile.dataMessages) {
counter++;
percentage = await handleDataMessage(dataMessage: dataMessage);
if (counter % 100 == 0) {
percentage = (counter / numberOfMessages * 100).round();
yield percentage;
}
}
state = 'persisted';
yield -1;
await setAverages();
yield -2;
}
Future<int> handleDataMessage({DataMessage dataMessage}) async {
if (dataMessage.definitionMessage.globalMessageName == null) {
switch (dataMessage.definitionMessage.globalMessageNumber) {
// Garmin Forerunner 235uses global message numbers, which are not specified:
case 13:
case 22:
case 79:
case 104:
case 113:
case 140:
case 141:
case 147:
case 216:
// Garmin Forerunner 935 uses global message numbers, which are not specified:
case 233:
// Garmin Forerunner 245 Music uses global message numbers, which are not specified:
case 288:
case 325:
case 326:
case 327:
// Garmin Forerunner 945
case 329:
break;
default:
print('Message number ' +
dataMessage.definitionMessage.globalMessageNumber.toString() +
' unknown.');
debugger();
}
} else {
switch (dataMessage.definitionMessage.globalMessageName) {
case 'developer_data_id':
case 'device_info':
case 'device_settings':
case 'field_description':
case 'file_creator':
case 'user_profile':
case 'zones_target':
break; // we are currently not storing these kinds of messages
case 'file_id':
_db
..serialNumber =
(dataMessage.get('serial_number') as double)?.round()
..timeCreated =
dateTimeFromStrava(dataMessage.get('time_created') as double);
await save();
break;
case 'sport':
_db
..sportName = dataMessage.get('name') as String
..sport = dataMessage.get('sport') as String
..subSport = dataMessage.get('sub_sport') as String;
await save();
break;
case 'event':
final Event event = Event(
dataMessage: dataMessage,
activity: this,
);
eventsForCurrentLap.add(event);
break;
case 'record':
final Event event = Event.fromRecord(
dataMessage: dataMessage,
activity: this,
lapsId: currentLap.id,
);
eventsForCurrentLap.add(event);
break;
case 'lap':
final Event event = Event.fromLap(
dataMessage: dataMessage,
activity: this,
lapsId: currentLap.id,
);
eventsForCurrentLap.add(event);
final Lap lap = Lap.fromLap(
dataMessage: dataMessage,
activity: this,
lap: currentLap,
);
await lap.save();
await Event.upsertAll(eventsForCurrentLap);
await resetCurrentLap();
break;
case 'segment_lap':
case 'gps_metadata':
case 'training_file':
case 'workout':
case 'workout_step':
case 'hrv':
break;
case 'session':
final DateTime startTime =
dateTimeFromStrava(dataMessage.get('start_time') as double);
if (name == 'new activity') {
name =
'Activity on ' + DateFormat.yMMMMd('en_US').format(startTime);
}
_db
..timeStamp =
dateTimeFromStrava(dataMessage.get('timestamp') as double)
..startTime = startTime
..startPositionLat = dataMessage.get('start_position_lat') as double
..startPositionLong =
dataMessage.get('start_position_long') as double
..totalElapsedTime =
(dataMessage.get('total_elapsed_time') as double)?.round()
..totalTimerTime =
(dataMessage.get('total_timer_time') as double)?.round()
..distance = (distance ??
(dataMessage.get('total_distance') as double)?.round())
..totalDistance =
(dataMessage.get('total_distance') as double)?.round()
..totalStrides =
(dataMessage.get('total_strides') as double)?.round()
..necLat = dataMessage.get('nec_lat') as double
..necLong = dataMessage.get('nec_long') as double
..swcLat = dataMessage.get('swc_lat') as double
..swcLong = dataMessage.get('swc_long') as double
..totalCalories =
(dataMessage.get('total_calories') as double)?.round()
..avgSpeed = dataMessage.get('avg_speed') == 65.535
? dataMessage.get('enhanced_avg_speed') as double
: dataMessage.get('avg_speed') as double
..maxSpeed = dataMessage.get('max_speed') == 65.535
? dataMessage.get('enhanced_max_speed') as double
: dataMessage.get('max_speed') as double
..totalAscent = (dataMessage.get('total_ascent') as double)?.round()
..totalDescent =
(dataMessage.get('total_descent') as double)?.round()
..maxRunningCadence =
(dataMessage.get('max_running_cadence') as double)?.round()
..firstLapIndex =
(dataMessage.get('first_lap_index') as double)?.round()
..numLaps = (dataMessage.get('num_laps') as double)?.round()
..event = dataMessage.get('event')?.toString()
..type = type ?? dataMessage.get('event_type') as String
..eventType = dataMessage.get('event_type') as String
..eventGroup = (dataMessage.get('event_group') as double)?.round()
..trigger = dataMessage.get('trigger') as String
..avgVerticalOscillation =
dataMessage.get('avg_vertical_oscillation') as double
..avgStanceTimePercent =
dataMessage.get('avg_stance_time_percent') as double
..avgStanceTime = dataMessage.get('avg_stance_time') as double
..sport = dataMessage.get('sport') as String
..subSport = dataMessage.get('sub_sport') as String
..avgHeartRate =
(dataMessage.get('avg_heart_rate') as double)?.round()
..maxHeartRate =
(dataMessage.get('max_heart_rate') as double)?.round()
..avgRunningCadence =
dataMessage.get('avg_running_cadence') as double
..totalTrainingEffect =
(dataMessage.get('total_training_effect') as double)?.round()
..avgTemperature =
(dataMessage.get('avg_temperature') as double)?.round()
..maxTemperature =
(dataMessage.get('max_temperature') as double)?.round()
..avgFractionalCadence =
dataMessage.get('avg_fractional_cadence') as double
..maxFractionalCadence =
dataMessage.get('max_fractional_cadence') as double
..totalFractionalCycles =
dataMessage.get('total_fractional_cycles') as double;
await save();
break;
case 'activity':
_db
..numSessions = (dataMessage.get('num_sessions') as double)?.round()
..localTimestamp = dateTimeFromStrava(
dataMessage.get('local_timestamp') as double);
await save();
break;
default:
print('Messages of type ' +
dataMessage.definitionMessage.globalMessageName +
' are not implemented yet.');
print(dataMessage.values.map((Value v) => v.fieldName).toList());
// Use this debugger to implement additional message types!
// debugger();
}
}
return null;
}
Future<void> resetCurrentLap() async {
currentLap = Lap();
await currentLap.save();
eventsForCurrentLap = <Event>[];
}
static Future<void> queryStrava({Athlete athlete}) async {
final StravaClient stravaClient =
StravaClient(clientId: clientId, secret: secret);
await stravaClient.authentication
.authenticate(scopes: <AuthenticationScope>[
AuthenticationScope.read_all,
AuthenticationScope.profile_read_all,
AuthenticationScope.activity_read_all
], redirectUrl: 'stravaflutter://redirect');
final DateTime now = DateTime.now();
final DateTime startDate =
DateTime.now().subtract(Duration(days: athlete.downloadInterval));
final List<SummaryActivity> summaryActivities = await stravaClient
.activities
.listLoggedInAthleteActivities(now, startDate, 1, 100);
await Future.forEach(summaryActivities,
(SummaryActivity summaryActivity) async {
final Activity activity = Activity.fromStrava(
summaryActivity: summaryActivity,
athlete: athlete,
);
final List<DbActivity> existingAlready = await DbActivity()
.select()
.stravaId
.equals(activity.stravaId)
.toList();
if (existingAlready.isEmpty) {
await activity.save();
}
});
}
Future<PowerZoneSchema> get powerZoneSchema async =>
_powerZoneSchema ??= await PowerZoneSchema.getBy(
athletesId: athletesId,
date: timeCreated,
);
Future<HeartRateZoneSchema> get heartRateZoneSchema async =>
_heartRateZoneSchema ??= await HeartRateZoneSchema.getBy(
athletesId: athletesId,
date: timeCreated,
);
Future<PowerZone> get powerZone async {
if (_powerZone == null) {
final DbPowerZone dbPowerZone = await DbPowerZone()
.select()
.powerZoneSchemataId
.equals((await powerZoneSchema).id)
.and
.lowerLimit
.lessThanOrEquals(avgPower)
.and
.upperLimit
.greaterThanOrEquals(avgPower)
.toSingle();
_powerZone = PowerZone.exDb(dbPowerZone);
}
return _powerZone;
}
Future<HeartRateZone> get heartRateZone async {
if (_heartRateZone == null) {
final DbHeartRateZone dbHeartRateZone = await DbHeartRateZone()
.select()
.heartRateZoneSchemataId
.equals((await heartRateZoneSchema).id)
.and
.lowerLimit
.lessThanOrEquals(avgHeartRate)
.and
.upperLimit
.greaterThanOrEquals(avgHeartRate)
.toSingle();
_heartRateZone = HeartRateZone.exDb(dbHeartRateZone);
}
return _heartRateZone;
}
Future<void> autoTagger({@required Athlete athlete}) async {
final PowerZone powerZone = await this.powerZone;
if (powerZone.id != null) {
final Tag powerTag = await Tag.autoPowerTag(
athlete: athlete,
color: powerZone.color,
sortOrder: powerZone.lowerLimit,
name: powerZone.name,
);
await ActivityTagging.createBy(
activity: this,
tag: powerTag,
system: true,
);
}
final HeartRateZone heartRateZone = await this.heartRateZone;
if (heartRateZone.id != null) {
final Tag heartRateTag = await Tag.autoHeartRateTag(
athlete: athlete,
color: heartRateZone.color,
sortOrder: heartRateZone.lowerLimit,
name: heartRateZone.name,
);
await ActivityTagging.createBy(
activity: this,
tag: heartRateTag,
system: true,
);
}
for (final Lap lap in await laps) {
await lap.autoTagger(athlete: athlete);
}
}
Future<List<BarZone>> powerZoneCounts() async {
final PowerZoneSchema powerZoneSchema = await this.powerZoneSchema;
final List<Event> records = await this.records;
final List<Event> powerRecords =
records.where((Event record) => record.power != null).toList();
final List<BarZone> powerZoneCounts = await RecordList<Event>(powerRecords)
.powerZoneCounts(powerZoneSchema: powerZoneSchema);
return powerZoneCounts;
}
Future<List<BarZone>> heartRateZoneCounts() async {
final HeartRateZoneSchema heartRateZoneSchema =
await this.heartRateZoneSchema;
final List<Event> records = await this.records;
final List<Event> heartRateRecords =
records.where((Event record) => record.heartRate != null).toList();
final List<BarZone> heartRateZoneCounts =
await RecordList<Event>(heartRateRecords)
.heartRateZoneCounts(heartRateZoneSchema: heartRateZoneSchema);
return heartRateZoneCounts;
}
Future<List<Lap>> get laps async {
if (cachedLaps.isEmpty) {
int counter = 1;
final List<DbLap> dbLapList = await _db.getDbLaps().toList();
cachedLaps = dbLapList.map(Lap.exDb).toList();
for (final Lap lap in cachedLaps) {
lap
..activity = this
..index = counter;
counter = counter + 1;
}
}
return cachedLaps;
}
Future<List<encrateia.Interval>> get intervals async {
if (cachedIntervals.isEmpty) {
int counter = 1;
final List<DbInterval> dbIntervalList =
await _db.getDbIntervals().toList();
cachedIntervals = dbIntervalList.map(encrateia.Interval.exDb).toList();
for (final encrateia.Interval interval in cachedIntervals) {
interval
..activity = this
..index = counter;
counter = counter + 1;
}
}
return cachedIntervals;
}
static Future<Activity> byId(int id) async {
final DbActivity dbActivity = await DbActivity().getById(id);
return exDb(dbActivity);
}
Future<BoolResult> deleteEvents() async => await _db.getDbEvents().delete();
Future<BoolResult> deleteLaps() async => await _db.getDbLaps().delete();
static Activity exDb(DbActivity db) => Activity._fromDb(db);
}