Files
GameTime/lib/application/app_bootstrap.dart
Blomios b12a04ba30 feat(monetization-261): prepare entitlement infrastructure and quota enforcement for release
- Add EntitlementSnapshot, EntitlementRevalidationUseCase, and BillingUseCases
- Add DriftEntitlementSnapshotRepository for offline entitlement caching
- Add HealthConnect gateway and integration
- Add quota enforcement in share acceptance (server and UI)
- Add entitlement API endpoints in server
- Add privacy policy v1 and release compliance checklist
- Add QA gates and device runbooks for release validation
- Add demo content screen and settings screen

Ticket #261
2026-08-28 23:12:25 +02:00

355 lines
14 KiB
Dart

import '../infrastructure/health_connect/health_connect.dart';
import '../infrastructure/local/local.dart';
import '../infrastructure/remote/remote.dart';
import '../infrastructure/security/security.dart';
import '../infrastructure/session_notification/session_notification.dart';
import '../infrastructure/watch_bridge/watch_bridge.dart';
import 'application.dart';
abstract interface class AppDependencies {
AuthUseCases get authUseCases;
ExerciseUseCases get exerciseUseCases;
MediaUseCases get mediaUseCases;
ProgramUseCases get programUseCases;
WorkoutTemplateUseCases get workoutTemplateUseCases;
ActiveWorkoutSessionUseCases get activeWorkoutSessionUseCases;
ActiveExerciseStepUseCases get activeExerciseStepUseCases;
ActiveWorkoutSensorUseCases get activeWorkoutSensorUseCases;
ExecutionDebugUseCase get executionDebugUseCase;
SeedQaFunctionalContentUseCase get seedQaFunctionalContentUseCase;
WorkoutTelemetryUseCases get workoutTelemetryUseCases;
WatchCompanionProjectionUseCases get watchCompanionProjectionUseCases;
CloseWorkoutSessionUseCase get closeWorkoutSessionUseCase;
WorkoutHistoryUseCases get workoutHistoryUseCases;
ProgressionStatsUseCase get progressionStatsUseCase;
ExercisePerformanceReferenceUseCase get exercisePerformanceReferenceUseCase;
WatchAlertPublisher get watchAlertPublisher;
DataExportUseCase get dataExportUseCase;
DataImportUseCase get dataImportUseCase;
HealthConnectUseCases get healthConnectUseCases;
EntitlementRevalidationUseCase get entitlementRevalidationUseCase;
SyncUseCases get syncUseCases;
ShareUseCases get shareUseCases;
}
final class AppBootstrap implements AppDependencies {
AppBootstrap._({
required this.database,
required this.authUseCases,
required this.exerciseUseCases,
required this.mediaUseCases,
required this.programUseCases,
required this.workoutTemplateUseCases,
required this.activeWorkoutSessionUseCases,
required this.activeExerciseStepUseCases,
required this.activeWorkoutSensorUseCases,
required this.executionDebugUseCase,
required this.seedQaFunctionalContentUseCase,
required this.workoutTelemetryUseCases,
required this.watchCompanionProjectionUseCases,
required this.watchCompanionCommandHandler,
required this.watchWearDataLayerAdapter,
required this.sessionNotificationCoordinator,
required this.closeWorkoutSessionUseCase,
required this.workoutHistoryUseCases,
required this.progressionStatsUseCase,
required this.exercisePerformanceReferenceUseCase,
required this.dataExportUseCase,
required this.dataImportUseCase,
required this.healthConnectUseCases,
required this.entitlementRevalidationUseCase,
required this.syncUseCases,
required this.shareUseCases,
required this.syncGateway,
});
final AppDatabase database;
@override
final AuthUseCases authUseCases;
@override
final ExerciseUseCases exerciseUseCases;
@override
final MediaUseCases mediaUseCases;
@override
final ProgramUseCases programUseCases;
@override
final WorkoutTemplateUseCases workoutTemplateUseCases;
@override
final ActiveWorkoutSessionUseCases activeWorkoutSessionUseCases;
@override
final ActiveExerciseStepUseCases activeExerciseStepUseCases;
@override
final ActiveWorkoutSensorUseCases activeWorkoutSensorUseCases;
@override
final ExecutionDebugUseCase executionDebugUseCase;
@override
final SeedQaFunctionalContentUseCase seedQaFunctionalContentUseCase;
@override
final WorkoutTelemetryUseCases workoutTelemetryUseCases;
@override
final WatchCompanionProjectionUseCases watchCompanionProjectionUseCases;
final WatchCompanionCommandHandler watchCompanionCommandHandler;
final WatchWearDataLayerAdapter watchWearDataLayerAdapter;
@override
WatchAlertPublisher get watchAlertPublisher => watchWearDataLayerAdapter;
final SessionNotificationCoordinator sessionNotificationCoordinator;
@override
final CloseWorkoutSessionUseCase closeWorkoutSessionUseCase;
@override
final WorkoutHistoryUseCases workoutHistoryUseCases;
@override
final ProgressionStatsUseCase progressionStatsUseCase;
@override
final ExercisePerformanceReferenceUseCase exercisePerformanceReferenceUseCase;
@override
final DataExportUseCase dataExportUseCase;
@override
final DataImportUseCase dataImportUseCase;
@override
final HealthConnectUseCases healthConnectUseCases;
@override
final EntitlementRevalidationUseCase entitlementRevalidationUseCase;
@override
final SyncUseCases syncUseCases;
@override
final ShareUseCases shareUseCases;
final SyncGateway syncGateway;
static Future<AppBootstrap> create() async {
final database = AppDatabase.open();
await database.customSelect('SELECT 1').get();
final exerciseRepository = DriftExerciseRepository(database);
final mediaRepository = DriftMediaAssetRepository(database);
final onlineAccountRepository = DriftOnlineAccountRepository(database);
final syncMetadataRepository = DriftSyncMetadataRepository(database);
final mappingRepository = DriftRemoteResourceMappingRepository(database);
final localSyncChangeRepository = DriftLocalSyncChangeRepository(database);
final starterSeedRepository = DriftStarterSeedRepository(database);
final shareInboxRepository = DriftShareInboxRepository(database);
final pendingShareActionRepository = DriftPendingShareActionRepository(
database,
);
final entitlementSnapshotRepository = DriftEntitlementSnapshotRepository(
database,
);
final programRepository = DriftProgramRepository(database);
final templateRepository = DriftWorkoutTemplateRepository(database);
final activeSessionRepository = DriftActiveSessionRepository(database);
final historyRepository = DriftWorkoutHistoryRepository(database);
final telemetryRepository = DriftWorkoutTelemetryRepository(database);
final progressionStatsRepository = DriftProgressionStatsRepository(
database,
);
final performanceReferenceRepository =
DriftExercisePerformanceReferenceRepository(database);
final localDataBackupRepository = DriftLocalDataBackupRepository(database);
const localBackupMediaStore = PathProviderLocalMediaStorage();
final ids = LocalIdGenerator();
const clock = SystemClock();
const authTokenStore = SecureStorageAuthTokenStore();
const originDeviceId = 'local-device';
final activeWorkoutSessionUseCases = ActiveWorkoutSessionUseCases(
sessionRepository: activeSessionRepository,
templateRepository: templateRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
);
final activeExerciseStepUseCases = ActiveExerciseStepUseCases(
sessionRepository: activeSessionRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
activeSessionUseCases: activeWorkoutSessionUseCases,
);
final activeWorkoutSensorUseCases = ActiveWorkoutSensorUseCases(
clock: clock,
);
final executionDebugUseCase = ExecutionDebugUseCase(
sessionRepository: activeSessionRepository,
clock: clock,
);
final seedQaFunctionalContentUseCase = SeedQaFunctionalContentUseCase(
seedStateRepository: starterSeedRepository,
contentRepository: starterSeedRepository,
clock: clock,
originDeviceId: originDeviceId,
);
final workoutTelemetryUseCases = WorkoutTelemetryUseCases(
repository: telemetryRepository,
sessionRepository: activeSessionRepository,
clock: clock,
ids: ids,
);
final watchCompanionProjectionUseCases = WatchCompanionProjectionUseCases(
sessionRepository: activeSessionRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
activeWorkoutSensorUseCases: activeWorkoutSensorUseCases,
);
final watchCompanionCommandHandler = WatchCompanionCommandHandler(
sessionRepository: activeSessionRepository,
activeSessionUseCases: activeWorkoutSessionUseCases,
stepUseCases: activeExerciseStepUseCases,
projectionSource: watchCompanionProjectionUseCases,
);
final workoutHistoryUseCases = WorkoutHistoryUseCases(
repository: historyRepository,
clock: clock,
);
final healthConnectUseCases = HealthConnectUseCases(
gateway: const MethodChannelHealthConnectGateway(),
historyRepository: historyRepository,
telemetryUseCases: workoutTelemetryUseCases,
);
final watchWearDataLayerAdapter = WatchWearDataLayerAdapter(
nativeChannel: const MethodChannelWatchBridgeNativeChannel(),
commandIngress: watchCompanionCommandHandler,
projectionSource: watchCompanionProjectionUseCases,
workoutHistoryUseCases: workoutHistoryUseCases,
activeWorkoutSensorUseCases: activeWorkoutSensorUseCases,
workoutTelemetryUseCases: workoutTelemetryUseCases,
);
final sessionNotificationCoordinator = SessionNotificationCoordinator(
projections: watchCompanionProjectionUseCases.projections,
gateway: const MethodChannelSessionNotificationGateway(),
);
await watchWearDataLayerAdapter.start();
sessionNotificationCoordinator.start();
await SeedStarterContentUseCase(
seedStateRepository: starterSeedRepository,
contentRepository: starterSeedRepository,
clock: clock,
originDeviceId: originDeviceId,
).run();
final remoteAuthApi = HttpRemoteAuthApi(
HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
);
final remoteSyncApi = HttpRemoteSyncApi(
HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
);
final remoteShareApi = HttpRemoteShareApi(
HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
);
final remoteEntitlementSnapshotSource = HttpRemoteEntitlementSnapshotSource(
client: HttpApiClient(baseUrl: Uri.parse(HttpApiClient.defaultBaseUrl)),
tokenStore: authTokenStore,
);
return AppBootstrap._(
database: database,
authUseCases: AuthUseCases(
tokenStore: authTokenStore,
accountRepository: onlineAccountRepository,
remoteAuthApi: remoteAuthApi,
clock: clock,
ids: ids,
),
exerciseUseCases: ExerciseUseCases(
repository: exerciseRepository,
programRepository: programRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
mediaUseCases: MediaUseCases(
mediaRepository: mediaRepository,
exerciseRepository: exerciseRepository,
storage: const PathProviderLocalMediaStorage(),
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
programUseCases: ProgramUseCases(
programRepository: programRepository,
exerciseRepository: exerciseRepository,
templateRepository: templateRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
workoutTemplateUseCases: WorkoutTemplateUseCases(
templateRepository: templateRepository,
programRepository: programRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
activeWorkoutSessionUseCases: activeWorkoutSessionUseCases,
activeExerciseStepUseCases: activeExerciseStepUseCases,
activeWorkoutSensorUseCases: activeWorkoutSensorUseCases,
executionDebugUseCase: executionDebugUseCase,
seedQaFunctionalContentUseCase: seedQaFunctionalContentUseCase,
workoutTelemetryUseCases: workoutTelemetryUseCases,
watchCompanionProjectionUseCases: watchCompanionProjectionUseCases,
watchCompanionCommandHandler: watchCompanionCommandHandler,
watchWearDataLayerAdapter: watchWearDataLayerAdapter,
sessionNotificationCoordinator: sessionNotificationCoordinator,
closeWorkoutSessionUseCase: CloseWorkoutSessionUseCase(
sessionRepository: activeSessionRepository,
historyRepository: historyRepository,
telemetryRepository: telemetryRepository,
healthConnectUseCases: healthConnectUseCases,
activeWorkoutSensorUseCases: activeWorkoutSensorUseCases,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
workoutHistoryUseCases: workoutHistoryUseCases,
progressionStatsUseCase: ProgressionStatsUseCase(
repository: progressionStatsRepository,
clock: clock,
),
exercisePerformanceReferenceUseCase: ExercisePerformanceReferenceUseCase(
repository: performanceReferenceRepository,
),
dataExportUseCase: DataExportUseCase(
repository: localDataBackupRepository,
mediaStore: localBackupMediaStore,
clock: clock,
),
dataImportUseCase: DataImportUseCase(
repository: localDataBackupRepository,
mediaStore: localBackupMediaStore,
clock: clock,
),
healthConnectUseCases: healthConnectUseCases,
entitlementRevalidationUseCase: EntitlementRevalidationUseCase(
repository: entitlementSnapshotRepository,
remote: remoteEntitlementSnapshotSource,
),
syncUseCases: SyncUseCases(
tokenStore: authTokenStore,
remoteSyncApi: remoteSyncApi,
metadataRepository: syncMetadataRepository,
mappingRepository: mappingRepository,
localChanges: localSyncChangeRepository,
clock: clock,
deviceId: originDeviceId,
),
shareUseCases: ShareUseCases(
tokenStore: authTokenStore,
remoteShareApi: remoteShareApi,
inboxRepository: shareInboxRepository,
pendingActionRepository: pendingShareActionRepository,
localChanges: localSyncChangeRepository,
programRepository: programRepository,
templateRepository: templateRepository,
clock: clock,
ids: ids,
originDeviceId: originDeviceId,
),
syncGateway: const NoOpSyncGateway(),
);
}
Future<void> dispose() async {
await sessionNotificationCoordinator.dispose();
await watchWearDataLayerAdapter.stop();
await watchCompanionProjectionUseCases.dispose();
await activeWorkoutSensorUseCases.dispose();
await database.close();
}
}