1039 lines
27 KiB
Dart
1039 lines
27 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import '../domain/domain.dart';
|
|
|
|
abstract interface class Clock {
|
|
DateTime now();
|
|
}
|
|
|
|
final class SystemClock implements Clock {
|
|
const SystemClock();
|
|
|
|
@override
|
|
DateTime now() => DateTime.now().toUtc();
|
|
}
|
|
|
|
abstract interface class IdGenerator {
|
|
String newId();
|
|
}
|
|
|
|
final class StarterContent {
|
|
const StarterContent({
|
|
required this.exercises,
|
|
required this.program,
|
|
required this.workoutTemplate,
|
|
});
|
|
|
|
final List<Exercise> exercises;
|
|
final Program program;
|
|
final WorkoutTemplate workoutTemplate;
|
|
}
|
|
|
|
final class TagUsage {
|
|
const TagUsage({required this.tag, required this.count});
|
|
|
|
final String tag;
|
|
final int count;
|
|
}
|
|
|
|
enum LocalBackupImportMode { merge, replaceAll }
|
|
|
|
enum LocalBackupValidationError {
|
|
invalidFile,
|
|
incompatibleFormat,
|
|
newerVersion,
|
|
corrupted,
|
|
activeWorkoutInProgress,
|
|
importFailedNoMutation,
|
|
}
|
|
|
|
final class LocalBackupException implements Exception {
|
|
const LocalBackupException(this.error);
|
|
|
|
final LocalBackupValidationError error;
|
|
|
|
@override
|
|
String toString() => error.name;
|
|
}
|
|
|
|
final class LocalBackupDocument {
|
|
const LocalBackupDocument({required this.fileName, required this.bytes});
|
|
|
|
final String fileName;
|
|
final Uint8List bytes;
|
|
}
|
|
|
|
final class LocalBackupCounts {
|
|
const LocalBackupCounts({
|
|
required this.exercises,
|
|
required this.programs,
|
|
required this.workoutTemplates,
|
|
required this.workoutHistories,
|
|
required this.mediaAssets,
|
|
required this.embeddedMediaFiles,
|
|
});
|
|
|
|
final int exercises;
|
|
final int programs;
|
|
final int workoutTemplates;
|
|
final int workoutHistories;
|
|
final int mediaAssets;
|
|
final int embeddedMediaFiles;
|
|
}
|
|
|
|
final class LocalBackupPreview {
|
|
const LocalBackupPreview({
|
|
required this.exportedAt,
|
|
required this.counts,
|
|
required this.hasEmbeddedMedia,
|
|
required this.missingMediaCount,
|
|
required this.hasNameDuplicates,
|
|
});
|
|
|
|
final DateTime exportedAt;
|
|
final LocalBackupCounts counts;
|
|
final bool hasEmbeddedMedia;
|
|
final int missingMediaCount;
|
|
final bool hasNameDuplicates;
|
|
}
|
|
|
|
final class LocalBackupImportResult {
|
|
const LocalBackupImportResult({
|
|
required this.insertedCount,
|
|
required this.updatedCount,
|
|
required this.ignoredOlderCount,
|
|
required this.deletedByReplaceCount,
|
|
required this.missingMediaCount,
|
|
});
|
|
|
|
final int insertedCount;
|
|
final int updatedCount;
|
|
final int ignoredOlderCount;
|
|
final int deletedByReplaceCount;
|
|
final int missingMediaCount;
|
|
|
|
LocalBackupImportResult copyWith({int? missingMediaCount}) {
|
|
return LocalBackupImportResult(
|
|
insertedCount: insertedCount,
|
|
updatedCount: updatedCount,
|
|
ignoredOlderCount: ignoredOlderCount,
|
|
deletedByReplaceCount: deletedByReplaceCount,
|
|
missingMediaCount: missingMediaCount ?? this.missingMediaCount,
|
|
);
|
|
}
|
|
}
|
|
|
|
final class LocalBackupResource {
|
|
const LocalBackupResource({
|
|
required this.id,
|
|
required this.updatedAt,
|
|
required this.payload,
|
|
});
|
|
|
|
final String id;
|
|
final DateTime updatedAt;
|
|
final Map<String, Object?> payload;
|
|
|
|
LocalBackupResource copyWith({Map<String, Object?>? payload}) {
|
|
return LocalBackupResource(
|
|
id: id,
|
|
updatedAt: updatedAt,
|
|
payload: payload ?? this.payload,
|
|
);
|
|
}
|
|
}
|
|
|
|
final class LocalDataExportSnapshot {
|
|
const LocalDataExportSnapshot({
|
|
required this.exportedAt,
|
|
required this.appSchemaVersion,
|
|
required this.originDeviceId,
|
|
required this.mediaAssets,
|
|
required this.exercises,
|
|
required this.programs,
|
|
required this.workoutTemplates,
|
|
required this.workoutHistories,
|
|
this.mediaFiles = const [],
|
|
});
|
|
|
|
final DateTime exportedAt;
|
|
final int appSchemaVersion;
|
|
final String originDeviceId;
|
|
final List<LocalBackupResource> mediaAssets;
|
|
final List<LocalBackupResource> exercises;
|
|
final List<LocalBackupResource> programs;
|
|
final List<LocalBackupResource> workoutTemplates;
|
|
final List<LocalBackupResource> workoutHistories;
|
|
final List<EmbeddedBackupMediaFile> mediaFiles;
|
|
|
|
LocalBackupCounts get counts => LocalBackupCounts(
|
|
exercises: exercises.length,
|
|
programs: programs.length,
|
|
workoutTemplates: workoutTemplates.length,
|
|
workoutHistories: workoutHistories.length,
|
|
mediaAssets: mediaAssets.length,
|
|
embeddedMediaFiles: mediaFiles.length,
|
|
);
|
|
|
|
LocalDataExportSnapshot copyWith({
|
|
List<LocalBackupResource>? mediaAssets,
|
|
List<EmbeddedBackupMediaFile>? mediaFiles,
|
|
}) {
|
|
return LocalDataExportSnapshot(
|
|
exportedAt: exportedAt,
|
|
appSchemaVersion: appSchemaVersion,
|
|
originDeviceId: originDeviceId,
|
|
mediaAssets: mediaAssets ?? this.mediaAssets,
|
|
exercises: exercises,
|
|
programs: programs,
|
|
workoutTemplates: workoutTemplates,
|
|
workoutHistories: workoutHistories,
|
|
mediaFiles: mediaFiles ?? this.mediaFiles,
|
|
);
|
|
}
|
|
}
|
|
|
|
final class EmbeddedBackupMediaFile {
|
|
const EmbeddedBackupMediaFile({
|
|
required this.mediaAssetId,
|
|
required this.role,
|
|
required this.fileName,
|
|
required this.mimeType,
|
|
required this.sizeBytes,
|
|
required this.base64,
|
|
});
|
|
|
|
final String mediaAssetId;
|
|
final String role;
|
|
final String fileName;
|
|
final String? mimeType;
|
|
final int sizeBytes;
|
|
final String base64;
|
|
}
|
|
|
|
final class RestoredMediaFile {
|
|
const RestoredMediaFile({
|
|
required this.mediaAssetId,
|
|
required this.localUri,
|
|
required this.sizeBytes,
|
|
});
|
|
|
|
final String mediaAssetId;
|
|
final String localUri;
|
|
final int sizeBytes;
|
|
}
|
|
|
|
abstract interface class LocalDataBackupRepository {
|
|
Future<LocalDataExportSnapshot> readExportSnapshot(DateTime exportedAt);
|
|
Future<bool> hasOpenActiveWorkoutSession();
|
|
Future<bool> hasAnyUserData();
|
|
Future<LocalBackupImportResult> applyImportSnapshot({
|
|
required LocalDataExportSnapshot snapshot,
|
|
required LocalBackupImportMode mode,
|
|
required DateTime importedAt,
|
|
});
|
|
}
|
|
|
|
abstract interface class LocalBackupMediaStore {
|
|
Future<List<EmbeddedBackupMediaFile>> readEmbeddableFiles(
|
|
List<LocalBackupResource> mediaAssets,
|
|
);
|
|
Future<Map<String, RestoredMediaFile>> restoreEmbeddedFiles(
|
|
List<EmbeddedBackupMediaFile> files,
|
|
);
|
|
}
|
|
|
|
enum ProgressionPeriod { fourWeeks, threeMonths, all }
|
|
|
|
enum ProgressionMeasure { manualScore, stopwatchScore, reps, time }
|
|
|
|
enum ProgressionRangeLabelKind { sinceDate, sinceFirstSession, none }
|
|
|
|
enum ProgressionSeriesState {
|
|
ready,
|
|
singlePoint,
|
|
emptyForPeriod,
|
|
emptyButHasAllTimeData,
|
|
noGraphableMeasure,
|
|
textScoreOnly,
|
|
}
|
|
|
|
enum ProgressionSeriesSummaryKind { bestLast, totals, none }
|
|
|
|
final class ProgressionDateRange {
|
|
const ProgressionDateRange({required this.startedAt, required this.endedAt});
|
|
|
|
final DateTime? startedAt;
|
|
final DateTime endedAt;
|
|
}
|
|
|
|
final class ProgressionGlobalStatsData {
|
|
const ProgressionGlobalStatsData({
|
|
required this.completedSessionCount,
|
|
required this.totalActiveMs,
|
|
required this.activeWeekStarts,
|
|
required this.hasAnyCompletedHistory,
|
|
this.firstCompletedSessionAt,
|
|
});
|
|
|
|
final int completedSessionCount;
|
|
final int totalActiveMs;
|
|
final List<DateTime> activeWeekStarts;
|
|
final bool hasAnyCompletedHistory;
|
|
final DateTime? firstCompletedSessionAt;
|
|
}
|
|
|
|
final class ProgressionExerciseOptionData {
|
|
const ProgressionExerciseOptionData({
|
|
required this.exerciseKey,
|
|
required this.nameSnapshot,
|
|
required this.isArchived,
|
|
required this.lastPerformedAt,
|
|
});
|
|
|
|
final String exerciseKey;
|
|
final String nameSnapshot;
|
|
final bool isArchived;
|
|
final DateTime lastPerformedAt;
|
|
}
|
|
|
|
final class ProgressionMeasureOptionData {
|
|
const ProgressionMeasureOptionData({
|
|
required this.measure,
|
|
required this.label,
|
|
this.scoreLabel,
|
|
this.scoreUnit,
|
|
required this.lowerIsBetter,
|
|
});
|
|
|
|
final ProgressionMeasure measure;
|
|
final String label;
|
|
final String? scoreLabel;
|
|
final String? scoreUnit;
|
|
final bool lowerIsBetter;
|
|
}
|
|
|
|
final class ProgressionExerciseSeriesData {
|
|
const ProgressionExerciseSeriesData({
|
|
required this.exerciseKey,
|
|
required this.exerciseNameSnapshot,
|
|
required this.measure,
|
|
required this.points,
|
|
required this.hasAnyAllTimeData,
|
|
required this.hasAnyCompletedExerciseResult,
|
|
});
|
|
|
|
final String exerciseKey;
|
|
final String exerciseNameSnapshot;
|
|
final ProgressionMeasureOptionData measure;
|
|
final List<ProgressionPointData> points;
|
|
final bool hasAnyAllTimeData;
|
|
final bool hasAnyCompletedExerciseResult;
|
|
}
|
|
|
|
final class ProgressionPointData {
|
|
const ProgressionPointData({
|
|
required this.workoutHistoryId,
|
|
required this.workoutNameSnapshot,
|
|
required this.startedAt,
|
|
required this.value,
|
|
required this.rawValue,
|
|
});
|
|
|
|
final String workoutHistoryId;
|
|
final String workoutNameSnapshot;
|
|
final DateTime startedAt;
|
|
final double value;
|
|
final Object rawValue;
|
|
}
|
|
|
|
abstract interface class ProgressionStatsRepository {
|
|
Future<ProgressionGlobalStatsData> readGlobalStats(
|
|
ProgressionDateRange range,
|
|
);
|
|
Future<List<ProgressionExerciseOptionData>> listExerciseOptions(
|
|
ProgressionDateRange range,
|
|
);
|
|
Future<List<ProgressionMeasureOptionData>> listMeasureOptions({
|
|
required ProgressionDateRange range,
|
|
required String exerciseKey,
|
|
});
|
|
Future<ProgressionExerciseSeriesData> readExerciseSeries({
|
|
required ProgressionDateRange range,
|
|
required String exerciseKey,
|
|
required ProgressionMeasure measure,
|
|
});
|
|
}
|
|
|
|
final class ProgressionOverview {
|
|
const ProgressionOverview({
|
|
required this.period,
|
|
required this.rangeLabelKind,
|
|
required this.completedSessionCount,
|
|
required this.totalActiveMs,
|
|
required this.activeWeekCount,
|
|
required this.totalWeekCount,
|
|
required this.hasAnyCompletedHistory,
|
|
required this.exerciseOptions,
|
|
});
|
|
|
|
final ProgressionPeriod period;
|
|
final ProgressionRangeLabelKind rangeLabelKind;
|
|
final int completedSessionCount;
|
|
final int totalActiveMs;
|
|
final int activeWeekCount;
|
|
final int? totalWeekCount;
|
|
final bool hasAnyCompletedHistory;
|
|
final List<ProgressionExerciseOption> exerciseOptions;
|
|
}
|
|
|
|
final class ProgressionExerciseOption {
|
|
const ProgressionExerciseOption({
|
|
required this.exerciseKey,
|
|
required this.nameSnapshot,
|
|
required this.isArchived,
|
|
required this.lastPerformedAt,
|
|
required this.measures,
|
|
});
|
|
|
|
final String exerciseKey;
|
|
final String nameSnapshot;
|
|
final bool isArchived;
|
|
final DateTime lastPerformedAt;
|
|
final List<ProgressionMeasureOption> measures;
|
|
}
|
|
|
|
final class ProgressionMeasureOption {
|
|
const ProgressionMeasureOption({
|
|
required this.measure,
|
|
required this.label,
|
|
this.scoreLabel,
|
|
this.scoreUnit,
|
|
required this.lowerIsBetter,
|
|
});
|
|
|
|
final ProgressionMeasure measure;
|
|
final String label;
|
|
final String? scoreLabel;
|
|
final String? scoreUnit;
|
|
final bool lowerIsBetter;
|
|
}
|
|
|
|
final class ProgressionExerciseSeries {
|
|
const ProgressionExerciseSeries({
|
|
required this.exerciseKey,
|
|
required this.exerciseNameSnapshot,
|
|
required this.measure,
|
|
required this.points,
|
|
required this.summary,
|
|
required this.state,
|
|
});
|
|
|
|
final String exerciseKey;
|
|
final String exerciseNameSnapshot;
|
|
final ProgressionMeasureOption measure;
|
|
final List<ProgressionPoint> points;
|
|
final ProgressionSeriesSummary summary;
|
|
final ProgressionSeriesState state;
|
|
}
|
|
|
|
final class ProgressionPoint {
|
|
const ProgressionPoint({
|
|
required this.workoutHistoryId,
|
|
required this.workoutNameSnapshot,
|
|
required this.startedAt,
|
|
required this.value,
|
|
required this.rawValue,
|
|
});
|
|
|
|
final String workoutHistoryId;
|
|
final String workoutNameSnapshot;
|
|
final DateTime startedAt;
|
|
final double value;
|
|
final Object rawValue;
|
|
}
|
|
|
|
final class ProgressionSeriesSummary {
|
|
const ProgressionSeriesSummary({
|
|
required this.kind,
|
|
this.best,
|
|
this.last,
|
|
this.recentTotal,
|
|
this.lastSessionTotal,
|
|
});
|
|
|
|
final ProgressionSeriesSummaryKind kind;
|
|
final Object? best;
|
|
final Object? last;
|
|
final Object? recentTotal;
|
|
final Object? lastSessionTotal;
|
|
}
|
|
|
|
abstract interface class StarterSeedStateRepository {
|
|
Future<int> readAppliedStarterSeedVersion();
|
|
Future<void> writeAppliedStarterSeedVersion(int version, DateTime appliedAt);
|
|
}
|
|
|
|
abstract interface class StarterContentRepository {
|
|
Future<bool> isLocalContentEmpty();
|
|
Future<void> insertStarterContent(StarterContent content);
|
|
}
|
|
|
|
enum RemoteAuthFailure {
|
|
invalidCredentials,
|
|
emailAlreadyUsed,
|
|
network,
|
|
unknown,
|
|
}
|
|
|
|
final class RemoteAuthException implements Exception {
|
|
const RemoteAuthException(this.failure, [this.message]);
|
|
|
|
final RemoteAuthFailure failure;
|
|
final String? message;
|
|
|
|
@override
|
|
String toString() => message ?? failure.name;
|
|
}
|
|
|
|
final class RemoteAuthResult {
|
|
const RemoteAuthResult({
|
|
required this.userId,
|
|
required this.email,
|
|
required this.token,
|
|
required this.expiresAt,
|
|
});
|
|
|
|
final String userId;
|
|
final String email;
|
|
final String token;
|
|
final DateTime expiresAt;
|
|
}
|
|
|
|
abstract interface class AuthTokenStore {
|
|
Future<void> saveToken(String token, DateTime expiresAt);
|
|
Future<String?> readToken();
|
|
Future<void> clearToken();
|
|
}
|
|
|
|
abstract interface class OnlineAccountRepository {
|
|
Future<UserAccountSession?> currentSession();
|
|
Future<void> saveSession(UserAccountSession session);
|
|
Future<void> clearSession();
|
|
}
|
|
|
|
abstract interface class RemoteAuthApi {
|
|
Future<RemoteAuthResult> register({
|
|
required String email,
|
|
required String password,
|
|
String? displayName,
|
|
});
|
|
|
|
Future<RemoteAuthResult> login({
|
|
required String email,
|
|
required String password,
|
|
});
|
|
|
|
Future<void> logout(String token);
|
|
}
|
|
|
|
enum SyncResourceType {
|
|
exercise,
|
|
program,
|
|
workoutTemplate,
|
|
workoutHistory,
|
|
mediaAsset,
|
|
}
|
|
|
|
enum RemoteSyncPushStatus { accepted, ignoredOlder, error }
|
|
|
|
enum OnlineSyncStatus { idle, syncing, success, failure }
|
|
|
|
final class RemoteSyncPushItem {
|
|
const RemoteSyncPushItem({
|
|
required this.resourceType,
|
|
required this.clientId,
|
|
required this.schemaVersion,
|
|
required this.clientUpdatedAt,
|
|
required this.deletedAt,
|
|
required this.payload,
|
|
});
|
|
|
|
final SyncResourceType resourceType;
|
|
final String clientId;
|
|
final int schemaVersion;
|
|
final DateTime clientUpdatedAt;
|
|
final DateTime? deletedAt;
|
|
final Map<String, Object?> payload;
|
|
}
|
|
|
|
final class RemoteSyncPushItemResult {
|
|
const RemoteSyncPushItemResult({
|
|
required this.resourceType,
|
|
required this.clientId,
|
|
this.serverId,
|
|
required this.status,
|
|
this.serverUpdatedAt,
|
|
this.errorMessage,
|
|
});
|
|
|
|
final SyncResourceType resourceType;
|
|
final String clientId;
|
|
final String? serverId;
|
|
final RemoteSyncPushStatus status;
|
|
final DateTime? serverUpdatedAt;
|
|
final String? errorMessage;
|
|
}
|
|
|
|
final class RemoteSyncPushResult {
|
|
const RemoteSyncPushResult({
|
|
required this.serverCursor,
|
|
required this.results,
|
|
});
|
|
|
|
final String? serverCursor;
|
|
final List<RemoteSyncPushItemResult> results;
|
|
}
|
|
|
|
final class RemoteSyncedItem {
|
|
const RemoteSyncedItem({
|
|
required this.resourceType,
|
|
required this.clientId,
|
|
required this.serverId,
|
|
required this.schemaVersion,
|
|
required this.clientUpdatedAt,
|
|
required this.serverUpdatedAt,
|
|
required this.deletedAt,
|
|
required this.payload,
|
|
});
|
|
|
|
final SyncResourceType resourceType;
|
|
final String clientId;
|
|
final String serverId;
|
|
final int schemaVersion;
|
|
final DateTime clientUpdatedAt;
|
|
final DateTime serverUpdatedAt;
|
|
final DateTime? deletedAt;
|
|
final Map<String, Object?> payload;
|
|
}
|
|
|
|
final class RemoteSyncPullResult {
|
|
const RemoteSyncPullResult({required this.serverCursor, required this.items});
|
|
|
|
final String? serverCursor;
|
|
final List<RemoteSyncedItem> items;
|
|
}
|
|
|
|
abstract interface class RemoteSyncApi {
|
|
Future<RemoteSyncPushResult> push({
|
|
required String deviceId,
|
|
required List<RemoteSyncPushItem> items,
|
|
required String token,
|
|
});
|
|
|
|
Future<RemoteSyncPullResult> pull({
|
|
required String? since,
|
|
required String token,
|
|
});
|
|
}
|
|
|
|
final class SyncMetadataSnapshot {
|
|
const SyncMetadataSnapshot({
|
|
this.serverCursor,
|
|
this.lastSuccessfulSyncAt,
|
|
this.lastAttemptAt,
|
|
this.lastFailureAt,
|
|
this.status = OnlineSyncStatus.idle,
|
|
this.pendingPushCount,
|
|
});
|
|
|
|
final String? serverCursor;
|
|
final DateTime? lastSuccessfulSyncAt;
|
|
final DateTime? lastAttemptAt;
|
|
final DateTime? lastFailureAt;
|
|
final OnlineSyncStatus status;
|
|
final int? pendingPushCount;
|
|
|
|
SyncMetadataSnapshot copyWith({
|
|
Object? serverCursor = _portsUnchanged,
|
|
Object? lastSuccessfulSyncAt = _portsUnchanged,
|
|
Object? lastAttemptAt = _portsUnchanged,
|
|
Object? lastFailureAt = _portsUnchanged,
|
|
OnlineSyncStatus? status,
|
|
Object? pendingPushCount = _portsUnchanged,
|
|
}) {
|
|
return SyncMetadataSnapshot(
|
|
serverCursor: serverCursor == _portsUnchanged
|
|
? this.serverCursor
|
|
: serverCursor as String?,
|
|
lastSuccessfulSyncAt: lastSuccessfulSyncAt == _portsUnchanged
|
|
? this.lastSuccessfulSyncAt
|
|
: lastSuccessfulSyncAt as DateTime?,
|
|
lastAttemptAt: lastAttemptAt == _portsUnchanged
|
|
? this.lastAttemptAt
|
|
: lastAttemptAt as DateTime?,
|
|
lastFailureAt: lastFailureAt == _portsUnchanged
|
|
? this.lastFailureAt
|
|
: lastFailureAt as DateTime?,
|
|
status: status ?? this.status,
|
|
pendingPushCount: pendingPushCount == _portsUnchanged
|
|
? this.pendingPushCount
|
|
: pendingPushCount as int?,
|
|
);
|
|
}
|
|
}
|
|
|
|
final class RemoteResourceMapping {
|
|
const RemoteResourceMapping({
|
|
required this.resourceType,
|
|
required this.clientId,
|
|
required this.serverId,
|
|
required this.serverUpdatedAt,
|
|
});
|
|
|
|
final SyncResourceType resourceType;
|
|
final String clientId;
|
|
final String serverId;
|
|
final DateTime serverUpdatedAt;
|
|
}
|
|
|
|
final class PendingSyncChange {
|
|
const PendingSyncChange({required this.changeLogIds, required this.item});
|
|
|
|
final List<String> changeLogIds;
|
|
final RemoteSyncPushItem item;
|
|
}
|
|
|
|
abstract interface class SyncMetadataRepository {
|
|
Future<SyncMetadataSnapshot> read();
|
|
Future<void> save(SyncMetadataSnapshot metadata);
|
|
}
|
|
|
|
abstract interface class RemoteResourceMappingRepository {
|
|
Future<RemoteResourceMapping?> find({
|
|
required SyncResourceType resourceType,
|
|
required String clientId,
|
|
});
|
|
Future<void> save(RemoteResourceMapping mapping);
|
|
}
|
|
|
|
abstract interface class LocalSyncChangeRepository {
|
|
Future<List<PendingSyncChange>> listPendingChanges();
|
|
Future<void> markChangesSynced(List<String> changeLogIds, DateTime syncedAt);
|
|
Future<bool> applyRemoteItem(RemoteSyncedItem item);
|
|
}
|
|
|
|
final class RemoteShareSendResult {
|
|
const RemoteShareSendResult({
|
|
required this.shareId,
|
|
required this.recipientUserIds,
|
|
required this.unresolvedEmails,
|
|
});
|
|
|
|
final String shareId;
|
|
final List<String> recipientUserIds;
|
|
final List<String> unresolvedEmails;
|
|
}
|
|
|
|
enum ShareSendStatus { sent, notConnected, queued }
|
|
|
|
final class ShareSendResult {
|
|
const ShareSendResult({
|
|
required this.status,
|
|
this.shareId,
|
|
this.recipientUserIds = const [],
|
|
this.unresolvedEmails = const [],
|
|
this.pendingActionId,
|
|
});
|
|
|
|
final ShareSendStatus status;
|
|
final String? shareId;
|
|
final List<String> recipientUserIds;
|
|
final List<String> unresolvedEmails;
|
|
final String? pendingActionId;
|
|
}
|
|
|
|
abstract interface class RemoteShareApi {
|
|
Future<RemoteShareSendResult> sendShare({
|
|
required ShareResourceType resourceType,
|
|
required Map<String, Object?> payload,
|
|
required List<String> recipientEmails,
|
|
required String token,
|
|
});
|
|
|
|
Future<List<ShareInboxItem>> fetchInbox(String token);
|
|
Future<RemoteSyncedItem> acceptShare(String shareId, String token);
|
|
Future<void> declineShare(String shareId, String token);
|
|
Future<void> revokeShare(String shareId, String token);
|
|
}
|
|
|
|
abstract interface class ShareInboxRepository {
|
|
Future<List<ShareInboxItem>> listAll();
|
|
Future<ShareInboxItem?> findByShareId(String shareId);
|
|
Future<void> upsert(ShareInboxItem item);
|
|
Future<void> upsertAll(List<ShareInboxItem> items);
|
|
Future<void> markStatus(
|
|
String shareId,
|
|
ShareInboxStatus status,
|
|
DateTime respondedAt,
|
|
);
|
|
}
|
|
|
|
abstract interface class PendingShareActionRepository {
|
|
Future<List<PendingShareAction>> listPending();
|
|
Future<void> add(PendingShareAction action);
|
|
Future<void> markSucceeded(String id, DateTime attemptedAt);
|
|
Future<void> markFailed(String id, DateTime attemptedAt);
|
|
}
|
|
|
|
const Object _portsUnchanged = Object();
|
|
|
|
abstract interface class ExerciseRepository {
|
|
Future<Exercise?> findById(String id);
|
|
Future<List<Exercise>> listActive();
|
|
Future<bool> isReferencedByProgram(String id);
|
|
Future<void> save(Exercise exercise);
|
|
}
|
|
|
|
final class StoredMediaFile {
|
|
const StoredMediaFile({
|
|
required this.localUri,
|
|
required this.mimeType,
|
|
required this.sizeBytes,
|
|
this.width,
|
|
this.height,
|
|
this.durationMs,
|
|
});
|
|
|
|
final String localUri;
|
|
final String? mimeType;
|
|
final int sizeBytes;
|
|
final int? width;
|
|
final int? height;
|
|
final int? durationMs;
|
|
}
|
|
|
|
abstract interface class LocalMediaStorage {
|
|
Future<StoredMediaFile> importFile({
|
|
required String sourcePath,
|
|
required MediaKind kind,
|
|
required String stableFileName,
|
|
});
|
|
|
|
Future<Set<String>> listManagedLocalUris();
|
|
Future<void> deleteByLocalUri(String localUri);
|
|
}
|
|
|
|
abstract interface class MediaAssetRepository {
|
|
Future<MediaAsset?> findById(String id);
|
|
Future<List<MediaAsset>> listActive();
|
|
Future<void> save(MediaAsset mediaAsset);
|
|
}
|
|
|
|
abstract interface class ProgramRepository {
|
|
Future<Program?> findById(String id);
|
|
Future<List<Program>> listActive();
|
|
Future<void> save(Program program);
|
|
Future<void> saveExercise(ProgramExercise exercise);
|
|
Future<void> replaceExercises(Program program, DateTime deletedAt);
|
|
}
|
|
|
|
abstract interface class WorkoutTemplateRepository {
|
|
Future<WorkoutTemplate?> findById(String id);
|
|
Future<List<WorkoutTemplate>> listActive();
|
|
Future<void> save(WorkoutTemplate template);
|
|
Future<void> saveProgram(WorkoutTemplateProgram program);
|
|
Future<void> saveOverride(WorkoutTemplateExerciseOverride override);
|
|
Future<void> replaceComposition(WorkoutTemplate template, DateTime deletedAt);
|
|
}
|
|
|
|
abstract interface class ActiveSessionRepository {
|
|
Future<ActiveWorkoutSession?> findById(String id);
|
|
Future<ActiveWorkoutSession?> findOpen();
|
|
Future<void> save(ActiveWorkoutSession session);
|
|
Future<void> saveSetResult(ActiveSetResult result);
|
|
Future<void> saveSetTimerState(ActiveSetTimerState state);
|
|
Future<void> saveRestState(ActiveRestState restState);
|
|
Future<void> saveScoreStopwatchState(ActiveScoreStopwatchState state);
|
|
Future<void> saveExerciseStepProgressState(
|
|
ActiveExerciseStepProgressState state,
|
|
);
|
|
Future<void> saveExerciseStepResult(ActiveExerciseStepResult result);
|
|
Future<void> deleteScoreStopwatchState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
required DateTime deletedAt,
|
|
});
|
|
Future<ActiveRestState?> findRestStateById(String id);
|
|
Future<ActiveScoreStopwatchState?> findScoreStopwatchState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
});
|
|
Future<ActiveSetTimerState?> findSetTimerState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
});
|
|
Future<ActiveExerciseStepProgressState?> findExerciseStepProgressState({
|
|
required String sessionId,
|
|
required int programIndex,
|
|
required int exerciseIndex,
|
|
required int setIndex,
|
|
});
|
|
Future<List<ActiveSetResult>> listSetResults(String sessionId);
|
|
Future<List<ActiveSetTimerState>> listSetTimerStates(String sessionId);
|
|
Future<List<ActiveExerciseStepProgressState>> listExerciseStepProgressStates(
|
|
String sessionId,
|
|
);
|
|
Future<List<ActiveExerciseStepResult>> listExerciseStepResults(
|
|
String sessionId,
|
|
);
|
|
Future<List<ActiveRestState>> listRestStates(String sessionId);
|
|
Future<List<ActiveScoreStopwatchState>> listScoreStopwatchStates(
|
|
String sessionId,
|
|
);
|
|
}
|
|
|
|
abstract interface class WorkoutHistoryRepository {
|
|
Future<WorkoutHistory?> findById(String id);
|
|
Future<List<WorkoutHistory>> listActive();
|
|
Future<void> save(WorkoutHistory history);
|
|
Future<void> saveSetResult(WorkoutHistorySetResult result);
|
|
Future<void> saveStepResult(WorkoutHistoryStepResult result);
|
|
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,
|
|
required this.pulledChanges,
|
|
this.skipped = false,
|
|
this.failed = false,
|
|
this.message,
|
|
});
|
|
|
|
final int pushedChanges;
|
|
final int pulledChanges;
|
|
final bool skipped;
|
|
final bool failed;
|
|
final String? message;
|
|
}
|
|
|
|
abstract interface class SyncGateway {
|
|
Future<SyncRunSummary> synchronize();
|
|
}
|
|
|
|
final class NoOpSyncGateway implements SyncGateway {
|
|
const NoOpSyncGateway();
|
|
|
|
@override
|
|
Future<SyncRunSummary> synchronize() async {
|
|
return const SyncRunSummary(pushedChanges: 0, pulledChanges: 0);
|
|
}
|
|
}
|