diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1bed383..b0ec936 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,21 +58,12 @@ jobs: include: - id: jvm label: JVM/Desktop - tasks: >- - :ohs-player-library:jvmTest - :ohs-player-reference-app:jvmTest + tasks: ':reference-app:jvmTest' kotlinDaemonHeap: '-Xmx6g -XX:+UseG1GC' extraArgs: '' - id: android label: Android - # ohs-player-library uses the androidKotlinMultiplatformLibrary - # plugin, which doesn't register `lintDebug` - assembleAndroidMain - # is the closest "compile + verify the Android variant" task it - # exposes. ohs-player-reference-app uses classic androidTarget - # and keeps lintDebug. - tasks: >- - :ohs-player-library:assembleAndroidMain - :ohs-player-reference-app:lintDebug + tasks: ':reference-app:lintDebug' kotlinDaemonHeap: '-Xmx6g -XX:+UseG1GC' extraArgs: '' - id: js @@ -81,19 +72,14 @@ jobs: # it still runs the compile prerequisites (compileKotlinJs + # compileTestDevelopmentExecutableKotlinJs) — which is the work # we want covered. - tasks: ':ohs-player-reference-app:jsBrowserTest' + tasks: ':reference-app:jsBrowserTest' kotlinDaemonHeap: '-Xmx10g -XX:+UseG1GC' extraArgs: '' - id: wasm label: Kotlin/Wasm - tasks: >- - :ohs-player-library:wasmJsBrowserTest - :ohs-player-reference-app:wasmJsBrowserTest + tasks: ':reference-app:wasmJsBrowserTest' kotlinDaemonHeap: '-Xmx10g -XX:+UseG1GC' - # Serialize the library + reference-app wasm compiles so they - # don't run concurrently as workers and double the daemon heap - # demand even at 10g. - extraArgs: '--max-workers=1' + extraArgs: '' steps: - uses: actions/checkout@v4 @@ -162,5 +148,4 @@ jobs: - name: Compile + link ${{ matrix.label }} run: | ./gradlew --stacktrace \ - :ohs-player-library:compileKotlin${{ matrix.target }} \ - :ohs-player-reference-app:linkDebugFramework${{ matrix.target }} + :reference-app:linkDebugFramework${{ matrix.target }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 928428e..9ef344c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -138,7 +138,7 @@ jobs: ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} - run: ./gradlew :ohs-player-reference-app:assembleRelease + run: ./gradlew :reference-app:assembleRelease - name: Stage outputs shell: bash @@ -154,7 +154,7 @@ jobs: # Fail loud if AGP ever produces 0 or >1 outputs (e.g. someone enables # ABI/density splits) — the single-name cp below would silently corrupt. - apks=(ohs-player-reference-app/build/outputs/apk/release/*.apk) + apks=(reference-app/build/outputs/apk/release/*.apk) if [ ${#apks[@]} -ne 1 ]; then echo "::error::Expected exactly one APK, got ${#apks[@]}: ${apks[*]}" echo "::error::If splits/flavors were enabled, update this step to rename each variant." @@ -180,25 +180,25 @@ jobs: include: - os: ubuntu-latest label: Linux deb - task: ':ohs-player-reference-app:packageDeb' - glob: 'ohs-player-reference-app/build/compose/binaries/main/deb/*.deb' + task: ':reference-app:packageDeb' + glob: 'reference-app/build/compose/binaries/main/deb/*.deb' artifact: linux-deb - os: ubuntu-latest label: Linux rpm - task: ':ohs-player-reference-app:packageRpm' - glob: 'ohs-player-reference-app/build/compose/binaries/main/rpm/*.rpm' + task: ':reference-app:packageRpm' + glob: 'reference-app/build/compose/binaries/main/rpm/*.rpm' artifact: linux-rpm - os: windows-latest label: Windows msi - task: ':ohs-player-reference-app:packageMsi' - glob: 'ohs-player-reference-app/build/compose/binaries/main/msi/*.msi' + task: ':reference-app:packageMsi' + glob: 'reference-app/build/compose/binaries/main/msi/*.msi' artifact: windows-msi # macos-latest is Apple Silicon — the .dmg is arm64-only. Intel Macs # need the portable Linux tarball or a future universal build. - os: macos-latest label: macOS dmg - task: ':ohs-player-reference-app:packageDmg' - glob: 'ohs-player-reference-app/build/compose/binaries/main/dmg/*.dmg' + task: ':reference-app:packageDmg' + glob: 'reference-app/build/compose/binaries/main/dmg/*.dmg' artifact: macos-dmg runs-on: ${{ matrix.os }} permissions: @@ -270,13 +270,13 @@ jobs: - name: Create distributable app image env: VERSION_NAME: ${{ needs.preflight.outputs.version_name }} - run: ./gradlew :ohs-player-reference-app:createDistributable + run: ./gradlew :reference-app:createDistributable - name: Tar up the distributable shell: bash run: | shopt -s nullglob - APP_BASE=ohs-player-reference-app/build/compose/binaries/main/app + APP_BASE=reference-app/build/compose/binaries/main/app app_dirs=("$APP_BASE"/*) if [ ${#app_dirs[@]} -ne 1 ]; then echo "::error::Expected exactly one app directory in $APP_BASE, got ${#app_dirs[@]}: ${app_dirs[*]}" @@ -322,12 +322,12 @@ jobs: - name: Build web distribution env: VERSION_NAME: ${{ needs.preflight.outputs.version_name }} - run: ./gradlew :ohs-player-reference-app:wasmJsBrowserDistribution + run: ./gradlew :reference-app:wasmJsBrowserDistribution - name: Zip distribution for the GitHub Release run: | mkdir -p out - DIST=ohs-player-reference-app/build/dist/wasmJs/productionExecutable + DIST=reference-app/build/dist/wasmJs/productionExecutable test -d "$DIST" || { echo "::error::Web dist directory missing: $DIST"; exit 1; } (cd "$DIST" && zip -r "$GITHUB_WORKSPACE/out/ohs-player-web-${{ needs.preflight.outputs.version_name }}.zip" .) @@ -342,7 +342,7 @@ jobs: if: needs.preflight.outputs.is_dry_run == 'false' && needs.preflight.outputs.is_prerelease == 'false' uses: actions/upload-pages-artifact@v3 with: - path: ohs-player-reference-app/build/dist/wasmJs/productionExecutable + path: reference-app/build/dist/wasmJs/productionExecutable # Disabled alongside build-web. See the comment above build-web for the # rationale and the steps to re-enable. diff --git a/README.md b/README.md index 6464c88..0c2466f 100644 --- a/README.md +++ b/README.md @@ -4,36 +4,90 @@ A Kotlin Multiplatform and Compose Multiplatform reference client for [Open Heal The application renders healthcare UI from configuration rather than hand-written mapping code. FHIR resources are projected into typed view-state by declarative configuration, and that state is rendered by renderers resolved through a registry. The two halves — extraction and rendering — are described below, then joined in a single end-to-end example. +This repository is a GitHub template: use it as the starting point for your own OHS-based app, or just run it to see OHS in action. + +## Supported platforms + +Android, iOS, Desktop (Windows, macOS, Linux), and Web (JS and Wasm) — all from a single Kotlin source tree. Every platform is built, tested, and released by GitHub Actions; see [Deployment](#deployment). + ## Getting started -### Prerequisites +### Just want to see OHS in action? + +The desktop app is the fastest path — it needs only JDK 21, no Android SDK or Xcode: + +```shell +git clone +cd ohs-player-reference-client-app +./gradlew :reference-app:run +``` + +The app ships with bundled sample FHIR data, so it works out of the box. Other platforms: + +| Target | Command | +| --- | --- | +| Android | `./gradlew :reference-app:assembleDebug` | +| Web (Wasm) | `./gradlew :reference-app:wasmJsBrowserDevelopmentRun` | +| Web (JS) | `./gradlew :reference-app:jsBrowserDevelopmentRun` | + +For iOS, open [`iosApp/`](./iosApp) in Xcode and run, or use the run-configuration widget in a Kotlin Multiplatform IDE. + +### Start from this template + +1. Click **Use this template → Create a new repository** on GitHub (or clone and re-init). +2. Follow [Developer setup](#developer-setup-kotlin-multiplatform) below and confirm `./gradlew build` passes. +3. Work through [Customizing the template](#customizing-the-template) to make the app yours. + +### Developer setup (Kotlin Multiplatform) - JDK 21 -- Android SDK (for Android builds) +- [Android Studio](https://developer.android.com/studio) with the Kotlin Multiplatform plugin (Android builds also need the Android SDK) - Xcode (for iOS builds, macOS only) +- Optional: run [`kdoctor`](https://github.com/Kotlin/kdoctor) to verify your multiplatform environment Use `./gradlew` on macOS and Linux, and `gradlew.bat` on Windows. All commands run from the repository root. ### Build ```shell -git clone -cd ohs-player-reference-client-app ./gradlew build ``` Code generation is part of compilation. The `ig-codegen` Gradle plugin runs its `generateIgCode` task automatically before Kotlin compilation, so there is no separate generation step. -### Run +## Customizing the template -| Target | Command | +The starting points, in rough order: + +- **Application id / namespace** — `applicationId` and `namespace` in [`reference-app/build.gradle.kts`](./reference-app/build.gradle.kts), the iOS bundle id in [`iosApp/Configuration/Config.xcconfig`](./iosApp/Configuration/Config.xcconfig), and the Kotlin package `dev.ohs.player.reference.app` under `reference-app/src/*/kotlin/`. +- **Application name** — Android: `app_name` in [`reference-app/src/androidMain/res/values/strings.xml`](./reference-app/src/androidMain/res/values/strings.xml); iOS: `PRODUCT_NAME` in `Config.xcconfig`; Desktop: `packageName` in the `compose.desktop` block of `reference-app/build.gradle.kts`; Web: `` in [`reference-app/src/webMain/resources/index.html`](./reference-app/src/webMain/resources/index.html). +- **Icons** — Android launcher icons in `reference-app/src/androidMain/res/mipmap-*/`; iOS in `iosApp/iosApp/Assets.xcassets`. +- **Generated code package** — `packageName` in the `igCodegen` block of `reference-app/build.gradle.kts` (defaults to `dev.ohs.player.generated`). +- **Project names** — `rootProject.name` and the module name in [`settings.gradle.kts`](./settings.gradle.kts). Note that renaming either changes the package of the generated Compose resources class (`Res`). +- **Screens and configuration** — replace the sample `Binary-*.json` configuration under `reference-app/src/commonMain/composeResources/files/` and the feature renderers under `reference-app/src/commonMain/kotlin/.../feature/`; the rest of this README explains how those two fit together. + +## Bundled OHS libraries + +The app is assembled from the OHS Player library plus the OHS Foundational Libraries; versions are pinned in [`gradle/libs.versions.toml`](./gradle/libs.versions.toml): + +| Library | Purpose | | --- | --- | -| Android | `./gradlew :ohs-player-reference-app:assembleDebug` | -| Desktop (JVM) | `./gradlew :ohs-player-reference-app:run` | -| Web (Wasm) | `./gradlew :ohs-player-reference-app:wasmJsBrowserDevelopmentRun` | -| Web (JS) | `./gradlew :ohs-player-reference-app:jsBrowserDevelopmentRun` | +| [`dev.ohs.player:reference-library`](https://github.com/ohs-foundation/ohs-player-reference-client-library) | Config-driven views using flattened data from FHIR resources (the player) | +| `dev.ohs.fhir:fhir-model` | Typed Kotlin models for FHIR resources | +| `dev.ohs.fhir:fhir-path` | FHIRPath expression evaluation | +| `dev.ohs.fhir:fhir-data-capture` | FHIR Structured Data Capture (questionnaires) | -For iOS, open [`iosApp/`](./iosApp) in Xcode and run, or use the run-configuration widget in a Kotlin Multiplatform IDE. +### Have an existing app? + +If you cannot start from this template, depend on the player library directly — it is a library, not a framework, and can be adopted one screen at a time in any Kotlin Multiplatform or Android project: + +```kotlin +commonMain.dependencies { + implementation("dev.ohs.player:reference-library:1.0.0-alpha01") +} +``` + +The [library README](https://github.com/ohs-foundation/ohs-player-reference-client-library#readme) is a standalone user guide; this repository then serves as the worked example. ## From FHIR data to view state @@ -46,7 +100,7 @@ A screen never consumes a raw FHIR resource. It consumes a typed *view-state* ### 1. Author configuration -A `ViewDefinition` declares the columns of a view as FHIRPath expressions over a FHIR resource. Each column carries a name, a path, and a FHIR type. Excerpt from [`Binary-PatientSummary.json`](./ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummary.json): +A `ViewDefinition` declares the columns of a view as FHIRPath expressions over a FHIR resource. Each column carries a name, a path, and a FHIR type. Excerpt from [`Binary-PatientSummary.json`](./reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummary.json): ```json { @@ -67,7 +121,7 @@ A `ViewDefinition` declares the columns of a view as FHIRPath expressions over a } ``` -A `ViewJoinMap` names the view-state and binds it to a pivot `ViewDefinition` (and, where needed, joined views). [`Binary-PatientSummaryState.json`](./ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummaryState.json): +A `ViewJoinMap` names the view-state and binds it to a pivot `ViewDefinition` (and, where needed, joined views). [`Binary-PatientSummaryState.json`](./reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummaryState.json): ```json { @@ -79,7 +133,7 @@ A `ViewJoinMap` names the view-state and binds it to a pivot `ViewDefinition` (a } ``` -A `ViewConfig` declares the configuration a renderer accepts, with defaults. [`Binary-PatientCardConfig.json`](./ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-PatientCardConfig.json): +A `ViewConfig` declares the configuration a renderer accepts, with defaults. [`Binary-PatientCardConfig.json`](./reference-app/src/commonMain/composeResources/files/configs/Binary-PatientCardConfig.json): ```json { @@ -93,11 +147,11 @@ A `ViewConfig` declares the configuration a renderer accepts, with defaults. [`B } ``` -A single `CodeSystem` Binary enumerates the view-types the app renders; see [`CodeSystem-ViewTypes.json`](./ohs-player-reference-app/src/commonMain/composeResources/files/viewtypes/CodeSystem-ViewTypes.json). +A single `CodeSystem` Binary enumerates the view-types the app renders; see [`CodeSystem-ViewTypes.json`](./reference-app/src/commonMain/composeResources/files/viewtypes/CodeSystem-ViewTypes.json). ### 2. Generate typed Kotlin -The `ig-codegen` plugin reads these Binaries and emits typed sources. It is applied and configured in [`ohs-player-reference-app/build.gradle.kts`](./ohs-player-reference-app/build.gradle.kts): +The `ig-codegen` plugin reads these Binaries and emits typed sources. It is applied and configured in [`reference-app/build.gradle.kts`](./reference-app/build.gradle.kts): ```kotlin plugins { @@ -137,7 +191,7 @@ data class PatientSummaryState( ### 3. Load configuration at runtime -A `ConfigStore` holds the parsed configuration, fed by a `ConfigSource`. The reference app reads the bundled Binaries; replacing this with a network fetch is the only change required to load configuration from a backend. See [`LocalConfigSource.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt): +A `ConfigStore` holds the parsed configuration, fed by a `ConfigSource`. The reference app reads the bundled Binaries; replacing this with a network fetch is the only change required to load configuration from a backend. See [`LocalConfigSource.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt): ```kotlin object LocalConfigSource : ConfigSource { @@ -150,7 +204,7 @@ object LocalConfigSource : ConfigSource { } ``` -The store and a single extractor are wired once in [`Extraction.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt): +The store and a single extractor are wired once in [`Extraction.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt): ```kotlin object Extraction { @@ -163,7 +217,7 @@ object Extraction { `GenericStateExtractor.extract<T>()` selects the configuration for `T` by name, evaluates its FHIRPath columns against a `SearchResult`, and returns a list of typed `T`. A `SearchResult` carries the pivot resource plus any forward-included and reverse-included resources, mirroring a FHIR search response. -From [`PatientRepository.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/PatientRepository.kt): +From [`PatientRepository.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/PatientRepository.kt): ```kotlin suspend fun getPatients(): List<PatientSummaryState> = @@ -208,7 +262,7 @@ class PatientCardRenderer : ComponentRenderer<PatientSummaryState, PatientCardCo ### 2. Register renderers -Group a feature's registrations into an extension on `ViewRegistry`. See [`PatientListRegistrations.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt): +Group a feature's registrations into an extension on `ViewRegistry`. See [`PatientListRegistrations.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt): ```kotlin fun ViewRegistry.registerPatientList() { @@ -224,7 +278,7 @@ fun ViewRegistry.registerPatientList() { } ``` -Assemble all feature registrations in one builder, as in [`AppViewRegistry.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt): +Assemble all feature registrations in one builder, as in [`AppViewRegistry.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt): ```kotlin fun buildAppViewRegistry(): ViewRegistry = ViewRegistry().apply { @@ -237,7 +291,7 @@ A registry lookup is keyed by both view-type and state type, and throws `NoSuchE ### 3. Install the registry -Provide the registry at the composition root so every screen can resolve renderers. See [`App.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt): +Provide the registry at the composition root so every screen can resolve renderers. See [`App.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt): ```kotlin @Composable @@ -253,7 +307,7 @@ fun App() { ### 4. Render -`ListScaffold` renders a list; `component(...)` and `layout(...)` name the view-types to resolve. An empty list short-circuits to `emptyState` without invoking the layout renderer, and omitting `layout(...)` falls back to `VerticalListRenderer`. See [`PatientListScreen.kt`](./ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt): +`ListScaffold` renders a list; `component(...)` and `layout(...)` name the view-types to resolve. An empty list short-circuits to `emptyState` without invoking the layout renderer, and omitting `layout(...)` falls back to `VerticalListRenderer`. See [`PatientListScreen.kt`](./reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt): ```kotlin ListScaffold<PatientSummaryState>( @@ -286,17 +340,21 @@ Adding a field is a configuration change: add a column to the `ViewDefinition`, Run all multiplatform tests: ```shell -./gradlew :ohs-player-library:allTests :ohs-player-reference-app:allTests +./gradlew :reference-app:allTests ``` Run JVM tests only: ```shell -./gradlew :ohs-player-library:jvmTest :ohs-player-reference-app:jvmTest +./gradlew :reference-app:jvmTest ``` ## Deployment +### Continuous integration + +Every pull request and push to `main` is validated by the [`ci.yml`](./.github/workflows/ci.yml) workflow: formatting (spotless), JVM tests, Android lint, JS/Wasm compilation, and iOS compile-and-link, each as a separate job. + ### Release pipeline Releases are produced by the [`release.yml`](./.github/workflows/release.yml) GitHub Actions workflow, triggered by a semantic version tag (`vX.Y.Z` or `vX.Y.Z-suffix`). The workflow builds and signs every platform, then publishes a GitHub Release with checksummed artifacts: @@ -312,11 +370,11 @@ A `workflow_dispatch` run performs a dry run: it builds, signs, and uploads arti Build a native installer or distributable locally: ```shell -./gradlew :ohs-player-reference-app:packageDmg # macOS .dmg -./gradlew :ohs-player-reference-app:packageMsi # Windows .msi -./gradlew :ohs-player-reference-app:packageDeb # Linux .deb -./gradlew :ohs-player-reference-app:createDistributable # portable app image -./gradlew :ohs-player-reference-app:wasmJsBrowserDistribution # web bundle +./gradlew :reference-app:packageDmg # macOS .dmg +./gradlew :reference-app:packageMsi # Windows .msi +./gradlew :reference-app:packageDeb # Linux .deb +./gradlew :reference-app:createDistributable # portable app image +./gradlew :reference-app:wasmJsBrowserDistribution # web bundle ``` ### Android release signing @@ -326,7 +384,7 @@ Release builds read signing inputs from environment variables first, then from a ```shell cp keystore.properties.template keystore.properties # Edit keystore.properties with your keystore path, alias, and passwords, then: -./gradlew :ohs-player-reference-app:bundleRelease +./gradlew :reference-app:bundleRelease ``` `keystore.properties` is gitignored and must never be committed. The environment variables `ANDROID_KEYSTORE_PATH`, `ANDROID_KEY_ALIAS`, `ANDROID_KEY_PASSWORD`, and `ANDROID_STORE_PASSWORD` take precedence over the file when both are set. If neither is configured, release builds are emitted unsigned. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9be9eb5..abc5e25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,7 @@ navigation-compose = "2.9.2" material3 = "1.10.0-alpha05" kotlinxSerializationJson = "1.11.0" spotless = "8.6.0" +ohsPlayerReferenceLibrary = "1.0.0-alpha01" ohsFhirModel = "1.0.0-beta05" ohsFhirPath = "1.0.0-beta03" kotlinxDatetime = "0.8.0" @@ -59,6 +60,7 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigation-compose" } +ohs-player-reference-library = { module = "dev.ohs.player:reference-library", version.ref = "ohsPlayerReferenceLibrary" } ohs-fhir-model = { module = "dev.ohs.fhir:fhir-model", version.ref = "ohsFhirModel" } ohs-fhir-path = { module = "dev.ohs.fhir:fhir-path", version.ref = "ohsFhirPath" } ionspin-bignum = { module = "com.ionspin.kotlin:bignum", version.ref = "ionspin-bignum" } diff --git a/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/generator/ViewTypeGenerator.kt b/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/generator/ViewTypeGenerator.kt index 2559a66..5f59508 100644 --- a/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/generator/ViewTypeGenerator.kt +++ b/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/generator/ViewTypeGenerator.kt @@ -34,7 +34,7 @@ class ViewTypeGenerator( private val outputDir: File, ) { - private val viewTypeClass = ClassName("dev.ohs.player.library.registry", "ViewType") + private val viewTypeClass = ClassName("dev.ohs.player.reference.library.registry", "ViewType") fun generate(codeSystem: CodeSystem) { FileSpec.builder("$basePackage.$subPackage", codeSystem.name) diff --git a/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/model/ViewDefinition.kt b/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/model/ViewDefinition.kt index c0db897..ecc5864 100644 --- a/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/model/ViewDefinition.kt +++ b/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/model/ViewDefinition.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable * Codegen view of a ViewDefinition — only the fields code generation needs to derive the typed * state class: the resource and its column tree. Runtime concerns the generator never reads * (`constant`, `where`, `status`, …) are intentionally omitted; the full SQL-on-FHIR model lives in - * the library (`dev.ohs.player.library.config.ViewDefinition`), where extraction uses them. Unknown + * the library (`dev.ohs.player.reference.library.config.ViewDefinition`), where extraction uses them. Unknown * JSON keys are ignored on decode. */ @Serializable diff --git a/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/util/GeneratorUtil.kt b/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/util/GeneratorUtil.kt index 71166d1..d5b7755 100644 --- a/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/util/GeneratorUtil.kt +++ b/ig-codegen/src/main/kotlin/dev/ohs/player/codegen/util/GeneratorUtil.kt @@ -29,7 +29,7 @@ private val fhirDateClass = ClassName("dev.ohs.fhir.model.r4", "FhirDate") private val fhirDateTimeClass = ClassName("dev.ohs.fhir.model.r4", "FhirDateTime") private val bigDecimalClass = ClassName("com.ionspin.kotlin.bignum.decimal", "BigDecimal") -private const val SERIALIZER_PKG = "dev.ohs.player.library.config" +private const val SERIALIZER_PKG = "dev.ohs.player.reference.library.config" private val fhirDateSerializer = ClassName(SERIALIZER_PKG, "FhirDateSerializer") private val fhirDateTimeSerializer = ClassName(SERIALIZER_PKG, "FhirDateTimeSerializer") private val fhirDecimalSerializer = ClassName(SERIALIZER_PKG, "FhirDecimalSerializer") diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 1548ecd..e444fe2 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -152,7 +152,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :ohs-player-reference-app:embedAndSignAppleFrameworkForXcode\n"; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :reference-app:embedAndSignAppleFrameworkForXcode\n"; }; /* End PBXShellScriptBuildPhase section */ diff --git a/ohs-player-library/build.gradle.kts b/ohs-player-library/build.gradle.kts deleted file mode 100644 index 08ec67b..0000000 --- a/ohs-player-library/build.gradle.kts +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.androidKotlinMultiplatformLibrary) - alias(libs.plugins.kotlinSerialization) - alias(libs.plugins.composeMultiplatform) - alias(libs.plugins.composeCompiler) - id("spotless-conventions") -} - -kotlin { - // TODO(AGP-9.0): rename `androidLibrary { }` to `android { }` once AGP is upgraded. - androidLibrary { - namespace = "dev.ohs.player.library" - compileSdk = libs.versions.android.compileSdk.get().toInt() - minSdk = libs.versions.android.minSdk.get().toInt() - - compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } - - withHostTest {} - } - - iosArm64() - iosSimulatorArm64() - - jvm() - - js { browser() } - - @OptIn(ExperimentalWasmDsl::class) wasmJs { browser() } - - sourceSets { - commonMain.dependencies { - implementation(libs.kotlinx.serialization.json) - implementation(libs.ohs.fhir.model) - implementation(libs.ohs.fhir.path) - implementation(libs.ionspin.bignum) - implementation(libs.kermit) - implementation(libs.kotlinx.datetime) - implementation(libs.compose.runtime) - implementation(libs.compose.ui) - implementation(libs.compose.foundation) - implementation(libs.compose.material3) - } - commonTest.dependencies { - implementation(libs.kotlin.test) - implementation(libs.compose.uiTest) - implementation(libs.kotlinx.coroutines.test) - } - jvmTest.dependencies { implementation(compose.desktop.currentOs) } - } -} - -// Targets to be skipped on CI until their respective test setups are sorted out. -// Only disabled when CI=true (set by GitHub Actions) so the CI build stays green; -// Local development still runs these so contributors can reproduce and -// fix the underlying failures. -// -// * Kotlin/JS IR backend crashes lowering the generated sealed-interface -// dispatch tables in dev.ohs.fhir:fhir-path (StackOverflow in -// KotlinLikeDumper.visitWhen -> visitElseBranch). Main JS compile is -// fine; only the JS *test* executable lowering trips because the test -// source set actually exercises those types. -// -// * Android host (JVM) tests blow up with NoClassDefFoundError on -// android/app/Activity. -// -// TODO: adopt Compose Multiplatform UI tests (runComposeUiTest) for -// commonTest so these run without a host Android framework. -// -// JVM, iOS, and Wasm tests still run and cover the same logic. -// -// TODO: if a future Kotlin/AGP release renames these tasks, the matching -// predicate silently no-ops and the underlying errors return. -val isCi = providers.environmentVariable("CI").map(String::toBoolean).getOrElse(false) - -if (isCi) { - tasks - .matching { - it.name in - setOf( - "compileTestDevelopmentExecutableKotlinJs", - "compileTestProductionExecutableKotlinJs", - "jsBrowserTest", - "wasmJsBrowserTest", - "testAndroidHostTest", - ) - } - .configureEach { enabled = false } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ConfigSource.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ConfigSource.kt deleted file mode 100644 index 2eecf5e..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ConfigSource.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.config - -/** - * Supplies the raw config Binary documents the [ConfigStore] indexes. This is the only seam an - * application must implement: in production it fetches from a backend; the reference app reads - * bundled resources. Each returned string is the JSON body of one config Binary. - */ -fun interface ConfigSource { - suspend fun readAll(): List<String> -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ConfigStore.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ConfigStore.kt deleted file mode 100644 index 32e5524..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ConfigStore.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.config - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.contentOrNull -import kotlinx.serialization.json.jsonObject - -/** - * Plain JSON used to parse authored config resources and decode extracted state rows. FHIR scalar - * types resolve via the `@file:UseSerializers` annotations on the `@Serializable` classes, so no - * contextual `SerializersModule` is needed. `ignoreUnknownKeys` skips the config metadata fields - * (url, title, publisher, …) that the runtime models intentionally omit. - */ -internal val fhirJson: Json = Json { - ignoreUnknownKeys = true - isLenient = true -} - -/** - * A generic store of runtime configuration resources loaded from a [ConfigSource]. - * - * Each resource is any FHIR-style JSON with a `resourceType` — ViewJoinMap, ViewDefinition, or - * anything else an app treats as config (Composition, Questionnaire, …). Resources are indexed by - * `resourceType` + key (the resource's `name`, else `id`, else `url`) and fetched with [get]. The - * store doesn't care where the bytes came from — bundled resources, a server download, etc. — - * that's the [ConfigSource]'s job. Loading is lazy and runs once. - */ -class ConfigStore(private val source: ConfigSource) { - - private val resources = mutableMapOf<String, JsonObject>() - private val decoded = mutableMapOf<String, Any?>() - private val mutex = Mutex() - private var loaded = false - - /** - * The resource of [resourceType] identified by [key] (its `name`/`id`/`url`), decoded via - * [deserializer], or `null` if no such resource was provided. Configs are immutable once loaded, - * so decoded results are memoized and reused across calls. - */ - suspend fun <T> get( - resourceType: String, - key: String, - deserializer: DeserializationStrategy<T>, - ): T? = - mutex.withLock { - ensureLoaded() - val resourceKey = indexKey(resourceType, key) - val cacheKey = "$resourceKey|${deserializer.descriptor.serialName}" - if (!decoded.containsKey(cacheKey)) { - decoded[cacheKey] = - resources[resourceKey]?.let { fhirJson.decodeFromJsonElement(deserializer, it) } - } - @Suppress("UNCHECKED_CAST") - decoded[cacheKey] as T? - } - - private suspend fun ensureLoaded() { - if (loaded) return - source.readAll().forEach { index(fhirJson.parseToJsonElement(it).jsonObject) } - loaded = true - } - - private fun index(resource: JsonObject) { - val type = resource.string("resourceType") ?: return - val key = resource.string("name") ?: resource.string("id") ?: resource.string("url") ?: return - resources[indexKey(type, key)] = resource - } - - private fun indexKey(resourceType: String, key: String) = "$resourceType/$key" - - private fun JsonObject.string(field: String): String? = - (this[field] as? JsonPrimitive)?.contentOrNull -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/FhirScalarSerializers.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/FhirScalarSerializers.kt deleted file mode 100644 index 5de8a20..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/FhirScalarSerializers.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.config - -import com.ionspin.kotlin.bignum.decimal.BigDecimal -import dev.ohs.fhir.model.r4.FhirDate -import dev.ohs.fhir.model.r4.FhirDateTime -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.JsonDecoder -import kotlinx.serialization.json.jsonPrimitive - -/** - * Serializers for the FHIR R4 scalar types that ViewDefinition/ViewConfig values and the generated - * state classes carry. They read each type's canonical representation: a `BigDecimal` from a JSON - * number or string (precision preserved), and `FhirDate`/`FhirDateTime` from their ISO strings. - * - * kotlin-fhir's own model serializers cover these types inside resource models, but they are - * `internal`, so our own `@Serializable` classes still need these. They are attached directly via - * `@file:UseSerializers(...)` on the classes that reference them — no contextual - * `SerializersModule` or specially-configured `Json` is required; a plain `Json` resolves them. - */ -object FhirDecimalSerializer : KSerializer<BigDecimal> { - override val descriptor = PrimitiveSerialDescriptor("decimal", PrimitiveKind.STRING) - - // Read the raw JSON token (number or string) so no precision is lost via Double. - override fun deserialize(decoder: Decoder): BigDecimal = - BigDecimal.parseString((decoder as JsonDecoder).decodeJsonElement().jsonPrimitive.content) - - override fun serialize(encoder: Encoder, value: BigDecimal) = - encoder.encodeString(value.toStringExpanded()) -} - -object FhirDateSerializer : KSerializer<FhirDate> { - override val descriptor = PrimitiveSerialDescriptor("date", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): FhirDate { - val raw = decoder.decodeString() - return FhirDate.fromString(raw) ?: throw SerializationException("Invalid FhirDate: '$raw'") - } - - override fun serialize(encoder: Encoder, value: FhirDate) = encoder.encodeString(value.toString()) -} - -object FhirDateTimeSerializer : KSerializer<FhirDateTime> { - override val descriptor = PrimitiveSerialDescriptor("dateTime", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): FhirDateTime { - val raw = decoder.decodeString() - return FhirDateTime.fromString(raw) - ?: throw SerializationException("Invalid FhirDateTime: '$raw'") - } - - override fun serialize(encoder: Encoder, value: FhirDateTime) = - encoder.encodeString(value.toString()) -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ViewDefinition.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ViewDefinition.kt deleted file mode 100644 index 6433110..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ViewDefinition.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:UseSerializers( - FhirDateSerializer::class, - FhirDateTimeSerializer::class, - FhirDecimalSerializer::class, -) - -package dev.ohs.player.library.config - -import com.ionspin.kotlin.bignum.decimal.BigDecimal -import dev.ohs.fhir.model.r4.FhirDate -import dev.ohs.fhir.model.r4.FhirDateTime -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers - -/** - * Runtime model of a SQL-on-FHIR ViewDefinition — a projection of a FHIR resource into flat rows. - * - * Mirrors the - * [SQL-on-FHIR ViewDefinition](https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition.html): - * `constant`s (FHIRPath variables), `where` filters, and a tree of `select` nodes. Metadata fields - * (url, title, publisher, …) and `column.tag` are not modelled — they don't affect extraction. - */ -@Serializable -data class ViewDefinition( - val resourceType: String = "", - val name: String = "", - val resource: String = "", - val status: String? = null, - val constant: List<Constant> = emptyList(), - val where: List<WhereClause> = emptyList(), - val select: List<SelectBlock> = emptyList(), -) { - /** - * A named constant usable in FHIRPath expressions via `%name`. Mirrors the full SQL-on-FHIR - * `value[x]` choice; [value] returns whichever variant is set. - */ - @Serializable - data class Constant( - val name: String, - val valueBase64Binary: String? = null, - val valueBoolean: Boolean? = null, - val valueCanonical: String? = null, - val valueCode: String? = null, - val valueDate: FhirDate? = null, - val valueDateTime: FhirDateTime? = null, - val valueDecimal: BigDecimal? = null, - val valueId: String? = null, - val valueInstant: FhirDateTime? = null, - val valueInteger: Int? = null, - val valueInteger64: Long? = null, - val valueOid: String? = null, - val valuePositiveInt: Int? = null, - val valueString: String? = null, - val valueTime: String? = null, - val valueUnsignedInt: Int? = null, - val valueUri: String? = null, - val valueUrl: String? = null, - val valueUuid: String? = null, - ) { - val value: Any? - get() = - valueBoolean - ?: valueInteger - ?: valueInteger64 - ?: valuePositiveInt - ?: valueUnsignedInt - ?: valueDecimal - ?: valueString - ?: valueCode - ?: valueDate - ?: valueDateTime - ?: valueInstant - ?: valueTime - ?: valueUri - ?: valueUrl - ?: valueCanonical - ?: valueId - ?: valueOid - ?: valueUuid - ?: valueBase64Binary - } - - /** A FHIRPath boolean filter on the resource; non-`true` rows are dropped. Clauses are AND-ed. */ - @Serializable data class WhereClause(val path: String) - - /** - * One node of the select tree. Per SQL-on-FHIR, a node may carry [column]s, nested [select] - * nodes, a [forEach]/[forEachOrNull] that re-roots evaluation at each element of a path, and a - * [unionAll] of further select nodes whose rows are concatenated. Columns + nested selects + - * unionAll combine by cross-join. - */ - @Serializable - data class SelectBlock( - val column: List<Column> = emptyList(), - val select: List<SelectBlock> = emptyList(), - val forEach: String? = null, - val forEachOrNull: String? = null, - val unionAll: List<SelectBlock> = emptyList(), - ) - - /** One output column. [collection] = true yields a `List<T>` field instead of `T?`. */ - @Serializable - data class Column( - val name: String, - val path: String, - val type: String? = null, - val collection: Boolean = false, - val description: String? = null, - ) - - /** - * Every column name the view can produce, walking the whole select tree (for the flat row - * schema). - */ - fun allColumns(): List<Column> = select.flatMap { it.collectColumns() } - - private fun SelectBlock.collectColumns(): List<Column> = buildList { - addAll(column) - select.forEach { addAll(it.collectColumns()) } - // unionAll branches share one schema, so the first branch defines the columns. - unionAll.firstOrNull()?.let { addAll(it.collectColumns()) } - } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ViewJoinMap.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ViewJoinMap.kt deleted file mode 100644 index 764e798..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/config/ViewJoinMap.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.config - -import kotlinx.serialization.Serializable - -/** - * Runtime model for a ViewJoinMap Binary — stitches one or more [ViewDefinition] projections, each - * sourced from a different scope of a `SearchResult`, into one flat state row per pivot resource. - * - * Downloaded and interpreted at runtime by - * [dev.ohs.player.library.extractor.GenericStateExtractor]. - */ -@Serializable -data class ViewJoinMap( - val resourceType: String = "", - val name: String = "", - val from: String = "", - val resource: String = "", - val view: String = "", - val searchParam: String? = null, - val joins: List<Join> = emptyList(), -) { - @Serializable - data class Join( - val view: String, - val from: String, - val resource: String, - val searchParam: String? = null, - val matchKey: String? = null, - ) -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/FhirPathEngineProvider.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/FhirPathEngineProvider.kt deleted file mode 100644 index d23f07b..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/FhirPathEngineProvider.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.extractor - -import dev.ohs.fhir.fhirpath.FhirPathEngine - -/** - * Shared, lazily-initialized R4 FHIRPath engine. [GenericStateExtractor] uses it by default. - * - * The engine holds mutable evaluation state and is therefore not safe for concurrent evaluation: - * callers sharing this singleton must serialize their use of it (e.g. a single-threaded - * dispatcher). - */ -object FhirPathEngineProvider { - val r4: FhirPathEngine by lazy { FhirPathEngine.forR4() } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/FhirPathEval.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/FhirPathEval.kt deleted file mode 100644 index 5356505..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/FhirPathEval.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.extractor - -import co.touchlab.kermit.Logger -import com.ionspin.kotlin.bignum.decimal.BigDecimal -import dev.ohs.fhir.fhirpath.FhirPathEngine -import dev.ohs.fhir.fhirpath.types.FhirPathDate -import dev.ohs.fhir.fhirpath.types.FhirPathDateTime -import dev.ohs.fhir.fhirpath.types.FhirPathTime -import dev.ohs.fhir.model.r4.FhirDate -import dev.ohs.fhir.model.r4.FhirDateTime - -/** - * Lightweight wrapper around a single FHIRPath evaluation result. - * - * The engine returns scalars as Kotlin types (`Boolean`, `String`, `BigDecimal`, …) and date/time - * values as its own `FhirPathDate`/`FhirPathDateTime`/`FhirPathTime` types. These accessors - * normalise both so callers read a value without casts. - */ -class EvalResult(val raw: Any?) { - val str: String? - get() = - when (raw) { - null -> null - is FhirPathDate -> raw.toFhirString() - is FhirPathDateTime -> raw.toFhirString() - is FhirPathTime -> raw.toFhirString() - else -> raw.toString() - } - - val bool: Boolean? - get() = raw as? Boolean - - val int: Int? - get() = (raw as? Number)?.toInt() - - val long: Long? - get() = (raw as? Number)?.toLong() - - val decimal: BigDecimal? - get() = raw as? BigDecimal - - val date: FhirDate? - get() = - when (raw) { - is FhirDate -> raw - is FhirPathDate -> FhirDate.fromString(raw.toFhirString()) - else -> null - } - - val dateTime: FhirDateTime? - get() = - when (raw) { - is FhirDateTime -> raw - is FhirPathDateTime -> FhirDateTime.fromString(raw.toFhirString()) - else -> null - } -} - -private fun Int.pad2(): String = toString().padStart(2, '0') - -/** Renders a [FhirPathDate] as a FHIR `date` string (`YYYY`, `YYYY-MM`, or `YYYY-MM-DD`). */ -private fun FhirPathDate.toFhirString(): String = buildString { - append(year.toString().padStart(4, '0')) - month?.let { append('-').append(it.pad2()) } - day?.let { append('-').append(it.pad2()) } -} - -/** Renders a [FhirPathTime] as a FHIR `time` string (`HH:MM:SS`). */ -private fun FhirPathTime.toFhirString(): String = buildString { - append(hour.pad2()) - append(':').append((minute ?: 0).pad2()) - append(':').append((second ?: 0.0).toInt().pad2()) -} - -/** Renders a [FhirPathDateTime] as a FHIR `dateTime` string, preserving its precision. */ -private fun FhirPathDateTime.toFhirString(): String = buildString { - append(year.toString().padStart(4, '0')) - month?.let { append('-').append(it.pad2()) } - day?.let { append('-').append(it.pad2()) } - hour?.let { h -> - append('T').append(h.pad2()) - append(':').append((minute ?: 0).pad2()) - second?.let { append(':').append(it.toInt().pad2()) } - utcOffset?.let { append(it.toString()) } - } -} - -/** - * Evaluates [path] against [focus] and returns an [EvalResult] for the first result. - * - * [variables] are accessible in the expression via `%name` syntax (ViewDefinition `constant` - * entries). Swallows evaluation errors — a failed expression returns [EvalResult] wrapping null. - */ -fun FhirPathEngine.eval( - focus: Any?, - path: String, - variables: Map<String, Any?> = emptyMap(), -): EvalResult = - EvalResult( - runCatching { - evaluateExpression(expression = path, base = focus, variables = variables).firstOrNull() - } - .onFailure { Logger.e(it) { it.message ?: "Error evaluating expression: $path" } } - .getOrNull() - ) - -/** - * Evaluates [path] against [focus] and returns an [EvalResult] for every element in the result - * collection. Use for columns declared with `collection: true` in a ViewDefinition. - * - * [variables] are accessible in the expression via `%name` syntax. Returns an empty list on - * evaluation error. - */ -fun FhirPathEngine.evalList( - focus: Any?, - path: String, - variables: Map<String, Any?> = emptyMap(), -): List<EvalResult> = - runCatching { - evaluateExpression(expression = path, base = focus, variables = variables).map { - EvalResult(it) - } - } - .onFailure { Logger.e(it) { it.message ?: "Error evaluating list for the expression: $path" } } - .getOrElse { emptyList() } diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/GenericStateExtractor.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/GenericStateExtractor.kt deleted file mode 100644 index 55e110f..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/extractor/GenericStateExtractor.kt +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.extractor - -import dev.ohs.fhir.fhirpath.FhirPathEngine -import dev.ohs.fhir.fhirpath.types.FhirPathDate -import dev.ohs.fhir.fhirpath.types.FhirPathDateTime -import dev.ohs.fhir.model.r4.FhirDate -import dev.ohs.fhir.model.r4.FhirDateTime -import dev.ohs.fhir.model.r4.Resource -import dev.ohs.player.library.config.ConfigStore -import dev.ohs.player.library.config.ViewDefinition -import dev.ohs.player.library.config.ViewJoinMap -import dev.ohs.player.library.config.fhirJson -import dev.ohs.player.library.model.SearchResult -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.SerialKind -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.serializer - -/** - * A config-driven extractor used to extract data from FHIR resources to a typed data class. - * - * Given a target state type it derives the config name from that type, resolves the downloaded - * [ViewJoinMap] + its [ViewDefinition]s from the [configStore], interprets them against a - * [SearchResult] — pivots, joins, `where` filters, `forEach`/`forEachOrNull`/`unionAll` expansion - * and constants — into one JSON row per output row, and deserializes each row into the typed state - * using the state's own (compiler-generated) serializer. - * - * Not safe for concurrent use: the underlying [FhirPathEngine] holds mutable evaluation state, so - * callers must serialize calls to [extract] (e.g. confine them to a single-threaded dispatcher). - * [ConfigStore] loading is internally synchronized and may be shared. - */ -class GenericStateExtractor( - private val configStore: ConfigStore, - private val engine: FhirPathEngine = FhirPathEngineProvider.r4, -) { - - /** Extracts every [T] the matching config produces from [result]. */ - suspend inline fun <reified T : Any> extract(result: SearchResult<Resource>): List<T> = - extract(result, serializer<T>()) - - @PublishedApi - internal suspend fun <T : Any> extract( - result: SearchResult<Resource>, - deserializer: KSerializer<T>, - ): List<T> { - // The ViewJoinMap name is the lowercase-first form of the state's simple name minus "State" - // (e.g. PatientAllergyState -> patientAllergy) — the inverse of the codegen naming rule. - val configName = - deserializer.descriptor.serialName - .substringAfterLast('.') - .removeSuffix("State") - .replaceFirstChar { it.lowercase() } - - val joinMap = - configStore.get(VIEW_JOIN_MAP, configName, ViewJoinMap.serializer()) - ?: error("ViewJoinMap '$configName' was not provided to the ConfigStore.") - val pivotView = - configStore.get(VIEW_DEFINITION, joinMap.view, ViewDefinition.serializer()) - ?: error("ViewDefinition '${joinMap.view}' (pivot of '$configName') was not provided.") - val joins = - joinMap.joins.map { join -> - val view = - configStore.get(VIEW_DEFINITION, join.view, ViewDefinition.serializer()) - ?: error("ViewDefinition '${join.view}' (join of '$configName') was not provided.") - join to view - } - - validate(configName, deserializer, pivotView, joinMap, joins.map { it.second }) - - return buildRows(result, pivotView, joinMap, joins).map { - fhirJson.decodeFromJsonElement(deserializer, it) - } - } - - private fun buildRows( - result: SearchResult<Resource>, - pivotView: ViewDefinition, - joinMap: ViewJoinMap, - joins: List<Pair<ViewJoinMap.Join, ViewDefinition>>, - ): List<JsonObject> { - val constants = collectConstants(pivotView, joins.map { it.second }) - val pivots = resourcesInScope(joinMap.from, joinMap.resource, joinMap.searchParam, result) - val joinResolvers = - joins.map { (join, view) -> JoinResolver.of(this, result, join, view, constants) } - - return buildList { - for (pivot in pivots) { - if (pivotView.where.any { eval(pivot, it.path, constants).bool != true }) continue - - for (row in viewRows(pivotView.select, pivot, constants)) { - val full = row.toMutableMap() - joinResolvers.forEach { - full.putAll(it.columnsForRow(this@GenericStateExtractor, full, constants)) - } - add(JsonObject(full)) - } - } - } - } - - /** Cross-joins every top-level select node, each evaluated against [focus]. */ - private fun viewRows( - selects: List<ViewDefinition.SelectBlock>, - focus: Any?, - constants: Map<String, Any?>, - ): List<Map<String, JsonElement>> { - var rows = listOf(emptyMap<String, JsonElement>()) - selects.forEach { node -> rows = crossJoin(rows, selectRows(node, focus, constants)) } - return rows - } - - /** - * Rows produced by one select node, per SQL-on-FHIR: `forEach`/`forEachOrNull` re-root [focus] at - * each element of a path; the node's own columns, its nested selects, and its `unionAll` branches - * combine by cross-join (with `unionAll` branches concatenated first). - */ - private fun selectRows( - node: ViewDefinition.SelectBlock, - focus: Any?, - constants: Map<String, Any?>, - ): List<Map<String, JsonElement>> { - val foci: List<Any?> = - when { - node.forEach != null -> evalList(focus, node.forEach, constants).map { it.raw } - node.forEachOrNull != null -> - evalList(focus, node.forEachOrNull, constants).map { it.raw }.ifEmpty { listOf(null) } - else -> listOf(focus) - } - - return foci.flatMap { element -> - var rows = listOf(columnRow(node.column, element, constants)) - node.select.forEach { child -> rows = crossJoin(rows, selectRows(child, element, constants)) } - if (node.unionAll.isNotEmpty()) { - rows = crossJoin(rows, node.unionAll.flatMap { selectRows(it, element, constants) }) - } - rows - } - } - - /** Evaluates [columns] against [focus] into a single row. */ - private fun columnRow( - columns: List<ViewDefinition.Column>, - focus: Any?, - constants: Map<String, Any?>, - ): Map<String, JsonElement> = columns.associate { it.name to columnValue(focus, it, constants) } - - /** - * Resolves the joined resource(s) for one join, computing column values lazily per output row. - */ - private class JoinResolver - private constructor( - private val view: ViewDefinition, - private val matchKey: String?, - private val staticValues: Map<String, JsonElement>, - private val index: Map<String, Resource>, - ) { - - fun columnsForRow( - extractor: GenericStateExtractor, - base: Map<String, JsonElement>, - constants: Map<String, Any?>, - ): Map<String, JsonElement> { - if (matchKey == null) return staticValues - val joined = (base[matchKey] as? JsonPrimitive)?.asString()?.let { index[it] } - return view.allColumns().associate { col -> - col.name to extractor.columnValue(joined, col, constants) - } - } - - companion object { - fun of( - extractor: GenericStateExtractor, - result: SearchResult<Resource>, - join: ViewJoinMap.Join, - view: ViewDefinition, - constants: Map<String, Any?>, - ): JoinResolver { - val candidates = resourcesInScope(join.from, join.resource, join.searchParam, result) - return if (join.matchKey == null) { - val resource = candidates.firstOrNull() - val values = - view.allColumns().associate { col -> - col.name to extractor.columnValue(resource, col, constants) - } - JoinResolver(view, null, values, emptyMap()) - } else { - val index = buildMap { - candidates.forEach { resource -> - extractor.eval(resource, "id").str?.let { id -> put(id, resource) } - } - } - JoinResolver(view, join.matchKey, emptyMap(), index) - } - } - - private fun JsonPrimitive.asString(): String? = if (this is JsonNull) null else content - } - } - - private fun validate( - configName: String, - deserializer: KSerializer<*>, - pivotView: ViewDefinition, - joinMap: ViewJoinMap, - joinViews: List<ViewDefinition>, - ) { - val descriptor = deserializer.descriptor - val fieldKinds = - (0 until descriptor.elementsCount).associate { - descriptor.getElementName(it) to descriptor.getElementDescriptor(it).kind - } - val producedColumns = pivotView.allColumns() + joinViews.flatMap { it.allColumns() } - - val unexpected = producedColumns.map { it.name }.toSet() - fieldKinds.keys - require(unexpected.isEmpty()) { - "Config '$configName' produces columns $unexpected absent from the generated state ${fieldKinds.keys}. " + - "The provided binaries do not match the generated state." - } - - producedColumns.forEach { col -> - if (col.collection) return@forEach - val required = requiredTypeCategory(fieldKinds[col.name]) ?: return@forEach - require(typeCategory(col.type) == required) { - "Column '${col.name}' in config '$configName' feeds a $required state field but its " + - "ViewDefinition type is '${col.type}'. Declare a matching scalar 'type' so the value is " + - "not decoded as a string." - } - } - - val scalarColumns = producedColumns.filterNot { it.collection }.map { it.name }.toSet() - joinMap.joins.forEach { join -> - val matchKey = join.matchKey ?: return@forEach - require(matchKey in scalarColumns) { - "Join matchKey '$matchKey' in config '$configName' must reference a scalar (non-collection) " + - "column produced by the pivot or an earlier join; produced scalar columns are $scalarColumns." - } - } - } - - /** Evaluates one column against [focus] and returns its JSON cell value (or [JsonNull]). */ - private fun columnValue( - focus: Any?, - col: ViewDefinition.Column, - constants: Map<String, Any?>, - ): JsonElement { - if (focus == null) return if (col.collection) JsonArray(emptyList()) else JsonNull - return if (col.collection) { - JsonArray(evalList(focus, col.path, constants).map { scalarValue(it, col.type) }) - } else { - scalarValue(eval(focus, col.path, constants), col.type) - } - } - - /** Maps a single [EvalResult] to a JSON value using the column's FHIR type. */ - private fun scalarValue(result: EvalResult, type: String?): JsonElement = - when (type?.substringAfterLast('/')) { - "boolean" -> JsonPrimitive(result.bool) - "integer", - "positiveInt", - "unsignedInt" -> JsonPrimitive(result.int) - "integer64" -> JsonPrimitive(result.long) - "decimal" -> JsonPrimitive(result.decimal?.toStringExpanded()) - "date" -> JsonPrimitive(result.date?.toString()) - "dateTime", - "instant" -> JsonPrimitive(result.dateTime?.toString()) - else -> JsonPrimitive(result.str) - } - - private fun eval( - focus: Any?, - path: String, - constants: Map<String, Any?> = emptyMap(), - ): EvalResult = engine.eval(focus, path, constants) - - private fun evalList( - focus: Any?, - path: String, - constants: Map<String, Any?> = emptyMap(), - ): List<EvalResult> = engine.evalList(focus, path, constants) - - private companion object { - - const val VIEW_JOIN_MAP = "http://ohs.dev/StructureDefinition/ViewJoinMap" - const val VIEW_DEFINITION = "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition" - - /** - * The scalar category a non-string state field requires, or `null` if a string value is fine. - */ - fun requiredTypeCategory(kind: SerialKind?): String? = - when (kind) { - PrimitiveKind.BOOLEAN -> "boolean" - PrimitiveKind.BYTE, - PrimitiveKind.SHORT, - PrimitiveKind.INT -> "integer" - PrimitiveKind.LONG -> "long" - else -> null - } - - /** The scalar category a ViewDefinition column [type] resolves to, mirroring [scalarValue]. */ - fun typeCategory(type: String?): String? = - when (type?.substringAfterLast('/')) { - "boolean" -> "boolean" - "integer", - "positiveInt", - "unsignedInt" -> "integer" - "integer64" -> "long" - else -> null - } - - /** - * Cartesian product of two row sets (empty if either side is empty — i.e. inner-join - * semantics). - */ - fun crossJoin( - left: List<Map<String, JsonElement>>, - right: List<Map<String, JsonElement>>, - ): List<Map<String, JsonElement>> = left.flatMap { l -> right.map { r -> l + r } } - - fun resourcesInScope( - from: String, - resource: String, - searchParam: String?, - result: SearchResult<Resource>, - ): List<Resource> = - when (from) { - "root" -> listOf(result.resource) - "included" -> - if (searchParam != null) result.included?.get(searchParam).orEmpty() - else result.included?.values?.flatten().orEmpty() - "revIncluded" -> - if (searchParam != null) result.revIncluded?.get(resource to searchParam).orEmpty() - else - result.revIncluded - ?.entries - ?.filter { it.key.first == resource } - ?.flatMap { it.value } - .orEmpty() - else -> emptyList() - } - - fun collectConstants( - pivotView: ViewDefinition, - joinViews: List<ViewDefinition>, - ): Map<String, Any?> = buildMap { - pivotView.constant.forEach { put(it.name, fhirPathValue(it.value)) } - joinViews.forEach { view -> view.constant.forEach { put(it.name, fhirPathValue(it.value)) } } - } - - /** - * Converts a constant's kotlin-fhir model value to the FHIRPath type the engine expects for a - * `%constant` variable. Date/time models must be the engine's own types to compare against - * extracted fields; numbers, booleans, and strings pass through unchanged. - */ - fun fhirPathValue(value: Any?): Any? = - when (value) { - is FhirDate -> FhirPathDate.fromFhirR4Date(value) - is FhirDateTime -> FhirPathDateTime.fromFhirR4DateTime(value) - else -> value - } - } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/GridListRenderer.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/GridListRenderer.kt deleted file mode 100644 index ff1ed21..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/GridListRenderer.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.layout - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.RenderOptions - -/** - * Renders items in a vertically-scrolling `LazyVerticalGrid`. - * - * View-type constants are typically declared once in an `object`; see - * [dev.ohs.player.library.registry.ViewType]. - * - * ``` - * object AppViewTypes { - * val Grid = ViewType("Grid") - * } - * - * registry.registerLayout<PatientView>( - * AppViewTypes.Grid, - * GridListRenderer( - * cells = GridCells.Fixed(2), - * contentPadding = PaddingValues(16.dp), - * itemSpacing = 12.dp, - * ), - * ) - * ``` - * - * @param cells column specification; defaults to two equal columns. - * @param contentPadding padding around the grid content. - * @param itemSpacing gap between items in both axes. - */ -class GridListRenderer<T>( - private val cells: GridCells = GridCells.Fixed(2), - private val contentPadding: PaddingValues = PaddingValues(0.dp), - private val itemSpacing: Dp = 0.dp, -) : LayoutRenderer<T> { - companion object { - /** - * ViewType constant for registering a [GridListRenderer] in the - * [dev.ohs.player.library.registry.ViewRegistry]. - */ - val VIEW_TYPE = ViewType("Grid") - } - - /** Lays out [items] in a `LazyVerticalGrid`, invoking [component] per item with [key]. */ - @Composable - override fun Render( - items: List<T>, - component: ConfiguredRenderer<T>, - key: ((T) -> Any)?, - onItemClick: (T) -> Unit, - modifier: Modifier, - ) { - LazyVerticalGrid( - columns = cells, - modifier = modifier, - contentPadding = contentPadding, - verticalArrangement = Arrangement.spacedBy(itemSpacing), - horizontalArrangement = Arrangement.spacedBy(itemSpacing), - ) { - items(items = items, key = key) { item -> - component.Render(item, RenderOptions(onClick = { onItemClick(item) })) - } - } - } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/HorizontalListRenderer.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/HorizontalListRenderer.kt deleted file mode 100644 index 8c45eba..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/HorizontalListRenderer.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.layout - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.RenderOptions - -/** - * Renders items horizontally as a `LazyRow`. - * - * View-type constants are typically declared once in an `object`; see - * [dev.ohs.player.library.registry.ViewType]. - * - * ``` - * object AppViewTypes { - * val HorizontalList = ViewType("HorizontalList") - * } - * - * registry.registerLayout<PatientView>( - * AppViewTypes.HorizontalList, - * HorizontalListRenderer( - * contentPadding = PaddingValues(horizontal = 16.dp), - * itemSpacing = 12.dp, - * ), - * ) - * ``` - * - * @param contentPadding padding around the list content. - * @param itemSpacing horizontal gap between items. - */ -class HorizontalListRenderer<T>( - private val contentPadding: PaddingValues = PaddingValues(0.dp), - private val itemSpacing: Dp = 0.dp, -) : LayoutRenderer<T> { - companion object { - /** - * ViewType constant for registering a [HorizontalListRenderer] in the - * [dev.ohs.player.library.registry.ViewRegistry]. - */ - val VIEW_TYPE = ViewType("HorizontalList") - } - - /** Lays out [items] in a `LazyRow`, invoking [component] per item with [key]. */ - @Composable - override fun Render( - items: List<T>, - component: ConfiguredRenderer<T>, - key: ((T) -> Any)?, - onItemClick: (T) -> Unit, - modifier: Modifier, - ) { - LazyRow( - modifier = modifier, - contentPadding = contentPadding, - horizontalArrangement = Arrangement.spacedBy(itemSpacing), - ) { - items(items = items, key = key) { item -> - component.Render(item, RenderOptions(onClick = { onItemClick(item) })) - } - } - } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/VerticalListRenderer.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/VerticalListRenderer.kt deleted file mode 100644 index b974938..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/layout/VerticalListRenderer.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.layout - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.RenderOptions - -/** - * Renders items vertically as a `LazyColumn`. The default layout for - * [dev.ohs.player.library.scaffold.ListScaffold] when none is specified. - * - * View-type constants are typically declared once in an `object`; see - * [dev.ohs.player.library.registry.ViewType]. - * - * ``` - * object AppViewTypes { - * val VerticalList = ViewType("VerticalList") - * } - * - * registry.registerLayout<PatientView>( - * AppViewTypes.VerticalList, - * VerticalListRenderer( - * contentPadding = PaddingValues(16.dp), - * itemSpacing = 12.dp, - * ), - * ) - * ``` - * - * @param contentPadding padding around the list content (not the items themselves). - * @param itemSpacing vertical gap between items. - */ -class VerticalListRenderer<T>( - private val contentPadding: PaddingValues = PaddingValues(0.dp), - private val itemSpacing: Dp = 0.dp, -) : LayoutRenderer<T> { - companion object { - /** - * ViewType constant for registering a [VerticalListRenderer] in the - * [dev.ohs.player.library.registry.ViewRegistry]. - */ - val VIEW_TYPE = ViewType("VerticalList") - } - - /** Lays out [items] in a `LazyColumn`, invoking [component] per item with [key]. */ - @Composable - override fun Render( - items: List<T>, - component: ConfiguredRenderer<T>, - key: ((T) -> Any)?, - onItemClick: (T) -> Unit, - modifier: Modifier, - ) { - LazyColumn( - modifier = modifier, - contentPadding = contentPadding, - verticalArrangement = Arrangement.spacedBy(itemSpacing), - ) { - items(items = items, key = key) { item -> - component.Render(item, RenderOptions(onClick = { onItemClick(item) })) - } - } - } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/model/SearchResult.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/model/SearchResult.kt deleted file mode 100644 index c2b7925..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/model/SearchResult.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.model - -import dev.ohs.fhir.model.r4.Resource - -// TODO delete this class as soon as FHIR Engine is integrated -/** - * Stands in for the engine's SearchResult until the KMP migration of kotlin-fhir-engine lands. - * - * Mirrors the engine's search result shape: a primary resource plus side-loaded resources returned - * by forward includes and reverse includes. - * - * Replace [ResourceType] with the engine's actual type and [Resource] with the engine's base - * interface once available. - * - * @param R the FHIR resource type of the primary (pivot) resource. - */ -data class SearchResult<R : Resource>( - /** The primary resource returned by the search query. */ - val resource: R, - /** - * Resources returned via forward include (`_include`), keyed by search parameter name. For - * example, `_include=AllergyIntolerance:patient` returns `"patient" → [Patient]`. - */ - val included: Map<SearchParamName, List<Resource>>? = null, - /** - * Resources returned via reverse include (`_revinclude`), keyed by `Pair(resourceType, - * searchParamName)`. For example, `_revinclude=AllergyIntolerance:patient` with resource type - * `"AllergyIntolerance"` returns `Pair("AllergyIntolerance", "patient") → - * [AllergyIntolerance, ...]`. - */ - val revIncluded: Map<Pair<ResourceType, SearchParamName>, List<Resource>>? = null, -) - -/** The FHIR resource type name used as a key in [SearchResult.revIncluded]. */ -typealias ResourceType = String - -/** - * The search parameter name used as a key in [SearchResult.included] and - * [SearchResult.revIncluded]. - */ -typealias SearchParamName = String diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/LocalViewRegistry.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/LocalViewRegistry.kt deleted file mode 100644 index b82eff4..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/LocalViewRegistry.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.registry - -import androidx.compose.runtime.staticCompositionLocalOf - -/** - * The active [ViewRegistry] for the composition. Defaults to an empty registry so direct-pass DSL - * paths (e.g. `component(MyRenderer(), MyConfig())`) work without a provider. Provide an app-level - * registry via: - * ``` - * CompositionLocalProvider(LocalViewRegistry provides registry) { ... } - * ``` - */ -val LocalViewRegistry = staticCompositionLocalOf { ViewRegistry() } diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewRegistry.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewRegistry.kt deleted file mode 100644 index d35f140..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewRegistry.kt +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.registry - -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.withConfig - -/** - * Mutable registry mapping `(ViewType, KClass<T>)` keys to renderers. - * - * Populated at app start (typically inside a `buildAppViewRegistry()` helper) and then installed - * into the composition via [LocalViewRegistry] so scaffolds can resolve renderers by name without - * the screen importing them directly. Lookups throw [NoSuchElementException] on miss with a - * diagnostic message naming the absent key. - * - * View-type constants are typically declared once in an `object`; see [ViewType]. - * - * ``` - * object AppViewTypes { - * val Card = ViewType("Card") - * val VerticalList = ViewType("VerticalList") - * } - * - * val registry = ViewRegistry().apply { - * registerComponent<PatientView, PatientCardConfig>( - * AppViewTypes.Card, PatientCardRenderer(), PatientCardConfig(), - * ) - * registerLayout<PatientView>( - * AppViewTypes.VerticalList, - * VerticalListRenderer(), - * ) - * } - * CompositionLocalProvider(LocalViewRegistry provides registry) { App() } - * ``` - */ -class ViewRegistry { - /** - * Storage entry pairing the original [ComponentRenderer] with its config and the pre-built - * [ConfiguredRenderer] wrapper. [configured] is built once at construction so lookups are O(1) - * and the same wrapper instance is returned on repeated calls. - * - * @param renderer the original renderer, retained for identity / introspection. - * @param config the config baked into [configured]. - */ - @PublishedApi - internal class ComponentEntry<T : Any, C : Any>( - val renderer: ComponentRenderer<T, C>, - val config: C, - ) { - /** Bound renderer, ready to invoke without carrying the config type. */ - val configured: ConfiguredRenderer<T> = renderer.withConfig(config) - } - - /** Component entries keyed by [ViewTypeKey]. */ - @PublishedApi internal val components = mutableMapOf<ViewTypeKey<*>, ComponentEntry<*, *>>() - - /** Layout renderers keyed by [ViewTypeKey]. */ - @PublishedApi internal val layouts = mutableMapOf<ViewTypeKey<*>, LayoutRenderer<*>>() - - /** Stores a component entry under [key]. Called by [registerComponent]. */ - @PublishedApi - internal fun <T : Any, C : Any> putComponent( - key: ViewTypeKey<T>, - renderer: ComponentRenderer<T, C>, - config: C, - ) { - components[key] = ComponentEntry(renderer, config) - } - - /** Stores a layout renderer under [key]. Called by [registerLayout]. */ - @PublishedApi - internal fun <T : Any> putLayout(key: ViewTypeKey<T>, renderer: LayoutRenderer<T>) { - layouts[key] = renderer - } - - /** - * Returns the cached [ConfiguredRenderer] registered under [key]. - * - * @throws NoSuchElementException if no component is registered for [key]. - */ - @Suppress("UNCHECKED_CAST") - @PublishedApi - internal fun <T : Any> getComponent(key: ViewTypeKey<T>): ConfiguredRenderer<T> = - (components[key] ?: missing("component", key)).configured as ConfiguredRenderer<T> - - /** - * Returns the original [ComponentRenderer] registered under [key], before [withConfig] was - * applied. Intended for identity-based tests and debugging. - * - * @throws NoSuchElementException if no component is registered for [key]. - */ - @Suppress("UNCHECKED_CAST") - @PublishedApi - internal fun <T : Any> getComponentSource(key: ViewTypeKey<T>): ComponentRenderer<T, *> = - (components[key] ?: missing("component", key)).renderer as ComponentRenderer<T, *> - - /** - * Returns the [LayoutRenderer] registered under [key]. - * - * @throws NoSuchElementException if no layout is registered for [key]. - */ - @Suppress("UNCHECKED_CAST") - @PublishedApi - internal fun <T : Any> getLayout(key: ViewTypeKey<T>): LayoutRenderer<T> { - val renderer = layouts[key] ?: missing("layout", key) - return renderer as LayoutRenderer<T> - } - - /** Throws a [NoSuchElementException] naming the missing renderer kind and key. */ - private fun missing(kind: String, key: ViewTypeKey<*>): Nothing = - throw NoSuchElementException( - "No $kind renderer registered for " + "(${key.dataType.simpleName}, ${key.viewType.value})." - ) -} - -/** - * Registers [renderer] under [viewType] for data type [T], with [config] baked in. - * - * The same renderer instance can be registered under multiple view-types with different configs to - * vary visual behavior per role. View-type constants are typically declared once in an `object`; - * see [ViewType]. - * - * ``` - * object AppViewTypes { - * val Card = ViewType("Card") - * val PatientHeader = ViewType("PatientHeader") - * } - * - * registry.registerComponent<PatientView, PatientCardConfig>( - * AppViewTypes.Card, PatientCardRenderer(), PatientCardConfig(), - * ) - * registry.registerComponent<PatientView, PatientCardConfig>( - * AppViewTypes.PatientHeader, - * PatientCardRenderer(), - * PatientCardConfig(showLastVisit = false), - * ) - * ``` - */ -inline fun <reified T : Any, C : Any> ViewRegistry.registerComponent( - viewType: ViewType, - renderer: ComponentRenderer<T, C>, - config: C, -) = putComponent(ViewTypeKey(viewType, T::class), renderer, config) - -/** - * Registers [renderer] as the [LayoutRenderer] for [viewType] / data type [T]. - * - * View-type constants are typically declared once in an `object`; see [ViewType]. - * - * ``` - * object AppViewTypes { - * val VerticalList = ViewType("VerticalList") - * } - * - * registry.registerLayout<PatientView>( - * AppViewTypes.VerticalList, - * VerticalListRenderer(contentPadding = PaddingValues(16.dp), itemSpacing = 12.dp), - * ) - * ``` - */ -inline fun <reified T : Any> ViewRegistry.registerLayout( - viewType: ViewType, - renderer: LayoutRenderer<T>, -) = putLayout(ViewTypeKey(viewType, T::class), renderer) - -/** - * Looks up the bound [ConfiguredRenderer] for [viewType] / data type [T] — the form scaffolds and - * layouts invoke. - * - * ``` - * val renderer = registry.componentRenderer<PatientView>(ViewType("Card")) - * ``` - * - * @throws NoSuchElementException if nothing is registered for the key. - */ -inline fun <reified T : Any> ViewRegistry.componentRenderer( - viewType: ViewType -): ConfiguredRenderer<T> = getComponent(ViewTypeKey(viewType, T::class)) - -/** - * Looks up the original [ComponentRenderer] (pre-[withConfig]) for [viewType] / data type [T]. - * Useful for identity-based assertions in tests and runtime introspection; not normally needed by - * app code. - * - * ``` - * val original = registry.componentSource<PatientView>(ViewType("Card")) - * assertSame(myRendererInstance, original) - * ``` - * - * @throws NoSuchElementException if nothing is registered for the key. - */ -inline fun <reified T : Any> ViewRegistry.componentSource( - viewType: ViewType -): ComponentRenderer<T, *> = getComponentSource(ViewTypeKey(viewType, T::class)) - -/** - * Looks up the [LayoutRenderer] for [viewType] / data type [T]. - * - * ``` - * val layout = registry.layoutRenderer<PatientView>(ViewType("VerticalList")) - * ``` - * - * @throws NoSuchElementException if nothing is registered for the key. - */ -inline fun <reified T : Any> ViewRegistry.layoutRenderer(viewType: ViewType): LayoutRenderer<T> = - getLayout(ViewTypeKey(viewType, T::class)) diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewType.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewType.kt deleted file mode 100644 index 639802d..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewType.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.registry - -import kotlin.jvm.JvmInline - -/** - * Opaque string label used to dispatch a renderer for a particular visual role. - * - * Paired with a data-type [kotlin.reflect.KClass] in [ViewTypeKey] to form the registry's lookup - * key. View-types are normally declared once per app as constants. - * - * ``` - * val Card = ViewType("Card") - * val PatientHeader = ViewType("PatientHeader") - * ``` - * - * @param value the underlying identifier; conventionally PascalCase. - */ -@JvmInline value class ViewType(val value: String) diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewTypeKey.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewTypeKey.kt deleted file mode 100644 index 7d4f81c..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/registry/ViewTypeKey.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.registry - -import kotlin.reflect.KClass - -/** - * Composite lookup key for the [ViewRegistry]: `(view-type × data type)`. - * - * Both dimensions are part of the key so the same [ViewType] name registered for different `T`s - * never collides. Looking up `(Card, PatientView)` and `(Card, MedicationView)` return distinct - * renderers; querying with the wrong `T` throws rather than returning a mismatched renderer. - * - * ``` - * val key = ViewTypeKey(ViewType("Card"), PatientView::class) - * ``` - * - * @param viewType the visual role label. - * @param dataType the Kotlin class of the data model that this renderer handles. - */ -data class ViewTypeKey<T : Any>(val viewType: ViewType, val dataType: KClass<T>) diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/renderer/ComponentRenderer.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/renderer/ComponentRenderer.kt deleted file mode 100644 index 584f3fa..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/renderer/ComponentRenderer.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.renderer - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -/** - * Groups the presentation-level options passed to every [ComponentRenderer.Render] call. - * - * @param onClick optional tap handler; null means the component is non-interactive. - * @param modifier applied to the root composable of the rendered item. - */ -data class RenderOptions(val modifier: Modifier = Modifier, val onClick: (() -> Unit)? = null) - -/** - * Author-facing renderer for a single item of type [T] using config [C]. - * - * The two type parameters let one renderer class be reused under multiple - * [dev.ohs.player.library.registry.ViewType] registrations with different configs — e.g. a patient - * card rendered both as a list card and as a detail header with `showLastVisit = false`. - * - * ``` - * data class PatientCardConfig(val showLastVisit: Boolean = true) - * - * class PatientCardRenderer : ComponentRenderer<PatientView, PatientCardConfig> { - * @Composable - * override fun Render( - * item: PatientView, - * config: PatientCardConfig, - * options: RenderOptions, - * ) { - * Card(onClick = options.onClick, modifier = options.modifier) { - * Text(item.fullName) - * if (config.showLastVisit) Text(item.lastVisitDate) - * } - * } - * } - * ``` - */ -interface ComponentRenderer<T, C> { - /** - * Renders [item] with [config] and [options] applied. - * - * @param item the data model instance. - * @param config configuration for this render call. - * @param options presentation options — tap handler and root modifier. - */ - @Composable fun Render(item: T, config: C, options: RenderOptions) -} - -/** - * Renders [item] with [config] and default [RenderOptions] (no click handler, [Modifier]). - * - * Convenience for Kotlin call sites that don't need custom options. - */ -@Composable -fun <T, C> ComponentRenderer<T, C>.Render(item: T, config: C) = - Render(item, config, RenderOptions()) - -/** - * A renderer with its config already applied. - * - * Created by [withConfig] when a [ComponentRenderer] is registered with its config, and then - * invoked by scaffolds and layouts during rendering. App code doesn't usually build these by hand — - * author a [ComponentRenderer] instead. - * - * ``` - * // Inside a LayoutRenderer.Render: - * component.Render(item, RenderOptions(onClick = { onItemClick(item) })) - * ``` - */ -fun interface ConfiguredRenderer<T> { - /** - * Renders [item]; the config from registration is already applied. - * - * @param item the data model instance. - * @param options presentation options — tap handler and root modifier. - */ - @Composable fun Render(item: T, options: RenderOptions) -} - -/** - * Captures [boundConfig] in a closure, producing the bound form for registry storage. - * - * Each invocation produces a fresh [ConfiguredRenderer] instance — they are not cached or - * value-equal. Callers needing identity (e.g. for tests) should retrieve the original - * [ComponentRenderer] via [dev.ohs.player.library.registry.componentSource] instead. - */ -@PublishedApi -internal fun <T : Any, C : Any> ComponentRenderer<T, C>.withConfig( - boundConfig: C -): ConfiguredRenderer<T> { - val source = this - return ConfiguredRenderer { item, options -> source.Render(item, boundConfig, options) } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/renderer/LayoutRenderer.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/renderer/LayoutRenderer.kt deleted file mode 100644 index f74d6fd..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/renderer/LayoutRenderer.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.renderer - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -/** - * Arranges a `List<T>` and delegates per-item rendering to a [ConfiguredRenderer]. - * - * The library ships three implementations — [dev.ohs.player.library.layout.VerticalListRenderer], - * [dev.ohs.player.library.layout.HorizontalListRenderer], and - * [dev.ohs.player.library.layout.GridListRenderer] — but apps can supply their own. - * - * ``` - * class StaggeredRenderer<T> : LayoutRenderer<T> { - * @Composable - * override fun Render(items, component, key, onItemClick, modifier) { - * LazyVerticalStaggeredGrid(StaggeredGridCells.Fixed(2), modifier) { - * items(items, key) { item -> - * component.Render(item, { onItemClick(item) }, Modifier) - * } - * } - * } - * } - * ``` - */ -interface LayoutRenderer<T> { - /** - * Lays out [items] using [component] for each entry. - * - * @param items the data to render. - * @param component the bound per-item renderer. - * @param key optional stable key function for the underlying lazy list; non-lazy renderers ignore - * it, and lazy renderers fall back to positional keys when it is `null`. - * @param onItemClick invoked when the user taps an item. - * @param modifier applied to the outer container. - */ - @Composable - fun Render( - items: List<T>, - component: ConfiguredRenderer<T>, - key: ((T) -> Any)? = null, - onItemClick: (T) -> Unit, - modifier: Modifier = Modifier, - ) -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/scaffold/DetailScaffold.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/scaffold/DetailScaffold.kt deleted file mode 100644 index 2dc8e7b..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/scaffold/DetailScaffold.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.scaffold - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import dev.ohs.player.library.registry.LocalViewRegistry -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.registry.ViewTypeKey -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.RenderOptions -import dev.ohs.player.library.renderer.withConfig -import kotlin.reflect.KClass - -/** - * Builder receiver for [DetailScaffold]. Collects the ordered list of sections plus optional top - * bar and not-found composables. Sections are appended via the `section(...)` overloads and render - * in the order they're declared. - * - * View-type constants are typically declared once in an `object`; see [ViewType]. - * - * ``` - * object AppViewTypes { - * val PersonalSection = ViewType("PersonalSection") - * val MedicalSection = ViewType("MedicalSection") - * } - * - * DetailScaffold(patient) { - * topBar { TopAppBar(title = { Text("Patient") }) } - * notFound { Text("Patient not found") } - * section(AppViewTypes.PersonalSection) - * section(AppViewTypes.MedicalSection) - * } - * ``` - * - * @param registry the registry to resolve view-type-based sections from. - * @param dataType the data class for the detail item, used in registry lookups. - */ -class DetailDslScope<T : Any> -@PublishedApi -internal constructor( - @PublishedApi internal val registry: ViewRegistry, - @PublishedApi internal val dataType: KClass<T>, -) { - /** Sections to render in declared order, each as a bound renderer. */ - @PublishedApi - internal var sections: List<ConfiguredRenderer<T>> = emptyList() - private set - - /** Optional top bar composable. */ - @PublishedApi internal var topBar: (@Composable () -> Unit)? = null - - /** Composable shown when the detail item is null. */ - @PublishedApi internal var notFound: (@Composable () -> Unit)? = null - - /** - * Appends a pre-built [ComponentRenderer] with its [config] as a section. - * - * ``` - * section(PersonalSectionRenderer(), PersonalSectionConfig) - * ``` - */ - fun <C : Any> section(renderer: ComponentRenderer<T, C>, config: C) { - sections += renderer.withConfig(config) - } - - /** - * Appends an inline section composable. Use for one-off sections that don't warrant a - * [ComponentRenderer] class. - * - * ``` - * section { patient -> Text("ID: ${patient.id}") } - * ``` - */ - fun section(content: @Composable (T) -> Unit) { - sections += ConfiguredRenderer { item, _ -> content(item) } - } - - /** - * Resolves the section renderer from the registry by [viewType]. - * - * ``` - * section(ViewType("PersonalSection")) - * ``` - */ - fun section(viewType: ViewType) { - sections += registry.getComponent(ViewTypeKey(viewType, dataType)) - } - - /** - * Sets the optional top bar composable. - * - * ``` - * topBar { TopAppBar(title = { Text("Patient") }) } - * ``` - */ - fun topBar(content: @Composable () -> Unit) { - topBar = content - } - - /** - * Sets the composable shown when the detail item is null. - * - * ``` - * notFound { Text("Patient not found") } - * ``` - */ - fun notFound(content: @Composable () -> Unit) { - notFound = content - } -} - -/** - * Scaffold for a single-item detail view of [T]. Null [item] renders the `notFound` composable and - * skips sections; non-null renders sections vertically in declared order inside a `LazyColumn`. - * Sections are read-only — their `onClick` is a no-op. - * - * View-type constants are typically declared once in an `object`; see [ViewType]. - * - * ``` - * object AppViewTypes { - * val PatientHeader = ViewType("PatientHeader") - * val PersonalSection = ViewType("PersonalSection") - * val MedicalSection = ViewType("MedicalSection") - * val ContactSection = ViewType("ContactSection") - * } - * - * val patient by viewModel.patient.collectAsStateWithLifecycle() - * DetailScaffold<PatientView>(item = patient) { - * topBar { TopAppBar(title = { Text(patient?.fullName.orEmpty()) }) } - * notFound { Text("Patient not found") } - * section(AppViewTypes.PatientHeader) - * section(AppViewTypes.PersonalSection) - * section(AppViewTypes.MedicalSection) - * section(AppViewTypes.ContactSection) - * } - * ``` - * - * @param item the detail item; null renders [DetailDslScope.notFound]. - * @param contentPadding padding around the section column. - * @param sectionSpacing vertical gap between sections. - * @param modifier applied to the root scaffold. - * @param builder DSL block configuring sections, top bar, and not-found state. - */ -@Composable -inline fun <reified T : Any> DetailScaffold( - item: T?, - contentPadding: PaddingValues = PaddingValues(16.dp), - sectionSpacing: Dp = 16.dp, - modifier: Modifier = Modifier, - builder: DetailDslScope<T>.() -> Unit, -) { - val registry = LocalViewRegistry.current - val scope = DetailDslScope(registry, T::class).apply(builder) - Scaffold(modifier = modifier, topBar = scope.topBar ?: {}) { padding -> - if (item == null) { - Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) { - scope.notFound?.invoke() - } - } else { - LazyColumn( - modifier = Modifier.fillMaxSize().padding(padding), - contentPadding = contentPadding, - verticalArrangement = Arrangement.spacedBy(sectionSpacing), - ) { - items(scope.sections) { section -> section.Render(item, RenderOptions()) } - } - } - } -} diff --git a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/scaffold/ListScaffold.kt b/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/scaffold/ListScaffold.kt deleted file mode 100644 index e4959ac..0000000 --- a/ohs-player-library/src/commonMain/kotlin/dev/ohs/player/library/scaffold/ListScaffold.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.scaffold - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import dev.ohs.player.library.layout.VerticalListRenderer -import dev.ohs.player.library.registry.LocalViewRegistry -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.registry.ViewTypeKey -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.withConfig -import kotlin.reflect.KClass - -/** - * Builder receiver for [ListScaffold]. Holds the chosen component renderer, layout renderer, top - * bar, and empty-state composable. One of the `component(...)` overloads is required; `layout(...)` - * is optional (defaults to [VerticalListRenderer]). - * - * View-type constants are typically declared once in an `object`; see [ViewType]. - * - * ``` - * object AppViewTypes { - * val Card = ViewType("Card") - * val VerticalList = ViewType("VerticalList") - * } - * - * ListScaffold<PatientView>(items = patients, onItemClick = ::onClick, key = { it.id }) { - * component(AppViewTypes.Card) - * layout(AppViewTypes.VerticalList) - * topBar { TopAppBar(title = { Text("Patients") }) } - * emptyState { Text("No patients") } - * } - * ``` - * - * @param registry the registry to resolve view-type-based renderers from. - * @param dataType the data class for the list items, used in registry lookups. - */ -class ListDslScope<T : Any> -@PublishedApi -internal constructor( - @PublishedApi internal val registry: ViewRegistry, - @PublishedApi internal val dataType: KClass<T>, -) { - /** Per-item renderer; must be set via one of the `component(...)` overloads before render. */ - @PublishedApi internal var component: ConfiguredRenderer<T>? = null - - /** Layout renderer; defaults to [VerticalListRenderer] if not set. */ - @PublishedApi internal var layout: LayoutRenderer<T>? = null - - /** Optional top bar composable. */ - @PublishedApi internal var topBar: (@Composable () -> Unit)? = null - - /** Composable shown when the items list is empty. */ - @PublishedApi internal var emptyState: (@Composable () -> Unit)? = null - - /** - * Sets the component renderer from a pre-built [ComponentRenderer] and its [config]. - * - * ``` - * component(PatientCardRenderer(), PatientCardConfig()) - * ``` - */ - fun <C : Any> component(renderer: ComponentRenderer<T, C>, config: C) { - component = renderer.withConfig(config) - } - - /** - * Sets the component renderer from an inline composable. Use for ad-hoc lists. - * - * ``` - * component { item, onClick -> - * Card(onClick = onClick) { Text(item.fullName) } - * } - * ``` - */ - fun component(content: @Composable (T, onClick: (() -> Unit)?) -> Unit) { - component = ConfiguredRenderer { item, options -> content(item, options.onClick) } - } - - /** - * Resolves the component renderer from the registry by [viewType]. - * - * ``` - * component(ViewType("Card")) - * ``` - */ - fun component(viewType: ViewType) { - component = registry.getComponent(ViewTypeKey(viewType, dataType)) - } - - /** - * Overrides the default vertical layout with [renderer]. - * - * ``` - * layout(GridListRenderer(cells = GridCells.Fixed(2))) - * ``` - */ - fun layout(renderer: LayoutRenderer<T>) { - layout = renderer - } - - /** - * Resolves the layout renderer from the registry by [viewType]. - * - * ``` - * layout(ViewType("HorizontalList")) - * ``` - */ - fun layout(viewType: ViewType) { - layout = registry.getLayout(ViewTypeKey(viewType, dataType)) - } - - /** - * Sets the optional top bar composable. - * - * ``` - * topBar { TopAppBar(title = { Text("Patients") }) } - * ``` - */ - fun topBar(content: @Composable () -> Unit) { - topBar = content - } - - /** - * Sets the composable shown when the items list is empty. - * - * ``` - * emptyState { Text("No patients") } - * ``` - */ - fun emptyState(content: @Composable () -> Unit) { - emptyState = content - } -} - -/** - * Scaffold for a list of [T]. Builds via [ListDslScope]. Empty [items] renders the `emptyState` - * composable and never invokes the layout renderer; non-empty delegates to the chosen - * [LayoutRenderer] (defaults to [VerticalListRenderer]). - * - * View-type constants are typically declared once in an `object`; see [ViewType]. - * - * ``` - * object AppViewTypes { - * val Card = ViewType("Card") - * val VerticalList = ViewType("VerticalList") - * } - * - * val patients by viewModel.patients.collectAsStateWithLifecycle() - * ListScaffold<PatientView>( - * items = patients, - * onItemClick = { onPatientClick(it.id) }, - * key = { it.id }, - * ) { - * component(AppViewTypes.Card) - * layout(AppViewTypes.VerticalList) - * topBar { TopAppBar(title = { Text("Patients") }) } - * emptyState { Text("No patients") } - * } - * ``` - * - * @param items the data list. - * @param onItemClick invoked when the user taps an item. - * @param key stable key function used by the underlying lazy list. - * @param modifier applied to the root scaffold. - * @param builder DSL block configuring component, layout, top bar, and empty state. - */ -@Composable -inline fun <reified T : Any> ListScaffold( - items: List<T>, - noinline onItemClick: (T) -> Unit, - noinline key: (T) -> Any, - modifier: Modifier = Modifier, - builder: ListDslScope<T>.() -> Unit, -) { - val registry = LocalViewRegistry.current - val scope = ListDslScope(registry, T::class).apply(builder) - val component = - requireNotNull(scope.component) { - "ListScaffold requires component(...) to be called in the builder." - } - val defaultLayout = remember { VerticalListRenderer<T>() } - val layoutRenderer = scope.layout ?: defaultLayout - - Scaffold(modifier = modifier, topBar = scope.topBar ?: {}) { padding -> - if (items.isEmpty()) { - Box(modifier = Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) { - scope.emptyState?.invoke() - } - } else { - layoutRenderer.Render( - items = items, - component = component, - key = key, - onItemClick = onItemClick, - modifier = Modifier.fillMaxSize().padding(padding), - ) - } - } -} diff --git a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/config/ConfigStoreTest.kt b/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/config/ConfigStoreTest.kt deleted file mode 100644 index 6c80ea5..0000000 --- a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/config/ConfigStoreTest.kt +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.config - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNull -import kotlin.test.assertSame -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.Serializable - -class ConfigStoreTest { - - private val viewDefinition = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "PatientSummary", "resource": "Patient", - "select": [{ "column": [{ "name": "patientId", "path": "id" }] }] } - """ - - private val viewJoinMap = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "patientSummary", "from": "root", "resource": "Patient", "view": "PatientSummary" } - """ - - // An arbitrary resource the app might also treat as config, keyed by `id`. - private val questionnaire = - """{ "resourceType": "Questionnaire", "id": "intake", "title": "Intake form" }""" - - @Serializable private data class Questionnaire(val id: String, val title: String) - - private fun storeOf(vararg resources: String) = ConfigStore { resources.toList() } - - @Test - fun get_returnsResource_byResourceTypeAndKey() = runTest { - val store = storeOf(viewDefinition, viewJoinMap, questionnaire) - - assertEquals( - "Patient", - store.get(VIEW_DEFINITION, "PatientSummary", ViewDefinition.serializer())?.resource, - ) - assertEquals( - "PatientSummary", - store.get(VIEW_JOIN_MAP, "patientSummary", ViewJoinMap.serializer())?.view, - ) - // The same store serves any resource kind — proving it isn't view-specific. - assertEquals( - "Intake form", - store.get("Questionnaire", "intake", Questionnaire.serializer())?.title, - ) - } - - @Test - fun get_loadsSourceOnce_andMemoizesDecodedResults() = runTest { - var reads = 0 - val store = ConfigStore { - reads++ - listOf(viewDefinition, viewJoinMap) - } - - val first = store.get(VIEW_DEFINITION, "PatientSummary", ViewDefinition.serializer()) - val second = store.get(VIEW_DEFINITION, "PatientSummary", ViewDefinition.serializer()) - store.get(VIEW_JOIN_MAP, "patientSummary", ViewJoinMap.serializer()) - - assertEquals(1, reads) - assertSame(first, second) - } - - @Test - fun get_returnsNull_whenResourceAbsent() = runTest { - val store = storeOf(viewJoinMap) - assertNull(store.get(VIEW_DEFINITION, "PatientSummary", ViewDefinition.serializer())) - assertNull(store.get("Questionnaire", "intake", Questionnaire.serializer())) - } - - private companion object { - const val VIEW_DEFINITION = "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition" - const val VIEW_JOIN_MAP = "http://ohs.dev/StructureDefinition/ViewJoinMap" - } -} diff --git a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/extractor/GenericStateExtractorTest.kt b/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/extractor/GenericStateExtractorTest.kt deleted file mode 100644 index 9e01bbb..0000000 --- a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/extractor/GenericStateExtractorTest.kt +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@file:UseSerializers( - FhirDateSerializer::class, - FhirDateTimeSerializer::class, - FhirDecimalSerializer::class, -) - -package dev.ohs.player.library.extractor - -import com.ionspin.kotlin.bignum.decimal.BigDecimal -import dev.ohs.fhir.model.r4.FhirDate -import dev.ohs.fhir.model.r4.FhirDateTime -import dev.ohs.fhir.model.r4.Resource -import dev.ohs.player.library.config.ConfigSource -import dev.ohs.player.library.config.ConfigStore -import dev.ohs.player.library.config.FhirDateSerializer -import dev.ohs.player.library.config.FhirDateTimeSerializer -import dev.ohs.player.library.config.FhirDecimalSerializer -import dev.ohs.player.library.model.SearchResult -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.Serializable -import kotlinx.serialization.UseSerializers -import kotlinx.serialization.json.Json - -@Serializable -private data class PatientAllergyTestState( - val allergyId: String? = null, - val substance: String? = null, - val patientRef: String? = null, - val manifestation: String? = null, - val severity: String? = null, - val patientId: String? = null, - val familyName: String? = null, -) - -@Serializable -private data class PatientTelecomTestState( - val patientId: String? = null, - val telecomSystem: String? = null, - val telecomValue: String? = null, -) - -@Serializable -private data class ScalarTypesTestState( - val active: Boolean? = null, - val birthDate: FhirDate? = null, - val gender: String? = null, -) - -@Serializable private data class DecimalTypesTestState(val longitude: BigDecimal? = null) - -@Serializable private data class DateTimeTypesTestState(val recorded: FhirDateTime? = null) - -@Serializable private data class DecimalConstTestState(val locId: String? = null) - -@Serializable private data class DateConstTestState(val patientId: String? = null) - -class GenericStateExtractorTest { - - private val patient = - """ - { "resourceType": "Patient", "id": "p1", "active": true, "gender": "female", "birthDate": "1990-03-14", - "name": [{ "family": "Diallo", "given": ["Amina"] }], - "telecom": [{ "system": "phone", "value": "555-0100" }, - { "system": "email", "value": "amina@example.org" }] } - """ - - private val activeAllergy = - """ - { "resourceType": "AllergyIntolerance", "id": "ai1", "patient": { "reference": "Patient/p1" }, - "code": { "coding": [{ "display": "Penicillin" }] }, - "clinicalStatus": { "coding": [{ "code": "active" }] }, - "reaction": [{ "manifestation": [{ "coding": [{ "display": "Rash" }] }], "severity": "moderate" }, - { "manifestation": [{ "coding": [{ "display": "Hives" }] }], "severity": "severe" }] } - """ - - private val inactiveAllergy = - """ - { "resourceType": "AllergyIntolerance", "id": "ai2", "patient": { "reference": "Patient/p1" }, - "code": { "coding": [{ "display": "Aspirin" }] }, - "clinicalStatus": { "coding": [{ "code": "inactive" }] }, - "reaction": [{ "manifestation": [{ "coding": [{ "display": "Nausea" }] }], "severity": "mild" }] } - """ - - private val allergyView = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "AllergyView", "resource": "AllergyIntolerance", - "where": [{ "path": "clinicalStatus.coding.where(code = 'active').exists()" }], - "select": [ - { "column": [{ "name": "allergyId", "path": "id" }, - { "name": "substance", "path": "code.coding.display.first()" }, - { "name": "patientRef", "path": "patient.reference.replace('Patient/', '')" }] }, - { "forEach": "reaction", - "column": [{ "name": "manifestation", "path": "manifestation.coding.display.first()" }, - { "name": "severity", "path": "severity" }] } - ] } - """ - - private val patientView = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "PatientView", "resource": "Patient", - "select": [{ "column": [{ "name": "patientId", "path": "id" }, - { "name": "familyName", "path": "name.family.first()" }] }] } - """ - - private val patientAllergyMap = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "patientAllergyTest", "from": "revIncluded", "resource": "AllergyIntolerance", "view": "AllergyView", - "joins": [{ "view": "PatientView", "from": "included", "resource": "Patient", - "searchParam": "patient", "matchKey": "patientRef" }] } - """ - - private val telecomView = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "TelecomView", "resource": "Patient", - "select": [ - { "column": [{ "name": "patientId", "path": "id" }] }, - { "unionAll": [ - { "column": [{ "name": "telecomSystem", "path": "telecom.where(system = 'phone').system.first()" }, - { "name": "telecomValue", "path": "telecom.where(system = 'phone').value.first()" }] }, - { "column": [{ "name": "telecomSystem", "path": "telecom.where(system = 'email').system.first()" }, - { "name": "telecomValue", "path": "telecom.where(system = 'email').value.first()" }] } - ] } - ] } - """ - - private val patientTelecomMap = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "patientTelecomTest", "from": "root", "resource": "Patient", "view": "TelecomView" } - """ - - private val jsonFormat = Json { ignoreUnknownKeys = true } - - private fun resource(json: String): Resource = - jsonFormat.decodeFromString(Resource.serializer(), json) - - private fun extractorOf(vararg configs: String): GenericStateExtractor = - GenericStateExtractor(ConfigStore(ConfigSource { configs.toList() })) - - @Test - fun extract_appliesWhere_expandsForEach_andDynamicJoins() = runTest { - val p1 = resource(patient) - val result = - SearchResult( - resource = p1, - included = mapOf("patient" to listOf(p1)), - revIncluded = - mapOf( - "AllergyIntolerance" to - "patient" to - listOf(resource(activeAllergy), resource(inactiveAllergy)) - ), - ) - - val states = - extractorOf(allergyView, patientView, patientAllergyMap) - .extract<PatientAllergyTestState>(result) - - // The inactive allergy is dropped by `where`; the active one fans out one row per reaction, - // each carrying the joined patient columns. - assertEquals(2, states.size) - assertEquals(listOf("Rash", "Hives"), states.map { it.manifestation }) - assertEquals(listOf("moderate", "severe"), states.map { it.severity }) - assertEquals(setOf("Penicillin"), states.map { it.substance }.toSet()) - assertEquals(setOf("Diallo"), states.map { it.familyName }.toSet()) - assertEquals(setOf("p1"), states.map { it.patientId }.toSet()) - } - - @Test - fun extract_crossJoinsUnionAll_withAnchorColumns() = runTest { - val p1 = resource(patient) - val result = SearchResult(resource = p1) - - val states = - extractorOf(telecomView, patientTelecomMap).extract<PatientTelecomTestState>(result) - - // unionAll yields one row per telecom system, each cross-joined with the anchor patientId - // column. - assertEquals(2, states.size) - assertEquals(setOf("p1"), states.map { it.patientId }.toSet()) - assertEquals( - setOf("phone" to "555-0100", "email" to "amina@example.org"), - states.map { it.telecomSystem to it.telecomValue }.toSet(), - ) - } - - private val location = - """{ "resourceType": "Location", "id": "loc1", "status": "active", - "position": { "longitude": -122.5, "latitude": 37.7 } }""" - - private val condition = - """{ "resourceType": "Condition", "id": "c1", "subject": { "reference": "Patient/p1" }, - "recordedDate": "2021-06-15T08:30:00Z" }""" - - private fun rootView(name: String, resource: String, vararg columns: String) = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "$name", "resource": "$resource", - "select": [{ "column": [${columns.joinToString(",")}] }] } - """ - - private fun rootMap(name: String, resource: String, view: String) = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "$name", "from": "root", "resource": "$resource", "view": "$view" } - """ - - @Test - fun extract_coercesBooleanDateAndStringTypes() = runTest { - val view = - rootView( - "ScalarTypesView", - "Patient", - """{ "name": "active", "path": "active", "type": "boolean" }""", - """{ "name": "birthDate", "path": "birthDate", "type": "date" }""", - """{ "name": "gender", "path": "gender", "type": "code" }""", - ) - val result = SearchResult(resource = resource(patient)) - - val state = - extractorOf(view, rootMap("scalarTypesTest", "Patient", "ScalarTypesView")) - .extract<ScalarTypesTestState>(result) - .single() - - assertEquals(true, state.active) - assertEquals("female", state.gender) - assertNotNull(state.birthDate, "date column must map to a non-null FhirDate") - assertEquals("1990-03-14", state.birthDate.toString()) - } - - @Test - fun extract_coercesDecimalToBigDecimal() = runTest { - val view = - rootView( - "DecimalTypesView", - "Location", - """{ "name": "longitude", "path": "position.longitude", "type": "decimal" }""", - ) - val result = SearchResult(resource = resource(location)) - - val state = - extractorOf(view, rootMap("decimalTypesTest", "Location", "DecimalTypesView")) - .extract<DecimalTypesTestState>(result) - .single() - - assertNotNull(state.longitude, "decimal column must map to a non-null BigDecimal") - assertEquals(BigDecimal.parseString("-122.5"), state.longitude) - } - - @Test - fun extract_coercesDateTimeToFhirDateTime() = runTest { - val view = - rootView( - "DateTimeTypesView", - "Condition", - """{ "name": "recorded", "path": "recordedDate", "type": "dateTime" }""", - ) - val result = SearchResult(resource = resource(condition)) - - val state = - extractorOf(view, rootMap("dateTimeTypesTest", "Condition", "DateTimeTypesView")) - .extract<DateTimeTypesTestState>(result) - .single() - - assertNotNull(state.recorded, "dateTime column must map to a non-null FhirDateTime") - assertTrue( - state.recorded.toString().startsWith("2021-06-15"), - "expected recorded to start with 2021-06-15 but was ${state.recorded}", - ) - } - - @Test - fun extract_appliesDecimalConstantInWhere() = runTest { - val view = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "DecimalConstView", "resource": "Location", - "constant": [{ "name": "boundary", "valueDecimal": 0.0 }], - "where": [{ "path": "position.longitude < %boundary" }], - "select": [{ "column": [{ "name": "locId", "path": "id" }] }] } - """ - val map = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "decimalConstTest", "from": "root", "resource": "Location", "view": "DecimalConstView" } - """ - val states = - extractorOf(view, map) - .extract<DecimalConstTestState>(SearchResult(resource = resource(location))) - - // longitude -122.5 < 0.0 the row is kept only if the %boundary decimals compare - assertEquals(listOf("loc1"), states.map { it.locId }) - } - - @Test - fun extract_appliesDateConstantInWhere() = runTest { - val view = - """ - { "resourceType": "https://sql-on-fhir.org/ig/StructureDefinition/ViewDefinition", - "name": "DateConstView", "resource": "Patient", - "constant": [{ "name": "cutoff", "valueDate": "2000-01-01" }], - "where": [{ "path": "birthDate < %cutoff" }], - "select": [{ "column": [{ "name": "patientId", "path": "id" }] }] } - """ - val map = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "dateConstTest", "from": "root", "resource": "Patient", "view": "DateConstView" } - """ - val states = - extractorOf(view, map).extract<DateConstTestState>(SearchResult(resource = resource(patient))) - - // birthDate 1990-03-14 < 2000-01-01 → kept only if the %cutoff date constant compares - // correctly. - assertEquals(listOf("p1"), states.map { it.patientId }) - } - - @Test - fun extract_fails_whenNonStringColumnHasNoType() = runTest { - val view = - rootView( - "ScalarTypesView", - "Patient", - """{ "name": "active", "path": "active" }""", - """{ "name": "birthDate", "path": "birthDate", "type": "date" }""", - """{ "name": "gender", "path": "gender", "type": "code" }""", - ) - val extractor = extractorOf(view, rootMap("scalarTypesTest", "Patient", "ScalarTypesView")) - - val error = - assertFailsWith<IllegalArgumentException> { - extractor.extract<ScalarTypesTestState>(SearchResult(resource = resource(patient))) - } - assertTrue( - error.message?.contains("active") == true, - "expected the error to name the untyped boolean column, was: ${error.message}", - ) - } - - @Test - fun extract_fails_whenJoinMatchKeyIsNotAProducedColumn() = runTest { - val badMap = - """ - { "resourceType": "http://ohs.dev/StructureDefinition/ViewJoinMap", - "name": "patientAllergyTest", "from": "revIncluded", "resource": "AllergyIntolerance", "view": "AllergyView", - "joins": [{ "view": "PatientView", "from": "included", "resource": "Patient", - "searchParam": "patient", "matchKey": "noSuchColumn" }] } - """ - val p1 = resource(patient) - val result = - SearchResult( - resource = p1, - included = mapOf("patient" to listOf(p1)), - revIncluded = mapOf("AllergyIntolerance" to "patient" to listOf(resource(activeAllergy))), - ) - val extractor = extractorOf(allergyView, patientView, badMap) - - val error = - assertFailsWith<IllegalArgumentException> { - extractor.extract<PatientAllergyTestState>(result) - } - assertTrue( - error.message?.contains("noSuchColumn") == true, - "expected the error to name the bad matchKey, was: ${error.message}", - ) - } -} diff --git a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/registry/ViewRegistryTest.kt b/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/registry/ViewRegistryTest.kt deleted file mode 100644 index 9810384..0000000 --- a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/registry/ViewRegistryTest.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.registry - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.RenderOptions -import kotlin.test.Test -import kotlin.test.assertFailsWith -import kotlin.test.assertSame - -private val FooViewType = ViewType("Foo") - -private data object TestConfig - -private class StringRenderer : ComponentRenderer<String, TestConfig> { - @Composable override fun Render(item: String, config: TestConfig, options: RenderOptions) {} -} - -private class StringLayoutRenderer : LayoutRenderer<String> { - @Composable - override fun Render( - items: List<String>, - component: ConfiguredRenderer<String>, - key: ((String) -> Any)?, - onItemClick: (String) -> Unit, - modifier: Modifier, - ) {} -} - -class ViewRegistryTest { - - @Test - fun registerAndLookup_works_forComponentAndLayout() { - val registry = ViewRegistry() - val component = StringRenderer() - val layout = StringLayoutRenderer() - - registry.registerComponent(FooViewType, component, TestConfig) - registry.registerLayout<String>(FooViewType, layout) - - assertSame(component, registry.componentSource<String>(FooViewType)) - assertSame(layout, registry.layoutRenderer<String>(FooViewType)) - } - - @Test - fun differentDataType_throwsOnLookup() { - val registry = ViewRegistry() - registry.registerComponent(FooViewType, StringRenderer(), TestConfig) - - // Same view-type value, different T, must throw to prevent silent fallback. - assertFailsWith<NoSuchElementException> { registry.componentRenderer<Int>(FooViewType) } - } -} diff --git a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/scaffold/DetailScaffoldTest.kt b/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/scaffold/DetailScaffoldTest.kt deleted file mode 100644 index 3565919..0000000 --- a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/scaffold/DetailScaffoldTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.scaffold - -import androidx.compose.foundation.text.BasicText as Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.test.ExperimentalTestApi -import androidx.compose.ui.test.assertCountEquals -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.onAllNodesWithText -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.runComposeUiTest -import dev.ohs.player.library.registry.LocalViewRegistry -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.registry.registerComponent -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions -import kotlin.test.Test - -private val SectionA = ViewType("SectionA") -private val SectionB = ViewType("SectionB") -private val SectionC = ViewType("SectionC") - -private data object DetailTestConfig - -private class LabeledRenderer(private val label: String) : - ComponentRenderer<String, DetailTestConfig> { - @Composable - override fun Render(item: String, config: DetailTestConfig, options: RenderOptions) { - Text("[$label] $item") - } -} - -@OptIn(ExperimentalTestApi::class) -class DetailScaffoldTest { - - @Test - fun nullItem_showsNotFound_andSkipsSections() = runComposeUiTest { - val registry = - ViewRegistry().apply { registerComponent(SectionA, LabeledRenderer("A"), DetailTestConfig) } - setContent { - CompositionLocalProvider(LocalViewRegistry provides registry) { - DetailScaffold<String>(item = null) { - section(SectionA) - notFound { Text("missing") } - } - } - } - onNodeWithText("missing").assertIsDisplayed() - // The section renderer would emit "[A] x" if invoked; assert no such node exists. - onAllNodesWithText("[A] x").assertCountEquals(0) - } - - @Test - fun sections_renderInDeclaredOrder() = runComposeUiTest { - val registry = - ViewRegistry().apply { - registerComponent(SectionA, LabeledRenderer("A"), DetailTestConfig) - registerComponent(SectionB, LabeledRenderer("B"), DetailTestConfig) - registerComponent(SectionC, LabeledRenderer("C"), DetailTestConfig) - } - setContent { - CompositionLocalProvider(LocalViewRegistry provides registry) { - DetailScaffold<String>(item = "x") { - section(SectionA) - section(SectionB) - section(SectionC) - } - } - } - onNodeWithText("[A] x").assertIsDisplayed() - onNodeWithText("[B] x").assertIsDisplayed() - onNodeWithText("[C] x").assertIsDisplayed() - } -} diff --git a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/scaffold/ListScaffoldTest.kt b/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/scaffold/ListScaffoldTest.kt deleted file mode 100644 index dd5a467..0000000 --- a/ohs-player-library/src/commonTest/kotlin/dev/ohs/player/library/scaffold/ListScaffoldTest.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2026 Open Health Stack Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.ohs.player.library.scaffold - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.text.BasicText as Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Modifier -import androidx.compose.ui.test.ExperimentalTestApi -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.runComposeUiTest -import dev.ohs.player.library.registry.LocalViewRegistry -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.ViewType -import dev.ohs.player.library.registry.registerComponent -import dev.ohs.player.library.registry.registerLayout -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.RenderOptions -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFails -import kotlin.test.assertTrue - -private val TextItemViewType = ViewType("TextItem") -private val PlainListViewType = ViewType("PlainList") - -private data object ListTestConfig - -private class TextRenderer : ComponentRenderer<String, ListTestConfig> { - @Composable - override fun Render(item: String, config: ListTestConfig, options: RenderOptions) { - Text(text = item, modifier = options.modifier.clickable { options.onClick?.invoke() }) - } -} - -private class RecordingLayout : LayoutRenderer<String> { - var renderInvocations = 0 - private set - - @Composable - override fun Render( - items: List<String>, - component: ConfiguredRenderer<String>, - key: ((String) -> Any)?, - onItemClick: (String) -> Unit, - modifier: Modifier, - ) { - renderInvocations++ - Column { - items.forEach { item -> - component.Render(item, RenderOptions(onClick = { onItemClick(item) })) - } - } - } -} - -@OptIn(ExperimentalTestApi::class) -class ListScaffoldTest { - - @Test - fun emptyList_showsEmptyState_andDoesNotInvokeLayout() = runComposeUiTest { - val registry = - ViewRegistry().apply { registerComponent(TextItemViewType, TextRenderer(), ListTestConfig) } - val layout = RecordingLayout() - - setContent { - CompositionLocalProvider(LocalViewRegistry provides registry) { - ListScaffold<String>(items = emptyList(), onItemClick = {}, key = { it }) { - component(TextItemViewType) - layout(layout) - emptyState { Text("nothing here") } - } - } - } - - onNodeWithText("nothing here").assertIsDisplayed() - assertEquals(0, layout.renderInvocations) - } - - @Test - fun rendersItems_andForwardsClicksFromRegistry() = runComposeUiTest { - var clicked: String? = null - val registry = - ViewRegistry().apply { - registerComponent(TextItemViewType, TextRenderer(), ListTestConfig) - registerLayout<String>(PlainListViewType, RecordingLayout()) - } - - setContent { - CompositionLocalProvider(LocalViewRegistry provides registry) { - ListScaffold<String>( - items = listOf("alpha", "beta", "gamma"), - onItemClick = { clicked = it }, - key = { it }, - ) { - component(TextItemViewType) - layout(PlainListViewType) - } - } - } - - onNodeWithText("alpha").assertIsDisplayed() - onNodeWithText("beta").assertIsDisplayed() - onNodeWithText("gamma").assertIsDisplayed() - - onNodeWithText("beta").performClick() - assertEquals("beta", clicked) - } - - @Test - fun omittingLayout_fallsBackToVerticalListRenderer() = runComposeUiTest { - val registry = - ViewRegistry().apply { registerComponent(TextItemViewType, TextRenderer(), ListTestConfig) } - - setContent { - CompositionLocalProvider(LocalViewRegistry provides registry) { - ListScaffold<String>(items = listOf("one", "two"), onItemClick = {}, key = { it }) { - component(TextItemViewType) - } - } - } - - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() - } - - @Test - fun unregisteredViewType_throwsWithDescriptiveMessage() = runComposeUiTest { - val thrown = assertFails { - setContent { - ListScaffold<String>(items = listOf("x"), onItemClick = {}, key = { it }) { - component(TextItemViewType) - } - } - } - val msg = thrown.message.orEmpty() - assertTrue(msg.contains("String"), "Message should mention data type: $msg") - assertTrue(msg.contains(TextItemViewType.value), "Message should mention view type value: $msg") - } -} diff --git a/ohs-player-reference-app/build.gradle.kts b/reference-app/build.gradle.kts similarity index 98% rename from ohs-player-reference-app/build.gradle.kts rename to reference-app/build.gradle.kts index 0f1f690..c14a96c 100644 --- a/ohs-player-reference-app/build.gradle.kts +++ b/reference-app/build.gradle.kts @@ -58,7 +58,7 @@ kotlin { implementation(libs.androidx.activity.compose) } commonMain.dependencies { - implementation(project(":ohs-player-library")) + implementation(libs.ohs.player.reference.library) implementation(libs.compose.runtime) implementation(libs.compose.foundation) implementation(libs.compose.material) @@ -216,7 +216,7 @@ val composePackageVersion: String = // * Kotlin/JS IR backend crashes lowering the generated sealed-interface dispatch tables in // dev.ohs.fhir:fhir-path (StackOverflow in KotlinLikeDumper). The main JS compile is fine; only // the JS *test* executable lowering trips because the test source set exercises those types. -// Mirrors the same skip in :ohs-player-library. +// Mirrors the same skip in the ohs-player-reference-client-library repo. // // * Android/JVM host unit tests need a host Android framework (NoClassDefFoundError). // diff --git a/ohs-player-reference-app/src/androidMain/AndroidManifest.xml b/reference-app/src/androidMain/AndroidManifest.xml similarity index 100% rename from ohs-player-reference-app/src/androidMain/AndroidManifest.xml rename to reference-app/src/androidMain/AndroidManifest.xml diff --git a/ohs-player-reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/MainActivity.kt b/reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/MainActivity.kt similarity index 100% rename from ohs-player-reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/MainActivity.kt rename to reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/MainActivity.kt diff --git a/ohs-player-reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.android.kt b/reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.android.kt similarity index 100% rename from ohs-player-reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.android.kt rename to reference-app/src/androidMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.android.kt diff --git a/ohs-player-reference-app/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/reference-app/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml rename to reference-app/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml diff --git a/ohs-player-reference-app/src/androidMain/res/drawable/ic_launcher_background.xml b/reference-app/src/androidMain/res/drawable/ic_launcher_background.xml similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/drawable/ic_launcher_background.xml rename to reference-app/src/androidMain/res/drawable/ic_launcher_background.xml diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml rename to reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to reference-app/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher.png rename to reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png rename to reference-app/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher.png rename to reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png rename to reference-app/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher.png rename to reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png rename to reference-app/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png rename to reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png rename to reference-app/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png rename to reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/ohs-player-reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png rename to reference-app/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/ohs-player-reference-app/src/androidMain/res/values/strings.xml b/reference-app/src/androidMain/res/values/strings.xml similarity index 100% rename from ohs-player-reference-app/src/androidMain/res/values/strings.xml rename to reference-app/src/androidMain/res/values/strings.xml diff --git a/ohs-player-reference-app/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/reference-app/src/commonMain/composeResources/drawable/compose-multiplatform.xml similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/drawable/compose-multiplatform.xml rename to reference-app/src/commonMain/composeResources/drawable/compose-multiplatform.xml diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/SampleResourcesBundle.json b/reference-app/src/commonMain/composeResources/files/SampleResourcesBundle.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/SampleResourcesBundle.json rename to reference-app/src/commonMain/composeResources/files/SampleResourcesBundle.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyReactionItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyReactionItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyReactionItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-AllergyReactionItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-ConditionItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-ConditionItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-ConditionItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-ConditionItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-ContactItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-ContactItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-ContactItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-ContactItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-GroupCardConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-GroupCardConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-GroupCardConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-GroupCardConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-GroupHeaderConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-GroupHeaderConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-GroupHeaderConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-GroupHeaderConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-ImmunizationItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-ImmunizationItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-ImmunizationItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-ImmunizationItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-MedicationItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-MedicationItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-MedicationItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-MedicationItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-MemberItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-MemberItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-MemberItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-MemberItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-PatientCardConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-PatientCardConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-PatientCardConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-PatientCardConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-PatientHeaderConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-PatientHeaderConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-PatientHeaderConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-PatientHeaderConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-SectionCardConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-SectionCardConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-SectionCardConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-SectionCardConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-TelecomItemConfig.json b/reference-app/src/commonMain/composeResources/files/configs/Binary-TelecomItemConfig.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Binary-TelecomItemConfig.json rename to reference-app/src/commonMain/composeResources/files/configs/Binary-TelecomItemConfig.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdMembers.json b/reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdMembers.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdMembers.json rename to reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdMembers.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdRegistration.json b/reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdRegistration.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdRegistration.json rename to reference-app/src/commonMain/composeResources/files/configs/Questionnaire-HouseholdRegistration.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/configs/Questionnaire-PatientClinicalData.json b/reference-app/src/commonMain/composeResources/files/configs/Questionnaire-PatientClinicalData.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/configs/Questionnaire-PatientClinicalData.json rename to reference-app/src/commonMain/composeResources/files/configs/Questionnaire-PatientClinicalData.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Allergy.json b/reference-app/src/commonMain/composeResources/files/states/Binary-Allergy.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Allergy.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-Allergy.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReaction.json b/reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReaction.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReaction.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReaction.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReactionState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReactionState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReactionState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-AllergyReactionState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Condition.json b/reference-app/src/commonMain/composeResources/files/states/Binary-Condition.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Condition.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-Condition.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Group.json b/reference-app/src/commonMain/composeResources/files/states/Binary-Group.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Group.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-Group.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-GroupHeaderState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-GroupHeaderState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-GroupHeaderState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-GroupHeaderState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-GroupListState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-GroupListState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-GroupListState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-GroupListState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-GroupMemberState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-GroupMemberState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-GroupMemberState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-GroupMemberState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Immunization.json b/reference-app/src/commonMain/composeResources/files/states/Binary-Immunization.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Immunization.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-Immunization.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Medication.json b/reference-app/src/commonMain/composeResources/files/states/Binary-Medication.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Medication.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-Medication.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Member.json b/reference-app/src/commonMain/composeResources/files/states/Binary-Member.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-Member.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-Member.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientAllergyState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientAllergyState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientAllergyState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientAllergyState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientConditionState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientConditionState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientConditionState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientConditionState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientContact.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientContact.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientContact.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientContact.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientContactState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientContactState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientContactState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientContactState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientImmunizationState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientImmunizationState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientImmunizationState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientImmunizationState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientMedicationState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientMedicationState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientMedicationState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientMedicationState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummary.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummary.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummary.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummary.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummaryState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummaryState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummaryState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientSummaryState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecom.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecom.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecom.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecom.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecomState.json b/reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecomState.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecomState.json rename to reference-app/src/commonMain/composeResources/files/states/Binary-PatientTelecomState.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/files/viewtypes/CodeSystem-ViewTypes.json b/reference-app/src/commonMain/composeResources/files/viewtypes/CodeSystem-ViewTypes.json similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/files/viewtypes/CodeSystem-ViewTypes.json rename to reference-app/src/commonMain/composeResources/files/viewtypes/CodeSystem-ViewTypes.json diff --git a/ohs-player-reference-app/src/commonMain/composeResources/values/strings.xml b/reference-app/src/commonMain/composeResources/values/strings.xml similarity index 100% rename from ohs-player-reference-app/src/commonMain/composeResources/values/strings.xml rename to reference-app/src/commonMain/composeResources/values/strings.xml diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt similarity index 98% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt index cd57fc1..8fc2e86 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/App.kt @@ -24,12 +24,12 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import androidx.savedstate.read -import dev.ohs.player.library.registry.LocalViewRegistry import dev.ohs.player.reference.app.feature.group.list.GroupListScreen import dev.ohs.player.reference.app.feature.group.profile.GroupProfileScreen import dev.ohs.player.reference.app.feature.patient.profile.PatientProfileScreen import dev.ohs.player.reference.app.feature.questionnaire.QuestionnaireHostScreen import dev.ohs.player.reference.app.feature.questionnaire.QuestionnaireIds +import dev.ohs.player.reference.library.registry.LocalViewRegistry import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt similarity index 94% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt index 5751143..cebae40 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/AppViewRegistry.kt @@ -15,11 +15,11 @@ */ package dev.ohs.player.reference.app -import dev.ohs.player.library.registry.ViewRegistry import dev.ohs.player.reference.app.feature.group.list.registerGroupList import dev.ohs.player.reference.app.feature.group.profile.registerGroupProfile import dev.ohs.player.reference.app.feature.patient.list.registerPatientList import dev.ohs.player.reference.app.feature.patient.profile.registerPatientProfile +import dev.ohs.player.reference.library.registry.ViewRegistry fun buildAppViewRegistry(): ViewRegistry = ViewRegistry().apply { diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/OhsPlayerTheme.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/OhsPlayerTheme.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/OhsPlayerTheme.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/OhsPlayerTheme.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/AppDependencies.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/AppDependencies.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/AppDependencies.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/AppDependencies.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt similarity index 89% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt index 2ffb2de..b5168a6 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/Extraction.kt @@ -15,9 +15,9 @@ */ package dev.ohs.player.reference.app.data -import dev.ohs.player.library.config.ConfigStore -import dev.ohs.player.library.extractor.GenericStateExtractor import dev.ohs.player.reference.app.data.datasource.LocalConfigSource +import dev.ohs.player.reference.library.config.ConfigStore +import dev.ohs.player.reference.library.extractor.GenericStateExtractor /** * App-wide extraction wiring: the runtime [ConfigStore] (fed by bundled config Binaries) and the diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/FhirReferences.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/FhirReferences.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/FhirReferences.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/FhirReferences.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt similarity index 91% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt index de922a0..58aed96 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/LocalConfigSource.kt @@ -16,8 +16,8 @@ package dev.ohs.player.reference.app.data.datasource import dev.ohs.player.generated.GeneratedConfigManifest -import dev.ohs.player.library.config.ConfigSource -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.Res +import dev.ohs.player.reference.library.config.ConfigSource +import ohs_player_reference_client_app.reference_app.generated.resources.Res /** * Loads the runtime config Binaries the extractor needs (ViewJoinMap + ViewDefinition) from diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/SampleDataStore.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/SampleDataStore.kt similarity index 98% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/SampleDataStore.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/SampleDataStore.kt index 607a95a..b6a15a8 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/SampleDataStore.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/datasource/SampleDataStore.kt @@ -22,10 +22,10 @@ import dev.ohs.fhir.model.r4.Immunization import dev.ohs.fhir.model.r4.MedicationRequest import dev.ohs.fhir.model.r4.Patient import dev.ohs.fhir.model.r4.Resource -import dev.ohs.player.library.model.SearchResult import dev.ohs.player.reference.app.data.AppDependencies import dev.ohs.player.reference.app.data.patientIdFromReference import dev.ohs.player.reference.app.data.repository.FhirRepository +import dev.ohs.player.reference.library.model.SearchResult /** Returns all patient IDs — used by the patient list screen. */ suspend fun allPatientIds( diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/FhirRepository.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/FhirRepository.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/FhirRepository.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/FhirRepository.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepository.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepository.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepository.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepository.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepository.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepository.kt similarity index 99% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepository.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepository.kt index 21af562..5997186 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepository.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepository.kt @@ -33,7 +33,7 @@ import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.Res +import ohs_player_reference_client_app.reference_app.generated.resources.Res import org.jetbrains.compose.resources.ExperimentalResourceApi /** diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/PatientRepository.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/PatientRepository.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/PatientRepository.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/PatientRepository.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/RepositorySnapshotStore.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/RepositorySnapshotStore.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/RepositorySnapshotStore.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/data/repository/RepositorySnapshotStore.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/CardView.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/CardView.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/CardView.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/CardView.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/Chip.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/Chip.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/Chip.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/Chip.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/SectionCardLayoutRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/SectionCardLayoutRenderer.kt similarity index 97% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/SectionCardLayoutRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/SectionCardLayoutRenderer.kt index c1567c9..7b9ddee 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/SectionCardLayoutRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/SectionCardLayoutRenderer.kt @@ -53,9 +53,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.SectionCardConfig -import dev.ohs.player.library.renderer.ConfiguredRenderer -import dev.ohs.player.library.renderer.LayoutRenderer -import dev.ohs.player.library.renderer.RenderOptions +import dev.ohs.player.reference.library.renderer.ConfiguredRenderer +import dev.ohs.player.reference.library.renderer.LayoutRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions /** * Layout renderer that wraps a list of items inside a titled section card. Registered under diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusChip.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusChip.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusChip.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusChip.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusRow.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusRow.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusRow.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/component/common/StatusRow.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCard.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCard.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCard.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCard.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCardRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCardRenderer.kt similarity index 89% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCardRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCardRenderer.kt index b705809..b7b233a 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCardRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupCardRenderer.kt @@ -18,8 +18,8 @@ package dev.ohs.player.reference.app.feature.group.list import androidx.compose.runtime.Composable import dev.ohs.player.generated.config.GroupCardConfig import dev.ohs.player.generated.state.GroupListState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class GroupCardRenderer : ComponentRenderer<GroupListState, GroupCardConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListRegistrations.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListRegistrations.kt similarity index 82% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListRegistrations.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListRegistrations.kt index 3e8e8a4..2923029 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListRegistrations.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListRegistrations.kt @@ -20,10 +20,10 @@ import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.GroupCardConfig import dev.ohs.player.generated.state.GroupListState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.layout.VerticalListRenderer -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.registerComponent -import dev.ohs.player.library.registry.registerLayout +import dev.ohs.player.reference.library.layout.VerticalListRenderer +import dev.ohs.player.reference.library.registry.ViewRegistry +import dev.ohs.player.reference.library.registry.registerComponent +import dev.ohs.player.reference.library.registry.registerLayout fun ViewRegistry.registerGroupList() { registerComponent<GroupListState, GroupCardConfig>( diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListScreen.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListScreen.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListScreen.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListScreen.kt index b2451b4..76b5544 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListScreen.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListScreen.kt @@ -37,8 +37,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import dev.ohs.player.generated.state.GroupListState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.layout.VerticalListRenderer -import dev.ohs.player.library.scaffold.ListScaffold +import dev.ohs.player.reference.library.layout.VerticalListRenderer +import dev.ohs.player.reference.library.scaffold.ListScaffold @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListViewModel.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListViewModel.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListViewModel.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/list/GroupListViewModel.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupHeaderRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupHeaderRenderer.kt similarity index 97% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupHeaderRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupHeaderRenderer.kt index 1ad51dd..b0de758 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupHeaderRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupHeaderRenderer.kt @@ -40,9 +40,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.GroupHeaderConfig import dev.ohs.player.generated.state.GroupHeaderState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.Chip +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class GroupHeaderRenderer : ComponentRenderer<GroupHeaderState, GroupHeaderConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileRegistrations.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileRegistrations.kt similarity index 89% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileRegistrations.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileRegistrations.kt index d41c11e..96fc3c0 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileRegistrations.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileRegistrations.kt @@ -23,10 +23,10 @@ import dev.ohs.player.generated.config.SectionCardConfig import dev.ohs.player.generated.state.GroupHeaderState import dev.ohs.player.generated.state.GroupMemberState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.registerComponent -import dev.ohs.player.library.registry.registerLayout import dev.ohs.player.reference.app.feature.component.common.SectionCardLayoutRenderer +import dev.ohs.player.reference.library.registry.ViewRegistry +import dev.ohs.player.reference.library.registry.registerComponent +import dev.ohs.player.reference.library.registry.registerLayout fun ViewRegistry.registerGroupProfile() { registerComponent<GroupHeaderState, GroupHeaderConfig>( diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileScreen.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileScreen.kt similarity index 94% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileScreen.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileScreen.kt index ab432c7..91d63af 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileScreen.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileScreen.kt @@ -45,10 +45,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel import dev.ohs.player.generated.state.GroupHeaderState import dev.ohs.player.generated.state.GroupMemberState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.registry.LocalViewRegistry -import dev.ohs.player.library.registry.componentRenderer -import dev.ohs.player.library.registry.layoutRenderer -import dev.ohs.player.library.renderer.RenderOptions +import dev.ohs.player.reference.library.registry.LocalViewRegistry +import dev.ohs.player.reference.library.registry.componentRenderer +import dev.ohs.player.reference.library.registry.layoutRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileViewModel.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileViewModel.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileViewModel.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/GroupProfileViewModel.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/MemberItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/MemberItemRenderer.kt similarity index 87% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/MemberItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/MemberItemRenderer.kt index 3b992f5..a4d9e63 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/MemberItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/group/profile/MemberItemRenderer.kt @@ -38,17 +38,17 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.MemberItemConfig import dev.ohs.player.generated.state.GroupMemberState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.Chip import dev.ohs.player.reference.app.feature.patient.list.calculateAge -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.Res -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.relationship_child -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.relationship_guardian -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.relationship_non_relative -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.relationship_other_relative -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.relationship_parent -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.relationship_spouse +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions +import ohs_player_reference_client_app.reference_app.generated.resources.Res +import ohs_player_reference_client_app.reference_app.generated.resources.relationship_child +import ohs_player_reference_client_app.reference_app.generated.resources.relationship_guardian +import ohs_player_reference_client_app.reference_app.generated.resources.relationship_non_relative +import ohs_player_reference_client_app.reference_app.generated.resources.relationship_other_relative +import ohs_player_reference_client_app.reference_app.generated.resources.relationship_parent +import ohs_player_reference_client_app.reference_app.generated.resources.relationship_spouse import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCard.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCard.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCard.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCard.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardRenderer.kt similarity index 89% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardRenderer.kt index 856940e..b94dd55 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardRenderer.kt @@ -18,8 +18,8 @@ package dev.ohs.player.reference.app.feature.patient.list import androidx.compose.runtime.Composable import dev.ohs.player.generated.config.PatientCardConfig import dev.ohs.player.generated.state.PatientSummaryState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class PatientCardRenderer : ComponentRenderer<PatientSummaryState, PatientCardConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt similarity index 79% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt index 74e6fc0..170d1dc 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListRegistrations.kt @@ -20,12 +20,12 @@ import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.PatientCardConfig import dev.ohs.player.generated.state.PatientSummaryState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.layout.GridListRenderer -import dev.ohs.player.library.layout.HorizontalListRenderer -import dev.ohs.player.library.layout.VerticalListRenderer -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.registerComponent -import dev.ohs.player.library.registry.registerLayout +import dev.ohs.player.reference.library.layout.GridListRenderer +import dev.ohs.player.reference.library.layout.HorizontalListRenderer +import dev.ohs.player.reference.library.layout.VerticalListRenderer +import dev.ohs.player.reference.library.registry.ViewRegistry +import dev.ohs.player.reference.library.registry.registerComponent +import dev.ohs.player.reference.library.registry.registerLayout fun ViewRegistry.registerPatientList() { registerComponent<PatientSummaryState, PatientCardConfig>( diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt index af0e696..14e3e1a 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreen.kt @@ -31,8 +31,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import dev.ohs.player.generated.state.PatientSummaryState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.layout.VerticalListRenderer -import dev.ohs.player.library.scaffold.ListScaffold +import dev.ohs.player.reference.library.layout.VerticalListRenderer +import dev.ohs.player.reference.library.scaffold.ListScaffold @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListViewModel.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListViewModel.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListViewModel.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListViewModel.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyItemRenderer.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyItemRenderer.kt index 9650dfe..31e3bfe 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyItemRenderer.kt @@ -20,10 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import dev.ohs.player.generated.config.AllergyItemConfig import dev.ohs.player.generated.state.PatientAllergyState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.StatusChipData import dev.ohs.player.reference.app.feature.component.common.StatusRow +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class AllergyItemRenderer : ComponentRenderer<PatientAllergyState, AllergyItemConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyReactionItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyReactionItemRenderer.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyReactionItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyReactionItemRenderer.kt index 4a106c2..fc84540 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyReactionItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/AllergyReactionItemRenderer.kt @@ -27,9 +27,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.AllergyReactionItemConfig import dev.ohs.player.generated.state.AllergyReactionState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.Chip +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class AllergyReactionItemRenderer : ComponentRenderer<AllergyReactionState, AllergyReactionItemConfig> { diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ConditionItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ConditionItemRenderer.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ConditionItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ConditionItemRenderer.kt index 5419615..49d09bb 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ConditionItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ConditionItemRenderer.kt @@ -20,10 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import dev.ohs.player.generated.config.ConditionItemConfig import dev.ohs.player.generated.state.PatientConditionState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.StatusChipData import dev.ohs.player.reference.app.feature.component.common.StatusRow +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions private val AmberAccent = Color(0xFFE37400) diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ContactItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ContactItemRenderer.kt similarity index 96% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ContactItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ContactItemRenderer.kt index 1420948..b4d36a2 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ContactItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ContactItemRenderer.kt @@ -33,9 +33,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.ContactItemConfig import dev.ohs.player.generated.state.PatientContactState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.Chip +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class ContactItemRenderer : ComponentRenderer<PatientContactState, ContactItemConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ImmunizationItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ImmunizationItemRenderer.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ImmunizationItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ImmunizationItemRenderer.kt index 3e1af97..5f5c27c 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ImmunizationItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ImmunizationItemRenderer.kt @@ -20,10 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import dev.ohs.player.generated.config.ImmunizationItemConfig import dev.ohs.player.generated.state.PatientImmunizationState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.StatusChipData import dev.ohs.player.reference.app.feature.component.common.StatusRow +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class ImmunizationItemRenderer : ComponentRenderer<PatientImmunizationState, ImmunizationItemConfig> { diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/MedicationItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/MedicationItemRenderer.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/MedicationItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/MedicationItemRenderer.kt index 9a557fb..9d0e066 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/MedicationItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/MedicationItemRenderer.kt @@ -20,10 +20,10 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import dev.ohs.player.generated.config.MedicationItemConfig import dev.ohs.player.generated.state.PatientMedicationState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.StatusChipData import dev.ohs.player.reference.app.feature.component.common.StatusRow +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class MedicationItemRenderer : ComponentRenderer<PatientMedicationState, MedicationItemConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientHeaderRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientHeaderRenderer.kt similarity index 97% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientHeaderRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientHeaderRenderer.kt index ee13658..40da960 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientHeaderRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientHeaderRenderer.kt @@ -38,10 +38,10 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.PatientHeaderConfig import dev.ohs.player.generated.state.PatientSummaryState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions import dev.ohs.player.reference.app.feature.component.common.StatusChip import dev.ohs.player.reference.app.feature.patient.list.calculateAge +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class PatientHeaderRenderer : ComponentRenderer<PatientSummaryState, PatientHeaderConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileRegistrations.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileRegistrations.kt similarity index 96% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileRegistrations.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileRegistrations.kt index 4a406d7..7a2e034 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileRegistrations.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileRegistrations.kt @@ -40,10 +40,10 @@ import dev.ohs.player.generated.state.PatientMedicationState import dev.ohs.player.generated.state.PatientSummaryState import dev.ohs.player.generated.state.PatientTelecomState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.registry.ViewRegistry -import dev.ohs.player.library.registry.registerComponent -import dev.ohs.player.library.registry.registerLayout import dev.ohs.player.reference.app.feature.component.common.SectionCardLayoutRenderer +import dev.ohs.player.reference.library.registry.ViewRegistry +import dev.ohs.player.reference.library.registry.registerComponent +import dev.ohs.player.reference.library.registry.registerLayout fun ViewRegistry.registerPatientProfile() { registerComponent<PatientSummaryState, PatientHeaderConfig>( diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreen.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreen.kt similarity index 96% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreen.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreen.kt index 8f9d511..59b4def 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreen.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreen.kt @@ -54,10 +54,10 @@ import dev.ohs.player.generated.state.PatientMedicationState import dev.ohs.player.generated.state.PatientSummaryState import dev.ohs.player.generated.state.PatientTelecomState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.registry.LocalViewRegistry -import dev.ohs.player.library.registry.componentRenderer -import dev.ohs.player.library.registry.layoutRenderer -import dev.ohs.player.library.renderer.RenderOptions +import dev.ohs.player.reference.library.registry.LocalViewRegistry +import dev.ohs.player.reference.library.registry.componentRenderer +import dev.ohs.player.reference.library.registry.layoutRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileViewModel.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileViewModel.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileViewModel.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileViewModel.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ProfileUiState.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ProfileUiState.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ProfileUiState.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/ProfileUiState.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/TelecomItemRenderer.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/TelecomItemRenderer.kt similarity index 95% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/TelecomItemRenderer.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/TelecomItemRenderer.kt index 80e24bd..8f8ee02 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/TelecomItemRenderer.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/patient/profile/TelecomItemRenderer.kt @@ -31,8 +31,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import dev.ohs.player.generated.config.TelecomItemConfig import dev.ohs.player.generated.state.PatientTelecomState -import dev.ohs.player.library.renderer.ComponentRenderer -import dev.ohs.player.library.renderer.RenderOptions +import dev.ohs.player.reference.library.renderer.ComponentRenderer +import dev.ohs.player.reference.library.renderer.RenderOptions class TelecomItemRenderer : ComponentRenderer<PatientTelecomState, TelecomItemConfig> { @Composable diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostScreen.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostScreen.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostScreen.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostScreen.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostViewModel.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostViewModel.kt similarity index 100% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostViewModel.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireHostViewModel.kt diff --git a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireService.kt b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireService.kt similarity index 99% rename from ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireService.kt rename to reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireService.kt index 160f828..4c5187c 100644 --- a/ohs-player-reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireService.kt +++ b/reference-app/src/commonMain/kotlin/dev/ohs/player/reference/app/feature/questionnaire/QuestionnaireService.kt @@ -37,7 +37,7 @@ import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import ohsplayerreferenceclientapp.ohs_player_reference_app.generated.resources.Res +import ohs_player_reference_client_app.reference_app.generated.resources.Res import org.jetbrains.compose.resources.ExperimentalResourceApi /** Caller-supplied identifiers describing why/for-whom a questionnaire was launched. */ diff --git a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/AppViewRegistryTest.kt b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/AppViewRegistryTest.kt similarity index 87% rename from ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/AppViewRegistryTest.kt rename to reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/AppViewRegistryTest.kt index 641896a..07436a8 100644 --- a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/AppViewRegistryTest.kt +++ b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/AppViewRegistryTest.kt @@ -23,11 +23,11 @@ import dev.ohs.player.generated.state.PatientImmunizationState import dev.ohs.player.generated.state.PatientMedicationState import dev.ohs.player.generated.state.PatientSummaryState import dev.ohs.player.generated.viewtype.ViewTypeCS -import dev.ohs.player.library.layout.GridListRenderer -import dev.ohs.player.library.layout.HorizontalListRenderer -import dev.ohs.player.library.layout.VerticalListRenderer -import dev.ohs.player.library.registry.componentRenderer -import dev.ohs.player.library.registry.layoutRenderer +import dev.ohs.player.reference.library.layout.GridListRenderer +import dev.ohs.player.reference.library.layout.HorizontalListRenderer +import dev.ohs.player.reference.library.layout.VerticalListRenderer +import dev.ohs.player.reference.library.registry.componentRenderer +import dev.ohs.player.reference.library.registry.layoutRenderer import kotlin.test.Test class AppViewRegistryTest { diff --git a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepositoryTest.kt b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepositoryTest.kt similarity index 100% rename from ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepositoryTest.kt rename to reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/GroupRepositoryTest.kt diff --git a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepositoryTest.kt b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepositoryTest.kt similarity index 100% rename from ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepositoryTest.kt rename to reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/data/repository/InMemoryFhirRepositoryTest.kt diff --git a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardTest.kt b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardTest.kt similarity index 100% rename from ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardTest.kt rename to reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientCardTest.kt diff --git a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreenTest.kt b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreenTest.kt similarity index 96% rename from ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreenTest.kt rename to reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreenTest.kt index f1c1421..be01f98 100644 --- a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreenTest.kt +++ b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/list/PatientListScreenTest.kt @@ -22,8 +22,8 @@ import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.runComposeUiTest -import dev.ohs.player.library.registry.LocalViewRegistry import dev.ohs.player.reference.app.buildAppViewRegistry +import dev.ohs.player.reference.library.registry.LocalViewRegistry import kotlin.test.Test import kotlin.test.assertEquals diff --git a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreenTest.kt b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreenTest.kt similarity index 96% rename from ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreenTest.kt rename to reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreenTest.kt index 27b7622..59b4434 100644 --- a/ohs-player-reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreenTest.kt +++ b/reference-app/src/commonTest/kotlin/dev/ohs/player/reference/app/feature/patient/profile/PatientProfileScreenTest.kt @@ -23,8 +23,8 @@ import androidx.compose.ui.test.hasText import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.performScrollToNode import androidx.compose.ui.test.runComposeUiTest -import dev.ohs.player.library.registry.LocalViewRegistry import dev.ohs.player.reference.app.buildAppViewRegistry +import dev.ohs.player.reference.library.registry.LocalViewRegistry import kotlin.test.Test import kotlin.test.assertTrue diff --git a/ohs-player-reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/MainViewController.kt b/reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/MainViewController.kt similarity index 100% rename from ohs-player-reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/MainViewController.kt rename to reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/MainViewController.kt diff --git a/ohs-player-reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.ios.kt b/reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.ios.kt similarity index 100% rename from ohs-player-reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.ios.kt rename to reference-app/src/iosMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.ios.kt diff --git a/ohs-player-reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.jvm.kt b/reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.jvm.kt similarity index 100% rename from ohs-player-reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.jvm.kt rename to reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.jvm.kt diff --git a/ohs-player-reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/main.kt b/reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/main.kt similarity index 100% rename from ohs-player-reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/main.kt rename to reference-app/src/jvmMain/kotlin/dev/ohs/player/reference/app/main.kt diff --git a/ohs-player-reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.web.kt b/reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.web.kt similarity index 100% rename from ohs-player-reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.web.kt rename to reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/data/repository/PlatformRepositorySnapshotStore.web.kt diff --git a/ohs-player-reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/main.kt b/reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/main.kt similarity index 100% rename from ohs-player-reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/main.kt rename to reference-app/src/webMain/kotlin/dev/ohs/player/reference/app/main.kt diff --git a/ohs-player-reference-app/src/webMain/resources/index.html b/reference-app/src/webMain/resources/index.html similarity index 91% rename from ohs-player-reference-app/src/webMain/resources/index.html rename to reference-app/src/webMain/resources/index.html index 2042101..6d13c8f 100644 --- a/ohs-player-reference-app/src/webMain/resources/index.html +++ b/reference-app/src/webMain/resources/index.html @@ -15,6 +15,6 @@ repeatCount="indefinite"/> </circle> </svg> -<script type="application/javascript" src="ohs-player-reference-app.js"></script> +<script type="application/javascript" src="reference-app.js"></script> </body> </html> \ No newline at end of file diff --git a/ohs-player-reference-app/src/webMain/resources/styles.css b/reference-app/src/webMain/resources/styles.css similarity index 100% rename from ohs-player-reference-app/src/webMain/resources/styles.css rename to reference-app/src/webMain/resources/styles.css diff --git a/settings.gradle.kts b/settings.gradle.kts index ca1e0db..1fc8522 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -rootProject.name = "OhsPlayerReferenceClientApp" +rootProject.name = "ohs-player-reference-client-app" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") @@ -52,6 +52,4 @@ dependencyResolutionManagement { plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } -include(":ohs-player-reference-app") - -include(":ohs-player-library") +include(":reference-app")