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 exercises; final Program program; final WorkoutTemplate workoutTemplate; } abstract interface class StarterSeedStateRepository { Future readAppliedStarterSeedVersion(); Future writeAppliedStarterSeedVersion(int version, DateTime appliedAt); } abstract interface class StarterContentRepository { Future isLocalContentEmpty(); Future 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 saveToken(String token, DateTime expiresAt); Future readToken(); Future clearToken(); } abstract interface class OnlineAccountRepository { Future currentSession(); Future saveSession(UserAccountSession session); Future clearSession(); } abstract interface class RemoteAuthApi { Future register({ required String email, required String password, String? displayName, }); Future login({ required String email, required String password, }); Future 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 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 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 payload; } final class RemoteSyncPullResult { const RemoteSyncPullResult({required this.serverCursor, required this.items}); final String? serverCursor; final List items; } abstract interface class RemoteSyncApi { Future push({ required String deviceId, required List items, required String token, }); Future 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 changeLogIds; final RemoteSyncPushItem item; } abstract interface class SyncMetadataRepository { Future read(); Future save(SyncMetadataSnapshot metadata); } abstract interface class RemoteResourceMappingRepository { Future find({ required SyncResourceType resourceType, required String clientId, }); Future save(RemoteResourceMapping mapping); } abstract interface class LocalSyncChangeRepository { Future> listPendingChanges(); Future markChangesSynced(List changeLogIds, DateTime syncedAt); Future applyRemoteItem(RemoteSyncedItem item); } final class RemoteShareSendResult { const RemoteShareSendResult({ required this.shareId, required this.recipientUserIds, required this.unresolvedEmails, }); final String shareId; final List recipientUserIds; final List 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 recipientUserIds; final List unresolvedEmails; final String? pendingActionId; } abstract interface class RemoteShareApi { Future sendShare({ required ShareResourceType resourceType, required Map payload, required List recipientEmails, required String token, }); Future> fetchInbox(String token); Future acceptShare(String shareId, String token); Future declineShare(String shareId, String token); Future revokeShare(String shareId, String token); } abstract interface class ShareInboxRepository { Future> listAll(); Future findByShareId(String shareId); Future upsert(ShareInboxItem item); Future upsertAll(List items); Future markStatus( String shareId, ShareInboxStatus status, DateTime respondedAt, ); } abstract interface class PendingShareActionRepository { Future> listPending(); Future add(PendingShareAction action); Future markSucceeded(String id, DateTime attemptedAt); Future markFailed(String id, DateTime attemptedAt); } const Object _portsUnchanged = Object(); abstract interface class ExerciseRepository { Future findById(String id); Future> listActive(); Future isReferencedByProgram(String id); Future 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 importFile({ required String sourcePath, required MediaKind kind, required String stableFileName, }); Future> listManagedLocalUris(); Future deleteByLocalUri(String localUri); } abstract interface class MediaAssetRepository { Future findById(String id); Future> listActive(); Future save(MediaAsset mediaAsset); } abstract interface class ProgramRepository { Future findById(String id); Future> listActive(); Future save(Program program); Future saveExercise(ProgramExercise exercise); Future replaceExercises(Program program, DateTime deletedAt); } abstract interface class WorkoutTemplateRepository { Future findById(String id); Future> listActive(); Future save(WorkoutTemplate template); Future saveProgram(WorkoutTemplateProgram program); Future saveOverride(WorkoutTemplateExerciseOverride override); Future replaceComposition(WorkoutTemplate template, DateTime deletedAt); } abstract interface class ActiveSessionRepository { Future findById(String id); Future findOpen(); Future save(ActiveWorkoutSession session); Future saveSetResult(ActiveSetResult result); Future saveSetTimerState(ActiveSetTimerState state); Future saveRestState(ActiveRestState restState); Future saveScoreStopwatchState(ActiveScoreStopwatchState state); Future saveExerciseStepProgressState( ActiveExerciseStepProgressState state, ); Future saveExerciseStepResult(ActiveExerciseStepResult result); Future deleteScoreStopwatchState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, required DateTime deletedAt, }); Future findRestStateById(String id); Future findScoreStopwatchState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, }); Future findSetTimerState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, }); Future findExerciseStepProgressState({ required String sessionId, required int programIndex, required int exerciseIndex, required int setIndex, }); Future> listSetResults(String sessionId); Future> listSetTimerStates(String sessionId); Future> listExerciseStepProgressStates( String sessionId, ); Future> listExerciseStepResults( String sessionId, ); Future> listRestStates(String sessionId); Future> listScoreStopwatchStates( String sessionId, ); } abstract interface class WorkoutHistoryRepository { Future findById(String id); Future> listActive(); Future save(WorkoutHistory history); Future saveSetResult(WorkoutHistorySetResult result); Future saveStepResult(WorkoutHistoryStepResult result); Future 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 hasAnyCompletedHistoryForExercise(String exerciseId); Future findLatestSetPerformance({ required String exerciseId, required ActivePerformanceMeasures activeMeasures, required int currentSetIndex, }); Future 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 synchronize(); } final class NoOpSyncGateway implements SyncGateway { const NoOpSyncGateway(); @override Future synchronize() async { return const SyncRunSummary(pushedChanges: 0, pulledChanges: 0); } }