feat(exécution): dernière performance et meilleur score par exercice (ticket #81)

Ajoute la migration Drift v17 (colonne sourceExerciseIdSnapshot sur
workout_history_set_results et workout_history_step_results, index
associés), le port ExercisePerformanceReferenceRepository et son
implémentation Drift, et le use case
ExercisePerformanceReferenceUseCase (dernière performance + meilleur
score par métrique active pour un exercice donné).

Validé GO par Main : dart analyze propre (1 lint mineur de style),
178 tests (4 échecs préexistants sans rapport avec ce lot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 07:54:49 +02:00
parent e05405157a
commit ac51c16b51
10 changed files with 1073 additions and 3 deletions

View File

@ -13,6 +13,7 @@ abstract interface class AppDependencies {
ActiveExerciseStepUseCases get activeExerciseStepUseCases;
CloseWorkoutSessionUseCase get closeWorkoutSessionUseCase;
WorkoutHistoryUseCases get workoutHistoryUseCases;
ExercisePerformanceReferenceUseCase get exercisePerformanceReferenceUseCase;
SyncUseCases get syncUseCases;
ShareUseCases get shareUseCases;
}
@ -29,6 +30,7 @@ final class AppBootstrap implements AppDependencies {
required this.activeExerciseStepUseCases,
required this.closeWorkoutSessionUseCase,
required this.workoutHistoryUseCases,
required this.exercisePerformanceReferenceUseCase,
required this.syncUseCases,
required this.shareUseCases,
required this.syncGateway,
@ -54,6 +56,8 @@ final class AppBootstrap implements AppDependencies {
@override
final WorkoutHistoryUseCases workoutHistoryUseCases;
@override
final ExercisePerformanceReferenceUseCase exercisePerformanceReferenceUseCase;
@override
final SyncUseCases syncUseCases;
@override
final ShareUseCases shareUseCases;
@ -77,6 +81,8 @@ final class AppBootstrap implements AppDependencies {
final templateRepository = DriftWorkoutTemplateRepository(database);
final activeSessionRepository = DriftActiveSessionRepository(database);
final historyRepository = DriftWorkoutHistoryRepository(database);
final performanceReferenceRepository =
DriftExercisePerformanceReferenceRepository(database);
final ids = LocalIdGenerator();
const clock = SystemClock();
const originDeviceId = 'local-device';
@ -159,6 +165,9 @@ final class AppBootstrap implements AppDependencies {
repository: historyRepository,
clock: clock,
),
exercisePerformanceReferenceUseCase: ExercisePerformanceReferenceUseCase(
repository: performanceReferenceRepository,
),
syncUseCases: SyncUseCases(
tokenStore: const SecureStorageAuthTokenStore(),
remoteSyncApi: remoteSyncApi,

View File

@ -466,6 +466,106 @@ abstract interface class WorkoutHistoryRepository {
Future<void> delete(String id, DateTime deletedAt);
}
final class ActivePerformanceMeasures {
const ActivePerformanceMeasures({
required this.timeEnabled,
required this.repsEnabled,
required this.scoreEnabled,
this.scoreInputMode = ScoreInputMode.manual,
});
final bool timeEnabled;
final bool repsEnabled;
final bool scoreEnabled;
final ScoreInputMode scoreInputMode;
bool get hasAny => timeEnabled || repsEnabled || scoreEnabled;
}
enum PerformanceMetric { score, reps, time }
final class WorkoutHistorySetPerformance {
const WorkoutHistorySetPerformance({
required this.workoutHistoryId,
required this.startedAt,
required this.setIndex,
required this.exerciseNameSnapshot,
required this.scoreInputModeSnapshot,
this.actualTimeMs,
this.actualReps,
this.actualScore,
this.actualScoreTimeMs,
this.completedAt,
});
final String workoutHistoryId;
final DateTime startedAt;
final int setIndex;
final String exerciseNameSnapshot;
final ScoreInputMode scoreInputModeSnapshot;
final int? actualTimeMs;
final int? actualReps;
final double? actualScore;
final int? actualScoreTimeMs;
final DateTime? completedAt;
}
final class WorkoutHistoryMetricPerformance {
const WorkoutHistoryMetricPerformance({
required this.workoutHistoryId,
required this.startedAt,
required this.setIndex,
required this.exerciseNameSnapshot,
required this.metric,
required this.scoreInputModeSnapshot,
this.actualTimeMs,
this.actualReps,
this.actualScore,
this.actualScoreTimeMs,
this.completedAt,
});
final String workoutHistoryId;
final DateTime startedAt;
final int setIndex;
final String exerciseNameSnapshot;
final PerformanceMetric metric;
final ScoreInputMode scoreInputModeSnapshot;
final int? actualTimeMs;
final int? actualReps;
final double? actualScore;
final int? actualScoreTimeMs;
final DateTime? completedAt;
}
final class ExercisePerformanceReference {
const ExercisePerformanceReference({
required this.hasAnyHistoryForExercise,
this.last,
this.record,
});
final bool hasAnyHistoryForExercise;
final WorkoutHistorySetPerformance? last;
final WorkoutHistoryMetricPerformance? record;
}
abstract interface class ExercisePerformanceReferenceRepository {
Future<bool> hasAnyCompletedHistoryForExercise(String exerciseId);
Future<WorkoutHistorySetPerformance?> findLatestSetPerformance({
required String exerciseId,
required ActivePerformanceMeasures activeMeasures,
required int currentSetIndex,
});
Future<WorkoutHistoryMetricPerformance?> findBestMetricPerformance({
required String exerciseId,
required PerformanceMetric metric,
required ScoreInputMode scoreInputMode,
});
}
final class SyncRunSummary {
const SyncRunSummary({
required this.pushedChanges,

View File

@ -3329,6 +3329,9 @@ final class CloseWorkoutSessionUseCase {
final stepResults = await sessionRepository.listExerciseStepResults(
sessionId,
);
final snapshots = _exerciseSnapshotsById(
session.resolvedTemplateSnapshotJson,
);
final historyId = ids.newId();
final historyResults = _historyResultsFromActiveResults(
historyId: historyId,
@ -3341,6 +3344,7 @@ final class CloseWorkoutSessionUseCase {
final historyStepResults = _historyStepResultsFromActiveResults(
historyId: historyId,
results: stepResults,
resolvedTemplateSnapshotJson: session.resolvedTemplateSnapshotJson,
now: now,
ids: ids,
originDeviceId: originDeviceId,
@ -3377,6 +3381,8 @@ final class CloseWorkoutSessionUseCase {
'scoreInputModeSnapshot': result.scoreInputModeSnapshot.name,
'scoreLabelSnapshot': result.scoreLabelSnapshot,
'scoreUnitSnapshot': result.scoreUnitSnapshot,
'sourceExerciseIdSnapshot':
snapshots[result.exerciseSnapshotId]?.sourceExerciseId,
'status': result.status.name,
},
)
@ -3407,6 +3413,8 @@ final class CloseWorkoutSessionUseCase {
'actualScore': result.actualScore,
'actualScoreTimeMs': result.actualScoreTimeMs,
'note': result.note,
'sourceExerciseIdSnapshot':
snapshots[result.exerciseSnapshotId]?.sourceExerciseId,
},
)
.toList(),
@ -3571,6 +3579,59 @@ void _validateExerciseSteps(List<ExerciseStep> steps) {
}
}
final class ExercisePerformanceReferenceUseCase {
const ExercisePerformanceReferenceUseCase({required this.repository});
final ExercisePerformanceReferenceRepository repository;
Future<ExercisePerformanceReference> getExercisePerformanceReference({
required String exerciseId,
required ActivePerformanceMeasures activeMeasures,
required int currentSetIndex,
}) async {
final hasAnyHistory = await repository.hasAnyCompletedHistoryForExercise(
exerciseId,
);
if (!activeMeasures.hasAny) {
return ExercisePerformanceReference(
hasAnyHistoryForExercise: hasAnyHistory,
);
}
final last = await repository.findLatestSetPerformance(
exerciseId: exerciseId,
activeMeasures: activeMeasures,
currentSetIndex: currentSetIndex,
);
final recordMetric = _recordMetricFor(activeMeasures);
final record = recordMetric == null
? null
: await repository.findBestMetricPerformance(
exerciseId: exerciseId,
metric: recordMetric,
scoreInputMode: activeMeasures.scoreInputMode,
);
return ExercisePerformanceReference(
hasAnyHistoryForExercise: hasAnyHistory,
last: last,
record: record,
);
}
}
PerformanceMetric? _recordMetricFor(ActivePerformanceMeasures measures) {
if (measures.scoreEnabled) {
return PerformanceMetric.score;
}
if (measures.repsEnabled) {
return PerformanceMetric.reps;
}
if (measures.timeEnabled) {
return PerformanceMetric.time;
}
return null;
}
void _validateAuthInput({required String email, required String password}) {
final normalizedEmail = email.trim();
final hasBasicEmailShape = RegExp(
@ -4055,6 +4116,7 @@ List<WorkoutHistorySetResult> _historyResultsFromActiveResults({
result.scoreLabelSnapshot ?? snapshot?.scoreLabelSnapshot,
scoreUnitSnapshot:
result.scoreUnitSnapshot ?? snapshot?.scoreUnitSnapshot,
sourceExerciseIdSnapshot: snapshot?.sourceExerciseId,
startedAt: result.startedAt,
completedAt: result.completedAt,
status: result.status,
@ -4089,6 +4151,7 @@ Map<String, _ResolvedExerciseSnapshot> _exerciseSnapshotsById(
exerciseSnapshotId: id,
programNameSnapshot: programName,
exerciseNameSnapshot: exercise['exerciseNameSnapshot'] as String? ?? id,
sourceExerciseId: exercise['sourceExerciseId'] as String?,
timeEnabled: exercise['timeEnabled'] == true,
repsEnabled: exercise['repsEnabled'] == true,
scoreEnabled: exercise['scoreEnabled'] == true,
@ -4118,6 +4181,7 @@ final class _ResolvedExerciseSnapshot {
required this.exerciseSnapshotId,
required this.programNameSnapshot,
required this.exerciseNameSnapshot,
this.sourceExerciseId,
required this.timeEnabled,
required this.repsEnabled,
required this.scoreEnabled,
@ -4136,6 +4200,7 @@ final class _ResolvedExerciseSnapshot {
final String exerciseSnapshotId;
final String programNameSnapshot;
final String exerciseNameSnapshot;
final String? sourceExerciseId;
final bool timeEnabled;
final bool repsEnabled;
final bool scoreEnabled;
@ -4181,11 +4246,14 @@ List<ExerciseStep> _exerciseStepsFromSnapshot(Object? value) {
List<WorkoutHistoryStepResult> _historyStepResultsFromActiveResults({
required String historyId,
required List<ActiveExerciseStepResult> results,
required String resolvedTemplateSnapshotJson,
required DateTime now,
required IdGenerator ids,
required String originDeviceId,
}) {
final snapshots = _exerciseSnapshotsById(resolvedTemplateSnapshotJson);
return results.map((result) {
final snapshot = snapshots[result.exerciseSnapshotId];
return WorkoutHistoryStepResult(
metadata: _newMetadata(ids, originDeviceId, now),
workoutHistoryId: historyId,
@ -4214,6 +4282,7 @@ List<WorkoutHistoryStepResult> _historyStepResultsFromActiveResults({
actualScore: result.actualScore,
actualScoreTimeMs: result.actualScoreTimeMs,
note: result.note,
sourceExerciseIdSnapshot: snapshot?.sourceExerciseId,
);
}).toList();
}

View File

@ -1444,6 +1444,7 @@ final class WorkoutHistorySetResult {
this.scoreInputModeSnapshot = ScoreInputMode.manual,
this.scoreLabelSnapshot,
this.scoreUnitSnapshot,
this.sourceExerciseIdSnapshot,
this.startedAt,
this.completedAt,
this.status = SetResultStatus.completed,
@ -1493,6 +1494,7 @@ final class WorkoutHistorySetResult {
final ScoreInputMode scoreInputModeSnapshot;
final String? scoreLabelSnapshot;
final String? scoreUnitSnapshot;
final String? sourceExerciseIdSnapshot;
final DateTime? startedAt;
final DateTime? completedAt;
final SetResultStatus status;
@ -1527,6 +1529,7 @@ final class WorkoutHistoryStepResult {
this.actualScore,
this.actualScoreTimeMs,
this.note,
this.sourceExerciseIdSnapshot,
}) {
_validateExerciseStepResult(
programIndex: programIndex,
@ -1577,6 +1580,7 @@ final class WorkoutHistoryStepResult {
final double? actualScore;
final int? actualScoreTimeMs;
final String? note;
final String? sourceExerciseIdSnapshot;
}
String _nonBlank(String? value, String label) {

View File

@ -48,7 +48,7 @@ final class AppDatabase extends _$AppDatabase {
}
@override
int get schemaVersion => 16;
int get schemaVersion => 17;
@override
MigrationStrategy get migration {
@ -113,6 +113,9 @@ final class AppDatabase extends _$AppDatabase {
if (from < 16) {
await _migrateToSchema16(migrator);
}
if (from < 17) {
await _migrateToSchema17();
}
await _createIndexes();
},
beforeOpen: (details) async {
@ -229,10 +232,24 @@ final class AppDatabase extends _$AppDatabase {
'CREATE INDEX IF NOT EXISTS idx_workout_history_set_results_history_id '
'ON workout_history_set_results (workout_history_id)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS '
'idx_workout_history_set_results_source_exercise '
'ON workout_history_set_results (source_exercise_id_snapshot, '
'set_index) WHERE deleted_at IS NULL AND '
'source_exercise_id_snapshot IS NOT NULL',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_workout_history_step_results_history_id '
'ON workout_history_step_results (workout_history_id)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS '
'idx_workout_history_step_results_source_exercise '
'ON workout_history_step_results (source_exercise_id_snapshot, '
'set_index, step_index) WHERE deleted_at IS NULL AND '
'source_exercise_id_snapshot IS NOT NULL',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_change_log_entity '
'ON change_log (entity_type, entity_id)',
@ -697,6 +714,79 @@ CREATE TABLE IF NOT EXISTS active_set_timer_states (
);
}
Future<void> _migrateToSchema17() async {
await _addColumnIfMissing(
tableName: 'workout_history_set_results',
columnName: 'source_exercise_id_snapshot',
definition: 'source_exercise_id_snapshot TEXT',
);
await _addColumnIfMissing(
tableName: 'workout_history_step_results',
columnName: 'source_exercise_id_snapshot',
definition: 'source_exercise_id_snapshot TEXT',
);
await _backfillWorkoutHistorySetSourceExerciseIds();
await _backfillWorkoutHistoryStepSourceExerciseIds();
}
Future<void> _backfillWorkoutHistorySetSourceExerciseIds() async {
await customStatement(r'''
UPDATE workout_history_set_results AS result
SET source_exercise_id_snapshot = (
SELECT json_extract(exercise.value, '$.sourceExerciseId')
FROM workout_history AS history,
json_each(
COALESCE(
json_extract(
history.history_snapshot_json,
'$.resolvedTemplateSnapshotJson'
),
history.history_snapshot_json
),
'$.programs'
) AS program,
json_each(
json_extract(program.value, '$.programSnapshotJson'),
'$.exercises'
) AS exercise
WHERE history.id = result.workout_history_id
AND json_extract(program.value, '$.id') = result.program_snapshot_id
AND json_extract(exercise.value, '$.id') = result.exercise_snapshot_id
LIMIT 1
)
WHERE result.source_exercise_id_snapshot IS NULL
''');
}
Future<void> _backfillWorkoutHistoryStepSourceExerciseIds() async {
await customStatement(r'''
UPDATE workout_history_step_results AS result
SET source_exercise_id_snapshot = (
SELECT json_extract(exercise.value, '$.sourceExerciseId')
FROM workout_history AS history,
json_each(
COALESCE(
json_extract(
history.history_snapshot_json,
'$.resolvedTemplateSnapshotJson'
),
history.history_snapshot_json
),
'$.programs'
) AS program,
json_each(
json_extract(program.value, '$.programSnapshotJson'),
'$.exercises'
) AS exercise
WHERE history.id = result.workout_history_id
AND json_extract(program.value, '$.id') = result.program_snapshot_id
AND json_extract(exercise.value, '$.id') = result.exercise_snapshot_id
LIMIT 1
)
WHERE result.source_exercise_id_snapshot IS NULL
''');
}
Future<void> _addColumnIfMissing({
required String tableName,
required String columnName,

View File

@ -24020,6 +24020,17 @@ class $WorkoutHistorySetResultsTable extends WorkoutHistorySetResults
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _sourceExerciseIdSnapshotMeta =
const VerificationMeta('sourceExerciseIdSnapshot');
@override
late final GeneratedColumn<String> sourceExerciseIdSnapshot =
GeneratedColumn<String>(
'source_exercise_id_snapshot',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _startedAtMeta = const VerificationMeta(
'startedAt',
);
@ -24087,6 +24098,7 @@ class $WorkoutHistorySetResultsTable extends WorkoutHistorySetResults
actualScoreTimeMs,
scoreLabelSnapshot,
scoreUnitSnapshot,
sourceExerciseIdSnapshot,
startedAt,
completedAt,
status,
@ -24410,6 +24422,15 @@ class $WorkoutHistorySetResultsTable extends WorkoutHistorySetResults
),
);
}
if (data.containsKey('source_exercise_id_snapshot')) {
context.handle(
_sourceExerciseIdSnapshotMeta,
sourceExerciseIdSnapshot.isAcceptableOrUnknown(
data['source_exercise_id_snapshot']!,
_sourceExerciseIdSnapshotMeta,
),
);
}
if (data.containsKey('started_at')) {
context.handle(
_startedAtMeta,
@ -24575,6 +24596,10 @@ class $WorkoutHistorySetResultsTable extends WorkoutHistorySetResults
DriftSqlType.string,
data['${effectivePrefix}score_unit_snapshot'],
),
sourceExerciseIdSnapshot: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}source_exercise_id_snapshot'],
),
startedAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}started_at'],
@ -24631,6 +24656,7 @@ class WorkoutHistorySetResult extends DataClass
final int? actualScoreTimeMs;
final String? scoreLabelSnapshot;
final String? scoreUnitSnapshot;
final String? sourceExerciseIdSnapshot;
final DateTime? startedAt;
final DateTime? completedAt;
final String status;
@ -24668,6 +24694,7 @@ class WorkoutHistorySetResult extends DataClass
this.actualScoreTimeMs,
this.scoreLabelSnapshot,
this.scoreUnitSnapshot,
this.sourceExerciseIdSnapshot,
this.startedAt,
this.completedAt,
required this.status,
@ -24740,6 +24767,11 @@ class WorkoutHistorySetResult extends DataClass
if (!nullToAbsent || scoreUnitSnapshot != null) {
map['score_unit_snapshot'] = Variable<String>(scoreUnitSnapshot);
}
if (!nullToAbsent || sourceExerciseIdSnapshot != null) {
map['source_exercise_id_snapshot'] = Variable<String>(
sourceExerciseIdSnapshot,
);
}
if (!nullToAbsent || startedAt != null) {
map['started_at'] = Variable<DateTime>(startedAt);
}
@ -24815,6 +24847,9 @@ class WorkoutHistorySetResult extends DataClass
scoreUnitSnapshot: scoreUnitSnapshot == null && nullToAbsent
? const Value.absent()
: Value(scoreUnitSnapshot),
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot == null && nullToAbsent
? const Value.absent()
: Value(sourceExerciseIdSnapshot),
startedAt: startedAt == null && nullToAbsent
? const Value.absent()
: Value(startedAt),
@ -24890,6 +24925,9 @@ class WorkoutHistorySetResult extends DataClass
scoreUnitSnapshot: serializer.fromJson<String?>(
json['scoreUnitSnapshot'],
),
sourceExerciseIdSnapshot: serializer.fromJson<String?>(
json['sourceExerciseIdSnapshot'],
),
startedAt: serializer.fromJson<DateTime?>(json['startedAt']),
completedAt: serializer.fromJson<DateTime?>(json['completedAt']),
status: serializer.fromJson<String>(json['status']),
@ -24938,6 +24976,9 @@ class WorkoutHistorySetResult extends DataClass
'actualScoreTimeMs': serializer.toJson<int?>(actualScoreTimeMs),
'scoreLabelSnapshot': serializer.toJson<String?>(scoreLabelSnapshot),
'scoreUnitSnapshot': serializer.toJson<String?>(scoreUnitSnapshot),
'sourceExerciseIdSnapshot': serializer.toJson<String?>(
sourceExerciseIdSnapshot,
),
'startedAt': serializer.toJson<DateTime?>(startedAt),
'completedAt': serializer.toJson<DateTime?>(completedAt),
'status': serializer.toJson<String>(status),
@ -24978,6 +25019,7 @@ class WorkoutHistorySetResult extends DataClass
Value<int?> actualScoreTimeMs = const Value.absent(),
Value<String?> scoreLabelSnapshot = const Value.absent(),
Value<String?> scoreUnitSnapshot = const Value.absent(),
Value<String?> sourceExerciseIdSnapshot = const Value.absent(),
Value<DateTime?> startedAt = const Value.absent(),
Value<DateTime?> completedAt = const Value.absent(),
String? status,
@ -25034,6 +25076,9 @@ class WorkoutHistorySetResult extends DataClass
scoreUnitSnapshot: scoreUnitSnapshot.present
? scoreUnitSnapshot.value
: this.scoreUnitSnapshot,
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot.present
? sourceExerciseIdSnapshot.value
: this.sourceExerciseIdSnapshot,
startedAt: startedAt.present ? startedAt.value : this.startedAt,
completedAt: completedAt.present ? completedAt.value : this.completedAt,
status: status ?? this.status,
@ -25129,6 +25174,9 @@ class WorkoutHistorySetResult extends DataClass
scoreUnitSnapshot: data.scoreUnitSnapshot.present
? data.scoreUnitSnapshot.value
: this.scoreUnitSnapshot,
sourceExerciseIdSnapshot: data.sourceExerciseIdSnapshot.present
? data.sourceExerciseIdSnapshot.value
: this.sourceExerciseIdSnapshot,
startedAt: data.startedAt.present ? data.startedAt.value : this.startedAt,
completedAt: data.completedAt.present
? data.completedAt.value
@ -25173,6 +25221,7 @@ class WorkoutHistorySetResult extends DataClass
..write('actualScoreTimeMs: $actualScoreTimeMs, ')
..write('scoreLabelSnapshot: $scoreLabelSnapshot, ')
..write('scoreUnitSnapshot: $scoreUnitSnapshot, ')
..write('sourceExerciseIdSnapshot: $sourceExerciseIdSnapshot, ')
..write('startedAt: $startedAt, ')
..write('completedAt: $completedAt, ')
..write('status: $status')
@ -25215,6 +25264,7 @@ class WorkoutHistorySetResult extends DataClass
actualScoreTimeMs,
scoreLabelSnapshot,
scoreUnitSnapshot,
sourceExerciseIdSnapshot,
startedAt,
completedAt,
status,
@ -25256,6 +25306,7 @@ class WorkoutHistorySetResult extends DataClass
other.actualScoreTimeMs == this.actualScoreTimeMs &&
other.scoreLabelSnapshot == this.scoreLabelSnapshot &&
other.scoreUnitSnapshot == this.scoreUnitSnapshot &&
other.sourceExerciseIdSnapshot == this.sourceExerciseIdSnapshot &&
other.startedAt == this.startedAt &&
other.completedAt == this.completedAt &&
other.status == this.status);
@ -25296,6 +25347,7 @@ class WorkoutHistorySetResultsCompanion
final Value<int?> actualScoreTimeMs;
final Value<String?> scoreLabelSnapshot;
final Value<String?> scoreUnitSnapshot;
final Value<String?> sourceExerciseIdSnapshot;
final Value<DateTime?> startedAt;
final Value<DateTime?> completedAt;
final Value<String> status;
@ -25334,6 +25386,7 @@ class WorkoutHistorySetResultsCompanion
this.actualScoreTimeMs = const Value.absent(),
this.scoreLabelSnapshot = const Value.absent(),
this.scoreUnitSnapshot = const Value.absent(),
this.sourceExerciseIdSnapshot = const Value.absent(),
this.startedAt = const Value.absent(),
this.completedAt = const Value.absent(),
this.status = const Value.absent(),
@ -25373,6 +25426,7 @@ class WorkoutHistorySetResultsCompanion
this.actualScoreTimeMs = const Value.absent(),
this.scoreLabelSnapshot = const Value.absent(),
this.scoreUnitSnapshot = const Value.absent(),
this.sourceExerciseIdSnapshot = const Value.absent(),
this.startedAt = const Value.absent(),
this.completedAt = const Value.absent(),
this.status = const Value.absent(),
@ -25428,6 +25482,7 @@ class WorkoutHistorySetResultsCompanion
Expression<int>? actualScoreTimeMs,
Expression<String>? scoreLabelSnapshot,
Expression<String>? scoreUnitSnapshot,
Expression<String>? sourceExerciseIdSnapshot,
Expression<DateTime>? startedAt,
Expression<DateTime>? completedAt,
Expression<String>? status,
@ -25480,6 +25535,8 @@ class WorkoutHistorySetResultsCompanion
if (scoreLabelSnapshot != null)
'score_label_snapshot': scoreLabelSnapshot,
if (scoreUnitSnapshot != null) 'score_unit_snapshot': scoreUnitSnapshot,
if (sourceExerciseIdSnapshot != null)
'source_exercise_id_snapshot': sourceExerciseIdSnapshot,
if (startedAt != null) 'started_at': startedAt,
if (completedAt != null) 'completed_at': completedAt,
if (status != null) 'status': status,
@ -25521,6 +25578,7 @@ class WorkoutHistorySetResultsCompanion
Value<int?>? actualScoreTimeMs,
Value<String?>? scoreLabelSnapshot,
Value<String?>? scoreUnitSnapshot,
Value<String?>? sourceExerciseIdSnapshot,
Value<DateTime?>? startedAt,
Value<DateTime?>? completedAt,
Value<String>? status,
@ -25563,6 +25621,8 @@ class WorkoutHistorySetResultsCompanion
actualScoreTimeMs: actualScoreTimeMs ?? this.actualScoreTimeMs,
scoreLabelSnapshot: scoreLabelSnapshot ?? this.scoreLabelSnapshot,
scoreUnitSnapshot: scoreUnitSnapshot ?? this.scoreUnitSnapshot,
sourceExerciseIdSnapshot:
sourceExerciseIdSnapshot ?? this.sourceExerciseIdSnapshot,
startedAt: startedAt ?? this.startedAt,
completedAt: completedAt ?? this.completedAt,
status: status ?? this.status,
@ -25688,6 +25748,11 @@ class WorkoutHistorySetResultsCompanion
if (scoreUnitSnapshot.present) {
map['score_unit_snapshot'] = Variable<String>(scoreUnitSnapshot.value);
}
if (sourceExerciseIdSnapshot.present) {
map['source_exercise_id_snapshot'] = Variable<String>(
sourceExerciseIdSnapshot.value,
);
}
if (startedAt.present) {
map['started_at'] = Variable<DateTime>(startedAt.value);
}
@ -25739,6 +25804,7 @@ class WorkoutHistorySetResultsCompanion
..write('actualScoreTimeMs: $actualScoreTimeMs, ')
..write('scoreLabelSnapshot: $scoreLabelSnapshot, ')
..write('scoreUnitSnapshot: $scoreUnitSnapshot, ')
..write('sourceExerciseIdSnapshot: $sourceExerciseIdSnapshot, ')
..write('startedAt: $startedAt, ')
..write('completedAt: $completedAt, ')
..write('status: $status, ')
@ -26171,6 +26237,17 @@ class $WorkoutHistoryStepResultsTable extends WorkoutHistoryStepResults
type: DriftSqlType.string,
requiredDuringInsert: false,
);
static const VerificationMeta _sourceExerciseIdSnapshotMeta =
const VerificationMeta('sourceExerciseIdSnapshot');
@override
late final GeneratedColumn<String> sourceExerciseIdSnapshot =
GeneratedColumn<String>(
'source_exercise_id_snapshot',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
@override
List<GeneratedColumn> get $columns => [
id,
@ -26210,6 +26287,7 @@ class $WorkoutHistoryStepResultsTable extends WorkoutHistoryStepResults
actualScore,
actualScoreTimeMs,
note,
sourceExerciseIdSnapshot,
];
@override
String get aliasedName => _alias ?? actualTableName;
@ -26560,6 +26638,15 @@ class $WorkoutHistoryStepResultsTable extends WorkoutHistoryStepResults
note.isAcceptableOrUnknown(data['note']!, _noteMeta),
);
}
if (data.containsKey('source_exercise_id_snapshot')) {
context.handle(
_sourceExerciseIdSnapshotMeta,
sourceExerciseIdSnapshot.isAcceptableOrUnknown(
data['source_exercise_id_snapshot']!,
_sourceExerciseIdSnapshotMeta,
),
);
}
return context;
}
@ -26720,6 +26807,10 @@ class $WorkoutHistoryStepResultsTable extends WorkoutHistoryStepResults
DriftSqlType.string,
data['${effectivePrefix}note'],
),
sourceExerciseIdSnapshot: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}source_exercise_id_snapshot'],
),
);
}
@ -26768,6 +26859,7 @@ class WorkoutHistoryStepResult extends DataClass
final double? actualScore;
final int? actualScoreTimeMs;
final String? note;
final String? sourceExerciseIdSnapshot;
const WorkoutHistoryStepResult({
required this.id,
required this.createdAt,
@ -26806,6 +26898,7 @@ class WorkoutHistoryStepResult extends DataClass
this.actualScore,
this.actualScoreTimeMs,
this.note,
this.sourceExerciseIdSnapshot,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -26883,6 +26976,11 @@ class WorkoutHistoryStepResult extends DataClass
if (!nullToAbsent || note != null) {
map['note'] = Variable<String>(note);
}
if (!nullToAbsent || sourceExerciseIdSnapshot != null) {
map['source_exercise_id_snapshot'] = Variable<String>(
sourceExerciseIdSnapshot,
);
}
return map;
}
@ -26956,6 +27054,9 @@ class WorkoutHistoryStepResult extends DataClass
? const Value.absent()
: Value(actualScoreTimeMs),
note: note == null && nullToAbsent ? const Value.absent() : Value(note),
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot == null && nullToAbsent
? const Value.absent()
: Value(sourceExerciseIdSnapshot),
);
}
@ -27018,6 +27119,9 @@ class WorkoutHistoryStepResult extends DataClass
actualScore: serializer.fromJson<double?>(json['actualScore']),
actualScoreTimeMs: serializer.fromJson<int?>(json['actualScoreTimeMs']),
note: serializer.fromJson<String?>(json['note']),
sourceExerciseIdSnapshot: serializer.fromJson<String?>(
json['sourceExerciseIdSnapshot'],
),
);
}
@override
@ -27065,6 +27169,9 @@ class WorkoutHistoryStepResult extends DataClass
'actualScore': serializer.toJson<double?>(actualScore),
'actualScoreTimeMs': serializer.toJson<int?>(actualScoreTimeMs),
'note': serializer.toJson<String?>(note),
'sourceExerciseIdSnapshot': serializer.toJson<String?>(
sourceExerciseIdSnapshot,
),
};
}
@ -27106,6 +27213,7 @@ class WorkoutHistoryStepResult extends DataClass
Value<double?> actualScore = const Value.absent(),
Value<int?> actualScoreTimeMs = const Value.absent(),
Value<String?> note = const Value.absent(),
Value<String?> sourceExerciseIdSnapshot = const Value.absent(),
}) => WorkoutHistoryStepResult(
id: id ?? this.id,
createdAt: createdAt ?? this.createdAt,
@ -27160,6 +27268,9 @@ class WorkoutHistoryStepResult extends DataClass
? actualScoreTimeMs.value
: this.actualScoreTimeMs,
note: note.present ? note.value : this.note,
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot.present
? sourceExerciseIdSnapshot.value
: this.sourceExerciseIdSnapshot,
);
WorkoutHistoryStepResult copyWithCompanion(
WorkoutHistoryStepResultsCompanion data,
@ -27256,6 +27367,9 @@ class WorkoutHistoryStepResult extends DataClass
? data.actualScoreTimeMs.value
: this.actualScoreTimeMs,
note: data.note.present ? data.note.value : this.note,
sourceExerciseIdSnapshot: data.sourceExerciseIdSnapshot.present
? data.sourceExerciseIdSnapshot.value
: this.sourceExerciseIdSnapshot,
);
}
@ -27298,7 +27412,8 @@ class WorkoutHistoryStepResult extends DataClass
..write('actualReps: $actualReps, ')
..write('actualScore: $actualScore, ')
..write('actualScoreTimeMs: $actualScoreTimeMs, ')
..write('note: $note')
..write('note: $note, ')
..write('sourceExerciseIdSnapshot: $sourceExerciseIdSnapshot')
..write(')'))
.toString();
}
@ -27342,6 +27457,7 @@ class WorkoutHistoryStepResult extends DataClass
actualScore,
actualScoreTimeMs,
note,
sourceExerciseIdSnapshot,
]);
@override
bool operator ==(Object other) =>
@ -27383,7 +27499,8 @@ class WorkoutHistoryStepResult extends DataClass
other.actualReps == this.actualReps &&
other.actualScore == this.actualScore &&
other.actualScoreTimeMs == this.actualScoreTimeMs &&
other.note == this.note);
other.note == this.note &&
other.sourceExerciseIdSnapshot == this.sourceExerciseIdSnapshot);
}
class WorkoutHistoryStepResultsCompanion
@ -27425,6 +27542,7 @@ class WorkoutHistoryStepResultsCompanion
final Value<double?> actualScore;
final Value<int?> actualScoreTimeMs;
final Value<String?> note;
final Value<String?> sourceExerciseIdSnapshot;
final Value<int> rowid;
const WorkoutHistoryStepResultsCompanion({
this.id = const Value.absent(),
@ -27464,6 +27582,7 @@ class WorkoutHistoryStepResultsCompanion
this.actualScore = const Value.absent(),
this.actualScoreTimeMs = const Value.absent(),
this.note = const Value.absent(),
this.sourceExerciseIdSnapshot = const Value.absent(),
this.rowid = const Value.absent(),
});
WorkoutHistoryStepResultsCompanion.insert({
@ -27504,6 +27623,7 @@ class WorkoutHistoryStepResultsCompanion
this.actualScore = const Value.absent(),
this.actualScoreTimeMs = const Value.absent(),
this.note = const Value.absent(),
this.sourceExerciseIdSnapshot = const Value.absent(),
this.rowid = const Value.absent(),
}) : id = Value(id),
createdAt = Value(createdAt),
@ -27563,6 +27683,7 @@ class WorkoutHistoryStepResultsCompanion
Expression<double>? actualScore,
Expression<int>? actualScoreTimeMs,
Expression<String>? note,
Expression<String>? sourceExerciseIdSnapshot,
Expression<int>? rowid,
}) {
return RawValuesInsertable({
@ -27610,6 +27731,8 @@ class WorkoutHistoryStepResultsCompanion
if (actualScore != null) 'actual_score': actualScore,
if (actualScoreTimeMs != null) 'actual_score_time_ms': actualScoreTimeMs,
if (note != null) 'note': note,
if (sourceExerciseIdSnapshot != null)
'source_exercise_id_snapshot': sourceExerciseIdSnapshot,
if (rowid != null) 'rowid': rowid,
});
}
@ -27652,6 +27775,7 @@ class WorkoutHistoryStepResultsCompanion
Value<double?>? actualScore,
Value<int?>? actualScoreTimeMs,
Value<String?>? note,
Value<String?>? sourceExerciseIdSnapshot,
Value<int>? rowid,
}) {
return WorkoutHistoryStepResultsCompanion(
@ -27694,6 +27818,8 @@ class WorkoutHistoryStepResultsCompanion
actualScore: actualScore ?? this.actualScore,
actualScoreTimeMs: actualScoreTimeMs ?? this.actualScoreTimeMs,
note: note ?? this.note,
sourceExerciseIdSnapshot:
sourceExerciseIdSnapshot ?? this.sourceExerciseIdSnapshot,
rowid: rowid ?? this.rowid,
);
}
@ -27820,6 +27946,11 @@ class WorkoutHistoryStepResultsCompanion
if (note.present) {
map['note'] = Variable<String>(note.value);
}
if (sourceExerciseIdSnapshot.present) {
map['source_exercise_id_snapshot'] = Variable<String>(
sourceExerciseIdSnapshot.value,
);
}
if (rowid.present) {
map['rowid'] = Variable<int>(rowid.value);
}
@ -27866,6 +27997,7 @@ class WorkoutHistoryStepResultsCompanion
..write('actualScore: $actualScore, ')
..write('actualScoreTimeMs: $actualScoreTimeMs, ')
..write('note: $note, ')
..write('sourceExerciseIdSnapshot: $sourceExerciseIdSnapshot, ')
..write('rowid: $rowid')
..write(')'))
.toString();
@ -45147,6 +45279,7 @@ typedef $$WorkoutHistorySetResultsTableCreateCompanionBuilder =
Value<int?> actualScoreTimeMs,
Value<String?> scoreLabelSnapshot,
Value<String?> scoreUnitSnapshot,
Value<String?> sourceExerciseIdSnapshot,
Value<DateTime?> startedAt,
Value<DateTime?> completedAt,
Value<String> status,
@ -45187,6 +45320,7 @@ typedef $$WorkoutHistorySetResultsTableUpdateCompanionBuilder =
Value<int?> actualScoreTimeMs,
Value<String?> scoreLabelSnapshot,
Value<String?> scoreUnitSnapshot,
Value<String?> sourceExerciseIdSnapshot,
Value<DateTime?> startedAt,
Value<DateTime?> completedAt,
Value<String> status,
@ -45395,6 +45529,11 @@ class $$WorkoutHistorySetResultsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get sourceExerciseIdSnapshot => $composableBuilder(
column: $table.sourceExerciseIdSnapshot,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<DateTime> get startedAt => $composableBuilder(
column: $table.startedAt,
builder: (column) => ColumnFilters(column),
@ -45603,6 +45742,11 @@ class $$WorkoutHistorySetResultsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get sourceExerciseIdSnapshot => $composableBuilder(
column: $table.sourceExerciseIdSnapshot,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<DateTime> get startedAt => $composableBuilder(
column: $table.startedAt,
builder: (column) => ColumnOrderings(column),
@ -45799,6 +45943,11 @@ class $$WorkoutHistorySetResultsTableAnnotationComposer
builder: (column) => column,
);
GeneratedColumn<String> get sourceExerciseIdSnapshot => $composableBuilder(
column: $table.sourceExerciseIdSnapshot,
builder: (column) => column,
);
GeneratedColumn<DateTime> get startedAt =>
$composableBuilder(column: $table.startedAt, builder: (column) => column);
@ -45906,6 +46055,7 @@ class $$WorkoutHistorySetResultsTableTableManager
Value<int?> actualScoreTimeMs = const Value.absent(),
Value<String?> scoreLabelSnapshot = const Value.absent(),
Value<String?> scoreUnitSnapshot = const Value.absent(),
Value<String?> sourceExerciseIdSnapshot = const Value.absent(),
Value<DateTime?> startedAt = const Value.absent(),
Value<DateTime?> completedAt = const Value.absent(),
Value<String> status = const Value.absent(),
@ -45944,6 +46094,7 @@ class $$WorkoutHistorySetResultsTableTableManager
actualScoreTimeMs: actualScoreTimeMs,
scoreLabelSnapshot: scoreLabelSnapshot,
scoreUnitSnapshot: scoreUnitSnapshot,
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot,
startedAt: startedAt,
completedAt: completedAt,
status: status,
@ -45984,6 +46135,7 @@ class $$WorkoutHistorySetResultsTableTableManager
Value<int?> actualScoreTimeMs = const Value.absent(),
Value<String?> scoreLabelSnapshot = const Value.absent(),
Value<String?> scoreUnitSnapshot = const Value.absent(),
Value<String?> sourceExerciseIdSnapshot = const Value.absent(),
Value<DateTime?> startedAt = const Value.absent(),
Value<DateTime?> completedAt = const Value.absent(),
Value<String> status = const Value.absent(),
@ -46022,6 +46174,7 @@ class $$WorkoutHistorySetResultsTableTableManager
actualScoreTimeMs: actualScoreTimeMs,
scoreLabelSnapshot: scoreLabelSnapshot,
scoreUnitSnapshot: scoreUnitSnapshot,
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot,
startedAt: startedAt,
completedAt: completedAt,
status: status,
@ -46135,6 +46288,7 @@ typedef $$WorkoutHistoryStepResultsTableCreateCompanionBuilder =
Value<double?> actualScore,
Value<int?> actualScoreTimeMs,
Value<String?> note,
Value<String?> sourceExerciseIdSnapshot,
Value<int> rowid,
});
typedef $$WorkoutHistoryStepResultsTableUpdateCompanionBuilder =
@ -46176,6 +46330,7 @@ typedef $$WorkoutHistoryStepResultsTableUpdateCompanionBuilder =
Value<double?> actualScore,
Value<int?> actualScoreTimeMs,
Value<String?> note,
Value<String?> sourceExerciseIdSnapshot,
Value<int> rowid,
});
@ -46401,6 +46556,11 @@ class $$WorkoutHistoryStepResultsTableFilterComposer
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get sourceExerciseIdSnapshot => $composableBuilder(
column: $table.sourceExerciseIdSnapshot,
builder: (column) => ColumnFilters(column),
);
$$WorkoutHistoriesTableFilterComposer get workoutHistoryId {
final $$WorkoutHistoriesTableFilterComposer composer = $composerBuilder(
composer: this,
@ -46614,6 +46774,11 @@ class $$WorkoutHistoryStepResultsTableOrderingComposer
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get sourceExerciseIdSnapshot => $composableBuilder(
column: $table.sourceExerciseIdSnapshot,
builder: (column) => ColumnOrderings(column),
);
$$WorkoutHistoriesTableOrderingComposer get workoutHistoryId {
final $$WorkoutHistoriesTableOrderingComposer composer = $composerBuilder(
composer: this,
@ -46807,6 +46972,11 @@ class $$WorkoutHistoryStepResultsTableAnnotationComposer
GeneratedColumn<String> get note =>
$composableBuilder(column: $table.note, builder: (column) => column);
GeneratedColumn<String> get sourceExerciseIdSnapshot => $composableBuilder(
column: $table.sourceExerciseIdSnapshot,
builder: (column) => column,
);
$$WorkoutHistoriesTableAnnotationComposer get workoutHistoryId {
final $$WorkoutHistoriesTableAnnotationComposer composer = $composerBuilder(
composer: this,
@ -46910,6 +47080,7 @@ class $$WorkoutHistoryStepResultsTableTableManager
Value<double?> actualScore = const Value.absent(),
Value<int?> actualScoreTimeMs = const Value.absent(),
Value<String?> note = const Value.absent(),
Value<String?> sourceExerciseIdSnapshot = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => WorkoutHistoryStepResultsCompanion(
id: id,
@ -46949,6 +47120,7 @@ class $$WorkoutHistoryStepResultsTableTableManager
actualScore: actualScore,
actualScoreTimeMs: actualScoreTimeMs,
note: note,
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot,
rowid: rowid,
),
createCompanionCallback:
@ -46990,6 +47162,7 @@ class $$WorkoutHistoryStepResultsTableTableManager
Value<double?> actualScore = const Value.absent(),
Value<int?> actualScoreTimeMs = const Value.absent(),
Value<String?> note = const Value.absent(),
Value<String?> sourceExerciseIdSnapshot = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) => WorkoutHistoryStepResultsCompanion.insert(
id: id,
@ -47029,6 +47202,7 @@ class $$WorkoutHistoryStepResultsTableTableManager
actualScore: actualScore,
actualScoreTimeMs: actualScoreTimeMs,
note: note,
sourceExerciseIdSnapshot: sourceExerciseIdSnapshot,
rowid: rowid,
),
withReferenceMapper: (p0) => p0

View File

@ -1602,6 +1602,138 @@ final class DriftWorkoutHistoryRepository implements WorkoutHistoryRepository {
}
}
final class DriftExercisePerformanceReferenceRepository
implements ExercisePerformanceReferenceRepository {
const DriftExercisePerformanceReferenceRepository(this.database);
final db.AppDatabase database;
@override
Future<bool> hasAnyCompletedHistoryForExercise(String exerciseId) async {
final row = await database
.customSelect(
'SELECT 1 FROM workout_history_set_results AS result '
'INNER JOIN workout_history AS history '
'ON history.id = result.workout_history_id '
'WHERE result.deleted_at IS NULL '
'AND history.deleted_at IS NULL '
'AND history.completed = 1 '
'AND result.status = ? '
'AND result.source_exercise_id_snapshot = ? '
'LIMIT 1',
variables: [
const Variable<String>('completed'),
Variable<String>(exerciseId),
],
)
.getSingleOrNull();
return row != null;
}
@override
Future<WorkoutHistorySetPerformance?> findLatestSetPerformance({
required String exerciseId,
required ActivePerformanceMeasures activeMeasures,
required int currentSetIndex,
}) async {
final valuePredicate = _activeMeasuresPredicate(activeMeasures);
if (valuePredicate == null) {
return null;
}
final row = await database
.customSelect(
'''
WITH candidates AS (
SELECT
result.workout_history_id,
history.started_at AS history_started_at,
result.set_index,
result.exercise_name_snapshot,
result.score_input_mode_snapshot,
result.actual_time_ms,
result.actual_reps,
result.actual_score,
result.actual_score_time_ms,
result.completed_at
FROM workout_history_set_results AS result
INNER JOIN workout_history AS history
ON history.id = result.workout_history_id
WHERE result.deleted_at IS NULL
AND history.deleted_at IS NULL
AND history.completed = 1
AND result.status = ?
AND result.source_exercise_id_snapshot = ?
AND ($valuePredicate)
),
latest_history AS (
SELECT workout_history_id
FROM candidates
ORDER BY history_started_at DESC
LIMIT 1
)
SELECT *
FROM candidates
WHERE workout_history_id = (SELECT workout_history_id FROM latest_history)
ORDER BY
CASE WHEN set_index = ? THEN 0 ELSE 1 END,
set_index DESC
LIMIT 1
''',
variables: [
const Variable<String>('completed'),
Variable<String>(exerciseId),
Variable<int>(currentSetIndex),
],
)
.getSingleOrNull();
return row == null ? null : _setPerformanceFromRow(row);
}
@override
Future<WorkoutHistoryMetricPerformance?> findBestMetricPerformance({
required String exerciseId,
required PerformanceMetric metric,
required domain.ScoreInputMode scoreInputMode,
}) async {
final metricSql = _metricSql(metric, scoreInputMode);
final row = await database
.customSelect(
'''
SELECT
result.workout_history_id,
history.started_at AS history_started_at,
result.set_index,
result.exercise_name_snapshot,
result.score_input_mode_snapshot,
result.actual_time_ms,
result.actual_reps,
result.actual_score,
result.actual_score_time_ms,
result.completed_at
FROM workout_history_set_results AS result
INNER JOIN workout_history AS history
ON history.id = result.workout_history_id
WHERE result.deleted_at IS NULL
AND history.deleted_at IS NULL
AND history.completed = 1
AND result.status = ?
AND result.source_exercise_id_snapshot = ?
AND ${metricSql.predicate}
ORDER BY ${metricSql.ordering},
history.started_at DESC,
result.completed_at DESC
LIMIT 1
''',
variables: [
const Variable<String>('completed'),
Variable<String>(exerciseId),
],
)
.getSingleOrNull();
return row == null ? null : _metricPerformanceFromRow(row, metric: metric);
}
}
Future<void> _upsertWithChangeLog({
required db.AppDatabase database,
required String tableName,
@ -2863,6 +2995,94 @@ domain.ActiveRestState _activeRestStateFromCustomRow(QueryRow row) {
);
}
WorkoutHistorySetPerformance _setPerformanceFromRow(QueryRow row) {
final data = row.data;
return WorkoutHistorySetPerformance(
workoutHistoryId: data['workout_history_id'] as String,
startedAt: _dateTimeFromData(data, 'history_started_at'),
setIndex: data['set_index'] as int,
exerciseNameSnapshot: data['exercise_name_snapshot'] as String,
scoreInputModeSnapshot: _scoreInputModeFromDb(
data['score_input_mode_snapshot'] as String,
),
actualTimeMs: data['actual_time_ms'] as int?,
actualReps: data['actual_reps'] as int?,
actualScore: (data['actual_score'] as num?)?.toDouble(),
actualScoreTimeMs: data['actual_score_time_ms'] as int?,
completedAt: _dateTimeOrNullFromData(data, 'completed_at'),
);
}
WorkoutHistoryMetricPerformance _metricPerformanceFromRow(
QueryRow row, {
required PerformanceMetric metric,
}) {
final set = _setPerformanceFromRow(row);
return WorkoutHistoryMetricPerformance(
workoutHistoryId: set.workoutHistoryId,
startedAt: set.startedAt,
setIndex: set.setIndex,
exerciseNameSnapshot: set.exerciseNameSnapshot,
metric: metric,
scoreInputModeSnapshot: set.scoreInputModeSnapshot,
actualTimeMs: set.actualTimeMs,
actualReps: set.actualReps,
actualScore: set.actualScore,
actualScoreTimeMs: set.actualScoreTimeMs,
completedAt: set.completedAt,
);
}
String? _activeMeasuresPredicate(ActivePerformanceMeasures measures) {
final predicates = <String>[];
if (measures.scoreEnabled) {
predicates.add(
measures.scoreInputMode == domain.ScoreInputMode.stopwatch
? "(result.score_input_mode_snapshot = 'stopwatch' "
'AND result.actual_score_time_ms IS NOT NULL)'
: "(result.score_input_mode_snapshot = 'manual' "
'AND result.actual_score IS NOT NULL)',
);
}
if (measures.repsEnabled) {
predicates.add('result.actual_reps IS NOT NULL');
}
if (measures.timeEnabled) {
predicates.add('result.actual_time_ms IS NOT NULL');
}
return predicates.isEmpty ? null : predicates.join(' OR ');
}
({String predicate, String ordering}) _metricSql(
PerformanceMetric metric,
domain.ScoreInputMode scoreInputMode,
) {
return switch (metric) {
PerformanceMetric.score =>
scoreInputMode == domain.ScoreInputMode.stopwatch
? (
predicate:
"result.score_input_mode_snapshot = 'stopwatch' "
'AND result.actual_score_time_ms IS NOT NULL',
ordering: 'result.actual_score_time_ms ASC',
)
: (
predicate:
"result.score_input_mode_snapshot = 'manual' "
'AND result.actual_score IS NOT NULL',
ordering: 'result.actual_score DESC',
),
PerformanceMetric.reps => (
predicate: 'result.actual_reps IS NOT NULL',
ordering: 'result.actual_reps DESC',
),
PerformanceMetric.time => (
predicate: 'result.actual_time_ms IS NOT NULL',
ordering: 'result.actual_time_ms DESC',
),
};
}
db.ActiveScoreStopwatchStatesCompanion _activeScoreStopwatchStateCompanion(
domain.ActiveScoreStopwatchState state,
) {
@ -3108,6 +3328,7 @@ db.WorkoutHistorySetResultsCompanion _workoutHistorySetResultCompanion(
actualScoreTimeMs: Value(result.actualScoreTimeMs),
scoreLabelSnapshot: Value(result.scoreLabelSnapshot),
scoreUnitSnapshot: Value(result.scoreUnitSnapshot),
sourceExerciseIdSnapshot: Value(result.sourceExerciseIdSnapshot),
startedAt: Value(_utcOrNull(result.startedAt)),
completedAt: Value(_utcOrNull(result.completedAt)),
status: Value(_setResultStatusToDb(result.status)),
@ -3160,6 +3381,7 @@ db.WorkoutHistoryStepResultsCompanion _workoutHistoryStepResultCompanion(
actualScore: Value<double?>(result.actualScore),
actualScoreTimeMs: Value<int?>(result.actualScoreTimeMs),
note: Value<String?>(result.note),
sourceExerciseIdSnapshot: Value<String?>(result.sourceExerciseIdSnapshot),
);
}
@ -3210,6 +3432,7 @@ domain.WorkoutHistorySetResult _workoutHistorySetResultFromRow(
actualScoreTimeMs: row.actualScoreTimeMs,
scoreLabelSnapshot: row.scoreLabelSnapshot,
scoreUnitSnapshot: row.scoreUnitSnapshot,
sourceExerciseIdSnapshot: row.sourceExerciseIdSnapshot,
startedAt: _utcOrNull(row.startedAt),
completedAt: _utcOrNull(row.completedAt),
status: _setResultStatusFromDb(row.status),
@ -3249,6 +3472,7 @@ domain.WorkoutHistoryStepResult _workoutHistoryStepResultFromRow(
actualScore: row.actualScore,
actualScoreTimeMs: row.actualScoreTimeMs,
note: row.note,
sourceExerciseIdSnapshot: row.sourceExerciseIdSnapshot,
);
}

View File

@ -745,6 +745,7 @@ class WorkoutHistorySetResults extends SyncableTable {
IntColumn get actualScoreTimeMs => integer().nullable()();
TextColumn get scoreLabelSnapshot => text().nullable()();
TextColumn get scoreUnitSnapshot => text().nullable()();
TextColumn get sourceExerciseIdSnapshot => text().nullable()();
DateTimeColumn get startedAt => dateTime().nullable()();
DateTimeColumn get completedAt => dateTime().nullable()();
TextColumn get status => text().withDefault(const Constant('completed'))();
@ -821,6 +822,7 @@ class WorkoutHistoryStepResults extends SyncableTable {
RealColumn get actualScore => real().nullable()();
IntColumn get actualScoreTimeMs => integer().nullable()();
TextColumn get note => text().nullable()();
TextColumn get sourceExerciseIdSnapshot => text().nullable()();
@override
List<String> get customConstraints => [