Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
3d07843
docs: design profile tab and scoped preferences
9thLevelSoftware Jul 11, 2026
af421be
docs: add profile feature implementation plans
9thLevelSoftware Jul 11, 2026
dc06f2a
docs: fix profile data task ordering
9thLevelSoftware Jul 11, 2026
4146f73
feat: define profile preference documents
9thLevelSoftware Jul 11, 2026
ac84d9b
docs: clarify profile migration fallback seed
9thLevelSoftware Jul 11, 2026
9e249b3
feat: add profile preference schema
9thLevelSoftware Jul 11, 2026
bc0b828
feat: add active profile preference facade
9thLevelSoftware Jul 12, 2026
3cd4d6a
fix: harden profile context recovery
9thLevelSoftware Jul 12, 2026
010ffb1
docs: align profile migration task bindings
9thLevelSoftware Jul 12, 2026
1af3b28
feat: migrate legacy profile preferences at startup
9thLevelSoftware Jul 12, 2026
631708c
fix: make legacy preference migration deterministic
9thLevelSoftware Jul 12, 2026
1a84814
docs: harden profile consumer routing plan
9thLevelSoftware Jul 12, 2026
0ef8adb
refactor: route training settings through active profile
9thLevelSoftware Jul 12, 2026
2103af0
docs: ground VBT gating in real runtime seams
9thLevelSoftware Jul 12, 2026
2a4236a
feat: gate live VBT by active profile
9thLevelSoftware Jul 12, 2026
0f9d035
docs: make profile deletion schema complete
9thLevelSoftware Jul 12, 2026
9753f15
fix: merge profile data safely on deletion
9thLevelSoftware Jul 12, 2026
26993be
docs: harden profile backup migration plan
9thLevelSoftware Jul 12, 2026
015192c
docs: harden profile foundation verification plan
9thLevelSoftware Jul 12, 2026
75e8da3
docs: harden profile backend handoff contract
9thLevelSoftware Jul 12, 2026
6ddf903
feat: back up profile preferences in schema v5
9thLevelSoftware Jul 12, 2026
207e0b6
docs(sync): add secured profile preferences schema handoff
9thLevelSoftware Jul 12, 2026
1df2c8c
docs: harden profile sync implementation plan
9thLevelSoftware Jul 12, 2026
2b0d095
docs(sync): define atomic profile preference edge contract
9thLevelSoftware Jul 12, 2026
b9220a3
docs: harden profile edge handoff contract
9thLevelSoftware Jul 12, 2026
3588f6a
docs(sync): harden profile preference edge contract
9thLevelSoftware Jul 12, 2026
994f298
docs: align edge float and unicode validation
9thLevelSoftware Jul 12, 2026
43c6e65
docs(sync): preserve edge validation boundaries
9thLevelSoftware Jul 12, 2026
9575a0f
docs: harden edge auth and logging contract
9thLevelSoftware Jul 12, 2026
4f50a1e
docs(sync): fail closed at edge auth boundary
9thLevelSoftware Jul 12, 2026
6e8f286
docs: align wire key normalization with edge
9thLevelSoftware Jul 12, 2026
29f2e96
feat(sync): add profile preference wire contract
9thLevelSoftware Jul 12, 2026
b2aeb02
docs: harden profile preference sync persistence plan
9thLevelSoftware Jul 12, 2026
7637526
docs: close profile preference wrapper boundaries
9thLevelSoftware Jul 12, 2026
9c1a8da
feat(sync): add profile preference sync repository
9thLevelSoftware Jul 12, 2026
e7408f8
docs: harden profile preference wire planning
9thLevelSoftware Jul 12, 2026
1fa30e1
docs: validate pull canonicals before persistence
9thLevelSoftware Jul 12, 2026
09cf18d
fix(sync): validate pull canonicals before lookup
9thLevelSoftware Jul 12, 2026
1245644
docs: harden profile preference transport plan
9thLevelSoftware Jul 12, 2026
001682e
feat(sync): plan profile preference wire chunks
9thLevelSoftware Jul 12, 2026
cf43f3b
docs: enforce edge timestamp grammar on pull
9thLevelSoftware Jul 12, 2026
759d48c
fix(sync): enforce strict preference timestamps
9thLevelSoftware Jul 12, 2026
3635f1a
docs: align pull timestamp parser with edge
9thLevelSoftware Jul 12, 2026
a6b1c69
feat(sync): enforce profile preference transport limits
9thLevelSoftware Jul 12, 2026
a9bf5d9
docs: harden preference push orchestration plan
9thLevelSoftware Jul 12, 2026
7c68006
feat(sync): orchestrate profile preference pushes
9thLevelSoftware Jul 12, 2026
943139c
docs: harden preference pull orchestration plan
9thLevelSoftware Jul 12, 2026
58ebc50
docs: harden profile preference sync verification plan
9thLevelSoftware Jul 12, 2026
cb3cdc7
feat(sync): merge canonical profile preferences on pull
9thLevelSoftware Jul 12, 2026
6f2c3dd
docs: harden assessment profile ownership plan
9thLevelSoftware Jul 12, 2026
1cfae1c
docs: close assessment ownership races
9thLevelSoftware Jul 12, 2026
d08dba8
docs: harden profile insight data plan
9thLevelSoftware Jul 12, 2026
f111783
fix: scope strength assessments to active profile
9thLevelSoftware Jul 12, 2026
02695a1
fix: close assessment cleanup races
9thLevelSoftware Jul 12, 2026
aae1b17
docs: harden profile resource contract
9thLevelSoftware Jul 12, 2026
f43f1dc
feat: resolve profile scoped exercise one rep max
9thLevelSoftware Jul 12, 2026
771f700
feat: add localized profile screen copy
9thLevelSoftware Jul 12, 2026
74858c8
fix: localize profile color accessibility labels
9thLevelSoftware Jul 12, 2026
aa1bd40
docs: harden profile identity UI plan
9thLevelSoftware Jul 12, 2026
6015286
docs: harden profile insights state plan
9thLevelSoftware Jul 12, 2026
3398602
refactor: extract profile identity components
9thLevelSoftware Jul 12, 2026
3d1723b
fix: harden profile identity accessibility
9thLevelSoftware Jul 12, 2026
d8f951d
feat: add profile scoped insights state
9thLevelSoftware Jul 12, 2026
e03b745
docs: harden profile screen plan
9thLevelSoftware Jul 12, 2026
e4513a3
docs: harden profile navigation plan
9thLevelSoftware Jul 12, 2026
39193a9
feat: build profile identity and exercise insights
9thLevelSoftware Jul 12, 2026
6052630
docs: harden profile preferences plan
9thLevelSoftware Jul 12, 2026
30bb676
fix: preserve profile deletion ownership
9thLevelSoftware Jul 12, 2026
d409f02
docs: harden global settings pruning plan
9thLevelSoftware Jul 12, 2026
88fc119
docs: harden profile ownership verification plan
9thLevelSoftware Jul 12, 2026
1ba605a
feat: add profile tab and sole root switcher
9thLevelSoftware Jul 12, 2026
2e0038f
docs: add final profile feature verification plan
9thLevelSoftware Jul 12, 2026
cbf7756
feat: move typed preferences to profile
9thLevelSoftware Jul 12, 2026
eabebb9
refactor: keep settings global only
9thLevelSoftware Jul 12, 2026
facfe1d
test: advance profile dialog ownership contract
9thLevelSoftware Jul 12, 2026
d894689
test: guard sole profile switcher ownership
9thLevelSoftware Jul 12, 2026
cd904e7
docs: plan profile readiness fixtures
9thLevelSoftware Jul 12, 2026
3e05b60
test: define schema 42 upgrade fixture
9thLevelSoftware Jul 13, 2026
ae90547
test: wait for profile migration before inspection
9thLevelSoftware Jul 13, 2026
1e54d11
test: pin schema 42 readiness polling block
9thLevelSoftware Jul 13, 2026
5d63286
test: isolate profile QA fixtures to debug
9thLevelSoftware Jul 13, 2026
2cb127d
test: add deterministic profile QA seed
9thLevelSoftware Jul 13, 2026
81928e7
docs: center Home in bottom navigation plan
9thLevelSoftware Jul 13, 2026
a9028ca
fix: center Home in bottom navigation
9thLevelSoftware Jul 13, 2026
aa3e4a7
docs: record profile readiness fixture evidence
9thLevelSoftware Jul 13, 2026
8103281
fix: use localized Insights navigation semantics
9thLevelSoftware Jul 13, 2026
edc5647
docs: preserve workout rack access
9thLevelSoftware Jul 13, 2026
cb3764d
docs: record profile readiness fixture evidence
9thLevelSoftware Jul 13, 2026
6901726
docs: include VBT UI wiring in final gate
9thLevelSoftware Jul 13, 2026
a02a0ca
docs: validate pinned backend digest semantically
9thLevelSoftware Jul 13, 2026
31a4e76
test: enforce profile QA release boundary
9thLevelSoftware Jul 13, 2026
361677c
docs: correct profile readiness gate evidence
9thLevelSoftware Jul 13, 2026
eff8470
docs: record completed profile readiness verification
9thLevelSoftware Jul 13, 2026
efac83b
Merge branch 'main' into codex/profile-readiness-fixtures
9thLevelSoftware Jul 13, 2026
e15c795
fix: resolve verbal encouragement merge conflict
9thLevelSoftware Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .superpowers/sdd/data-task-5-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Task 5 Report: Active-Profile Training Settings

## Scope and baseline

- Base commit: `1a84814990db2129ccd587955eafab7f1d929af1`
- Branch: `codex/add-profile-tab`
- Scope: Task 5 only. No Task 6 work or tracked design/plan documents were changed.

## Implemented

- Reworked `SettingsManager` into a global-plus-active-profile compatibility facade. Profile-owned setters capture the originating Ready profile, serialize section changes, reject stale/switching writes, preserve consent/VBT cascades and clamps, and never write legacy Settings.
- Seeded the compatibility stream and every derived `StateFlow` synchronously from an already-Ready profile, avoiding transient legacy/default values for immediate consumers.
- Narrowed the `PreferencesManager` abstraction to global writes and deprecated migration-only quick-start reads. Concrete legacy profile writers remain internal solely for migration-source tests; the production-call audit found no callers.
- Added repository-level atomic `mutateCore`, used by both SettingsManager and health import, so a long health read changes only body weight against the latest core section without reverting concurrent unit/increment updates.
- Added `RequiredMigrationGate`; required-migration awaiters now terminate on Ready or throw `RequiredMigrationFailedException` on Failed. Health maps migration failure and profile-context failures to `HealthBodyWeightSyncResult.Failed`.
- Replaced the production equipment-rack binding with a lifecycle-owned active-profile repository and retained the Settings implementation only as an explicit legacy implementation.
- Routed SafeWord effective-state gating, quick-start documents and auto-saves, DWSM VBT reads, MainViewModel LED unlocks, BLE color application, and workout start gating through the active Ready profile.
- Preserved complete single-exercise documents, rack IDs, per-set arrays, and Just Lift conversion fidelity; the existing Just Lift runtime `Int` boundary intentionally rounds the persisted float.
- Made `JustLiftScreen` reload defaults on Ready profile-ID changes and suppress parameter writes while Switching or while the current profile's defaults are loading.
- Updated Koin bindings and shared Android-host/DWSM fixtures to use one Ready `FakeUserProfileRepository` per fixture.
- Replaced the adults-only dialog's former callback sequence with the serialized composite operation and removed the now-dead callback/comments.

## TDD evidence

- Initial consumer RED: 57 tests executed, 6 expected failures covering legacy-setting isolation, A/B rack switching, health active-core writes, SafeWord effective gating, BLE profile color, and the Ready start gate.
- Additional RED regressions:
- health profile transition surfaced an uncaught `ProfileContextUnavailableException`;
- immediate `SettingsManager.userPreferences` exposed the legacy initial snapshot;
- final review set executed 21 tests with 4 intended failures: stale health core overwrite, failed migration gate handling, migration await timeout, and one-shot Just Lift defaults loading.
- The same final review set passed 21/21 after the fixes.

## Final verification

All commands used Android Studio's JBR and `-Pskip.supabase.check=true`.

- Required focused consumer suite: **128 tests, 0 failures, 0 errors**.
- Required DWSM/regression suite: **173 tests, 0 failures, 0 errors**.
- `:shared:compileAndroidMain` plus `KoinModuleVerifyTest`: **build passed; 1 test, 0 failures, 0 errors**.
- Unfiltered `:shared:testAndroidHostTest`: **2516 tests, 0 failures, 0 errors**.
- `:shared:compileKotlinIosArm64`: **build passed**.
- Independent full-diff review: **no remaining Critical or Important findings**.
- Static boundary audit: no common-main profile-owned calls through `PreferencesManager`, no common-main legacy quick-start calls, and production DI binds `ProfileEquipmentRackRepository` only.
- `git diff --check`: passed (Git reports only the repository's Windows line-ending notices).

## Notes

- Existing project compiler/deprecation warnings remain; verification introduced no build or test failures.
- `openspec/AGENTS.md` referenced by the root instructions is absent in this checkout; the complete Task 5 brief was used as the authoritative implementation contract.
41 changes: 41 additions & 0 deletions .superpowers/sdd/data-task-6-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Data Task 6 implementation report

Base: `2103af00`
Commit subject: `feat: gate live VBT by active profile`

## Delivered behavior

- Preserved unconditional real biomechanics capture and extracted the existing live decision into `ActiveSessionEngine.evaluateLatestVbtResult()` with the working-rep guard inside that production seam.
- Added one immutable `VbtRuntimeSettings(enabled, velocityLossThresholdPercent, autoEndOnVelocityLoss)` `StateFlow` in `WorkoutCoordinator`; live evaluation reads it once per completed rep.
- Initialized and updated the complete VBT triple from `SettingsManager.userPreferences`, using `map` plus `distinctUntilChanged`, rethrowing collector cancellation, and adding no new DI binding.
- Disabled VBT now suppresses only velocity-loss interpretation, threshold/verbal events, and auto-end. Raw rep results, set summaries, persistence, MCV/peak values, PR/progression inputs, and history remain available.
- Re-enabling preserves the configured threshold and auto-end choice and restores the prior alert/auto-end behavior without resetting the biomechanics baseline.
- Replaced the test-local verbal tracker with production policy `UserPreferences.verbalEncouragementEventOrNull()`. Local adult confirmation neutralizes vulgar and dominatrix routing while leaving persisted profile intent unchanged.
- Threaded `vbtEnabled` through `WorkoutUiState`, the `ActiveWorkoutScreen` remember key and value, both `WorkoutTab` layers, `WorkoutHud`, and `StatsPage`.
- Disabled UI keeps raw MCV and Peak visible in a neutral color while hiding Zone, velocity-loss, and estimated-reps-left interpretation. `BiomechanicsHistoryCard` remains ungated.

## TDD evidence

1. Baseline characterization: the original focused VBT/event/integration matrix passed (86 tests, zero failures).
2. Behavior-neutral seam extraction: the same matrix passed again (86 tests, zero failures).
3. Feature RED:
- The first production-backed disabled test failed because live threshold processing still auto-ended and reset the real `BiomechanicsEngine`; the XML failure was `latestRepResult` unexpectedly null, with logs showing `VELOCITY_THRESHOLD_REACHED` and auto-end after two threshold reps.
- The completed test set then failed to compile on the deliberately absent `VbtRuntimeSettings`, coherent snapshot flow, and production verbal-policy API.
- After runtime GREEN, the two UI source-contract tests remained RED until the full Compose chain was threaded and the interpretation branches were gated.
4. GREEN: the final focused matrix contains 92 tests across 9 suites with zero failures or errors. `VbtUiWiringTest` contributes three tests, including a malformed-source proof that the gate-slicing assertions reject raw metrics moved inside a VBT gate or estimated reps moved outside the loss gate.

Runtime tests use `DWSMTestHarness`, its Ready fake profile, the real `BiomechanicsEngine`, real `WorkoutState`, real `HapticEvent` stream, and `FakeBiomechanicsRepository`. Source contracts use `readProjectFile` and verify all required Compose hops plus the absence of a history gate.

## Verification

- `:shared:testAndroidHostTest --tests "*Vbt*" --tests "*VerbalEncouragementGateTest*" --tests "*WorkoutCoordinatorEventTest*" --tests "*ActiveSessionEngineIntegrationTest*"`: PASS, 92/92.
- `:shared:compileKotlinIosArm64`: PASS.
- Android main and Android host test compilation: PASS as part of the focused matrix.
- `git diff --check`: PASS (Git only reports the repository's LF-to-CRLF checkout notices).

The build continues to print existing project deprecation/performance warnings; this task introduced no new compilation failure or Koin graph change.

## Adaptations to the real codebase

- No extra harness API was required: `DWSMTestHarness` already exposed the active engine, coordinator, Ready repository, real settings facade, and fake biomechanics persistence repository needed by the production-backed tests.
- Existing `WorkoutCoordinatorEventTest` and `ActiveSessionEngineIntegrationTest` already covered event coexistence and subordinate VBT configuration. The new runtime test adds the missing master-toggle/coherent-switch/persistence/adult-policy assertions without duplicating those fixtures.
140 changes: 140 additions & 0 deletions androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,131 @@ abstract class VerifyReleaseCueResourcesTask : DefaultTask() {
}
}

abstract class VerifyQaReleaseBoundaryTask : DefaultTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val forbiddenMarkersFile: RegularFileProperty

@get:Internal
abstract val releaseApkDir: DirectoryProperty

@get:Internal
abstract val releaseIntermediatesDir: DirectoryProperty

@get:Internal
abstract val projectRootDir: DirectoryProperty

@TaskAction
fun verifyReleaseBoundary() {
fun ByteArray.containsSequence(sequence: ByteArray): Boolean {
if (sequence.isEmpty()) return true
if (sequence.size > size) return false

for (startIndex in 0..size - sequence.size) {
var matches = true
for (offset in sequence.indices) {
if (this[startIndex + offset] != sequence[offset]) {
matches = false
break
}
}
if (matches) return true
}
return false
}

val projectRoot = projectRootDir.get().asFile
val markerFile = forbiddenMarkersFile.get().asFile
val forbiddenMarkers = markerFile.readLines()
.map(String::trim)
.filter(String::isNotEmpty)

if (forbiddenMarkers.isEmpty()) {
throw GradleException("QA release marker inventory is empty: ${markerFile.relativeTo(projectRoot)}")
}
if (forbiddenMarkers.distinct().size != forbiddenMarkers.size) {
throw GradleException("QA release marker inventory contains duplicates: ${markerFile.relativeTo(projectRoot)}")
}

val markerBytes = forbiddenMarkers.associateWith { it.toByteArray(Charsets.UTF_8) }
val releaseArtifacts = releaseApkDir.get().asFile
.takeIf(File::isDirectory)
?.walkTopDown()
?.filter { it.isFile && it.extension.equals("apk", ignoreCase = true) }
?.sortedBy { it.invariantSeparatorsPath }
?.toList()
.orEmpty()

if (releaseArtifacts.isEmpty()) {
throw GradleException("No Android release APK artifacts found. Run :androidApp:assembleRelease first.")
}

val releaseIntermediates = releaseIntermediatesDir.get().asFile
val releaseManifests = listOf(
"merged_manifest",
"merged_manifests",
"packaged_manifests",
).flatMap { relativeRoot ->
File(releaseIntermediates, relativeRoot)
.takeIf(File::isDirectory)
?.walkTopDown()
?.filter { file ->
file.isFile &&
file.name == "AndroidManifest.xml" &&
file.relativeTo(releaseIntermediates).invariantSeparatorsPath
.contains("/release/", ignoreCase = true)
}
?.toList()
.orEmpty()
}.distinct().sortedBy { it.invariantSeparatorsPath }

if (releaseManifests.isEmpty()) {
throw GradleException(
"No merged or packaged Android release manifests found after :androidApp:assembleRelease.",
)
}

val leaks = mutableListOf<String>()
releaseManifests.forEach { manifest ->
val content = manifest.readBytes()
markerBytes.forEach { (marker, bytes) ->
if (content.containsSequence(bytes)) {
leaks += "${manifest.relativeTo(projectRoot).invariantSeparatorsPath}: $marker"
}
}
}

var scannedEntries = 0
releaseArtifacts.forEach { artifact ->
ZipFile(artifact).use { zip ->
zip.entries().asSequence()
.filterNot { it.isDirectory }
.forEach { entry ->
scannedEntries += 1
val content = zip.getInputStream(entry).use { it.readBytes() }
markerBytes.forEach { (marker, bytes) ->
if (content.containsSequence(bytes)) {
leaks +=
"${artifact.relativeTo(projectRoot).invariantSeparatorsPath}!/${entry.name}: $marker"
}
}
}
}
}

if (leaks.isNotEmpty()) {
throw GradleException(
"Android release outputs contain debug-only QA markers:\n${leaks.joinToString("\n") { " - $it" }}",
)
}

println(
"Verified ${forbiddenMarkers.size} QA markers absent from ${releaseManifests.size} release manifests " +
"and $scannedEntries entries across ${releaseArtifacts.size} release APK(s).",
)
}
}

abstract class VerifySupabaseRuntimeConfigTask : DefaultTask() {
@get:Input
abstract val configuredSupabaseUrl: Property<String>
Expand Down Expand Up @@ -285,6 +410,18 @@ tasks.register<VerifyReleaseCueResourcesTask>("verifyReleaseCueResources") {
projectRootDir.set(rootProject.layout.projectDirectory)
}

tasks.register<VerifyQaReleaseBoundaryTask>("verifyQaReleaseBoundary") {
group = "verification"
description = "Assembles and rejects Android release outputs containing debug-only Profile QA markers."

dependsOn("assembleRelease")

forbiddenMarkersFile.set(layout.projectDirectory.file("config/qa-release-forbidden-markers.txt"))
releaseApkDir.set(layout.buildDirectory.dir("outputs/apk/release"))
releaseIntermediatesDir.set(layout.buildDirectory.dir("intermediates"))
projectRootDir.set(rootProject.layout.projectDirectory)
}

dependencies {
// Shared module
implementation(project(":shared"))
Expand Down Expand Up @@ -332,4 +469,7 @@ dependencies {
testImplementation(libs.truth)
testImplementation(libs.koin.test)
testImplementation(libs.koin.test.junit4)
testImplementation(libs.ktor.client.mock)
testImplementation(libs.multiplatform.settings)
testImplementation(libs.multiplatform.settings.test)
}
7 changes: 7 additions & 0 deletions androidApp/config/qa-release-forbidden-markers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ProfileQa
QaBlockingPortalApiClient
QA_SEED_PROFILE
[QA] Profile
QA fixture is local-only
profile_qa_fixture_gate
com.devil.phoenixproject.qa
18 changes: 18 additions & 0 deletions androidApp/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:name=".qa.ProfileQaDebugApp"
tools:replace="android:name">

<receiver
android:name=".qa.ProfileQaSeedReceiver"
android:exported="true">
<intent-filter>
<action android:name="com.devil.phoenixproject.QA_SEED_PROFILE" />
</intent-filter>
</receiver>
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.devil.phoenixproject.qa

import com.devil.phoenixproject.VitruvianApp
import com.devil.phoenixproject.data.repository.AssessmentRepository
import com.devil.phoenixproject.data.repository.ExerciseRepository
import com.devil.phoenixproject.data.repository.PersonalRecordRepository
import com.devil.phoenixproject.data.repository.RepMetricRepository
import com.devil.phoenixproject.data.repository.UserProfileRepository
import com.devil.phoenixproject.data.repository.VelocityOneRepMaxRepository
import com.devil.phoenixproject.data.repository.WorkoutRepository
import com.devil.phoenixproject.data.sync.PortalApiClient
import com.devil.phoenixproject.data.sync.PortalTokenStorage
import com.devil.phoenixproject.data.sync.SupabaseConfig
import com.devil.phoenixproject.database.VitruvianDatabase
import org.koin.core.context.loadKoinModules
import org.koin.dsl.module

class ProfileQaDebugApp : VitruvianApp() {
override fun onCreate() {
super.onCreate()

loadKoinModules(
module {
single { ProfileQaFixtureGate(this@ProfileQaDebugApp) }
single {
ProfileQaSeeder(
userProfileRepository = get<UserProfileRepository>(),
exerciseRepository = get<ExerciseRepository>(),
workoutRepository = get<WorkoutRepository>(),
repMetricRepository = get<RepMetricRepository>(),
personalRecordRepository = get<PersonalRecordRepository>(),
assessmentRepository = get<AssessmentRepository>(),
velocityOneRepMaxRepository = get<VelocityOneRepMaxRepository>(),
database = get<VitruvianDatabase>(),
)
}
single<PortalApiClient> {
QaBlockingPortalApiClient(
supabaseConfig = get<SupabaseConfig>(),
tokenStorage = get<PortalTokenStorage>(),
fixtureGate = get<ProfileQaFixtureGate>(),
)
}
},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.devil.phoenixproject.qa

import android.content.Context

class ProfileQaFixtureGate(context: Context) {
private val preferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE)

fun isEnabled(): Boolean = preferences.getBoolean(KEY_ENABLED, false)

fun enable() {
check(preferences.edit().putBoolean(KEY_ENABLED, true).commit()) {
"Could not persist the profile QA fixture gate"
}
}

private companion object {
const val PREFERENCES_FILE = "profile_qa_fixture_gate"
const val KEY_ENABLED = "enabled"
}
}
Loading
Loading