diff --git a/.github/actions/build-core/action.yml b/.github/actions/build-core/action.yml deleted file mode 100644 index c1862d726..000000000 --- a/.github/actions/build-core/action.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Build mobile core' -description: 'Builds the mobile core library' -runs: - using: "composite" - steps: - - name: Get mobile core version - id: version - run: | - echo VERSION=`git rev-parse HEAD` >> $GITHUB_OUTPUT - working-directory: mobilecore-src - shell: bash - - name: Restore from cache - id: corecache - uses: actions/cache@v3 - with: - path: | - mobilecore/mobilecore.aar - key: mobilecore-aar-${{ steps.version.outputs.VERSION }}-${{ hashfiles('mobilecore/build.gradle') }} - - name: Setup go - if: steps.corecache.outputs.cache-hit != 'true' - uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f - with: - go-version: '1.20.6' - - name: Build mobile core - if: steps.corecache.outputs.cache-hit != 'true' - run: | - if [ `uname` == "Darwin" ]; then - export ARCH="mac" - else - export ARCH="linux" - fi - # Install NDK because it's too old on the Github runner image used - wget -q https://dl.google.com/android/repository/commandlinetools-$ARCH-8512546_latest.zip - unzip commandlinetools-$ARCH-8512546_latest.zip - export SDK_ROOT=$PWD/cmdline-tools - bash -c "yes || true" | cmdline-tools/bin/sdkmanager --licenses --sdk_root=$SDK_ROOT - cmdline-tools/bin/sdkmanager 'ndk;21.4.7075529' --sdk_root=$SDK_ROOT - export ANDROID_NDK_HOME=$SDK_ROOT/ndk/21.4.7075529 - ./gradlew mobilecore:buildCore - shell: bash diff --git a/.github/actions/build-web-pdf-tools/action.yml b/.github/actions/build-web-pdf-tools/action.yml deleted file mode 100644 index 67dac025b..000000000 --- a/.github/actions/build-web-pdf-tools/action.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: 'Build web pdf tools' -description: 'Builds the web pdf tools' -inputs: - web_pdf_tools_commit_hash: - type: string - required: true - node_version: - type: string - required: false - default: '16' - temp_path: - type: string - required: false - default: 'tmp-web-pdf-tools' - destination_path: - type: string - required: false - default: 'holder/src/main/res/raw/web_pdf_tools.js' - -runs: - using: "composite" - steps: - - name: Checkout web-pdf-tools - shell: bash - run: | - set -eux - git clone https://github.com/minvws/nl-covid19-coronacheck-web-pdf-tools.git "${{ inputs.temp_path }}" - cd "${{ inputs.temp_path }}" - git checkout ${{ inputs.web_pdf_tools_commit_hash }} - - - name: Restore web-pdf-tools.js from cache - id: cachestep - uses: actions/cache@v3 - with: - path: | - ${{ inputs.destination_path }} - key: web-pdf-tools-js-${{ inputs.web_pdf_tools_commit_hash }}-${{ inputs.node_version }}-v1 - - - name: "Setup node.js" - if: steps.cachestep.outputs.cache-hit != 'true' - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # pin@v3 - with: - node-version: ${{ inputs.node_version }} - cache: npm - cache-dependency-path: '${{ inputs.temp_path }}/package-lock.json' - - - name: Compile web-pdf-tools.js - if: steps.cachestep.outputs.cache-hit != 'true' - shell: bash - run: | - set -eux - - pushd "${{ inputs.temp_path }}" - sed -i.bak 's/output: { file: pkg.main },/output: { file: pkg.main, format: "iife", name: "pdfTools" },/g' rollup.config.js - npm ci - npm run build - popd - - rm -f "${{ inputs.destination_path }}" - cp "${{ inputs.temp_path }}/dist/index.js" "${{ inputs.destination_path }}" - rm -rf "${{ inputs.temp_path }}" - - - name: Archive web-pdf-tools.js - uses: actions/upload-artifact@v3 - with: - name: web_pdf_tools.js - path: | - ${{ inputs.destination_path }} - retention-days: 2 diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 13cffa95f..bae906b45 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -21,5 +21,3 @@ runs: with: # Cache results from main and release branches only per docs cache-read-only: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/release' }} - - name: Build mobile core - uses: ./.github/actions/build-core diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5f282e241..cf7035b9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,9 +6,6 @@ on: - 'release/*' pull_request: -env: - WEB_PDF_TOOLS_COMMIT_HASH: 530e22f23d98dc41c1611b1ecfb18719e5ab2570 # v4.1.6 - concurrency: group: build-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -20,17 +17,10 @@ jobs: - uses: actions/checkout@v3 - id: setup uses: ./.github/actions/setup - - name: Archive mobile core dependency - uses: actions/upload-artifact@v3 - with: - name: mobilecore - path: | - mobilecore/mobilecore.aar - retention-days: 2 - name: Spotless run: ./gradlew spotlessCheck - name: Test - run: ./gradlew testAccDebugUnitTest verifier:testAccDebugUnitTest + run: ./gradlew testAccDebugUnitTest - name: Emulator Test if: "!contains(github.event.pull_request.labels.*.name, 'skip-screenshot-tests')" uses: reactivecircus/android-emulator-runner@v2 @@ -45,7 +35,6 @@ jobs: name: test-results path: | holder/build/reports - verifier/build/reports retention-days: 2 distribute: @@ -56,8 +45,6 @@ jobs: KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} - GENERATE_FDROID_BUILDS: ${{ contains(github.event.pull_request.labels.*.name, 'generate-fdroid-builds') }} - GENERATE_VERIFIER_BUILDS: ${{ contains(github.event.pull_request.labels.*.name, 'generate-verifier-builds') }} KEYSTORE_FILE: ${{ secrets.KEYSTORE_FILE }} runs-on: ubuntu-latest needs: [ "build-and-test" ] @@ -72,18 +59,11 @@ jobs: run: | version=$(( $GITHUB_RUN_NUMBER )) echo VERSION_NUMBER=$version >> $GITHUB_ENV - - name: Build pdf tools - uses: ./.github/actions/build-web-pdf-tools - with: - web_pdf_tools_commit_hash: ${{ env.WEB_PDF_TOOLS_COMMIT_HASH }} - name: Build run: ./gradlew :holder:assemAccRelease :holder:assemProdRelease :holder:bundleProdRelease - name: Build fdroid if: ${{ env.GENERATE_FDROID_BUILDS == 'true' }} run: ./gradlew assemFdroidAccRelease assemFdroidProdRelease - - name: Build verifier - if: ${{ env.GENERATE_VERIFIER_BUILDS == 'true' }} - run: ./gradlew :verifier:assemAccRelease :verifier:assemProdRelease :verifier:bundleProdRelease - name: Clean up key store run: rm keystore.jks - name: Archive apks @@ -92,7 +72,6 @@ jobs: name: apks path: | holder/build/outputs/apk - verifier/build/outputs/apk retention-days: 5 - name: Archive bundle uses: actions/upload-artifact@v3 @@ -100,7 +79,6 @@ jobs: name: bundle path: | holder/build/outputs/bundle - verifier/build/outputs/bundle retention-days: 5 - name: Download all workflow run artifacts @@ -116,44 +94,23 @@ jobs: - name: Distribute holder acc variant run: | - firebase appdistribution:distribute `ls apks/holder/build/outputs/apk/acc/release/holder-*.apk` \ + firebase appdistribution:distribute `ls apks/acc/release/holder-*.apk` \ --app 1:168257592968:android:5df6b2057b90a30826493d \ --groups testers - name: Distribute holder fdroid acc variant if: ${{ env.GENERATE_FDROID_BUILDS == 'true' }} run: | - firebase appdistribution:distribute `ls apks/holder/build/outputs/apk/fdroidAcc/release/holder-*.apk` \ + firebase appdistribution:distribute `ls apks/fdroidAcc/release/holder-*.apk` \ --app 1:168257592968:android:28c578809115867926493d \ --groups testers - name: Distribute holder prod variant run: | - firebase appdistribution:distribute `ls apks/holder/build/outputs/apk/prod/release/holder-*.apk` \ + firebase appdistribution:distribute `ls apks/prod/release/holder-*.apk` \ --app 1:168257592968:android:aaa5afb416536fdb26493d \ --groups testers - - name: Distribute verifier acc variant - if: ${{ env.GENERATE_VERIFIER_BUILDS == 'true' }} - run: | - firebase appdistribution:distribute `ls apks/verifier/build/outputs/apk/acc/release/verifier-*.apk` \ - --app 1:168257592968:android:e9d445c4115a5c9d26493d \ - --groups testers - - - name: Distribute verifier fdroid acc variant - if: ${{ env.GENERATE_FDROID_BUILDS == 'true' }} - run: | - firebase appdistribution:distribute `ls apks/verifier/build/outputs/apk/fdroidAcc/release/verifier-*.apk` \ - --app 1:168257592968:android:a5a6c5d5af1c18c726493d \ - --groups testers - - - name: Distribute verifier prod variant - if: ${{ env.GENERATE_VERIFIER_BUILDS == 'true' }} - run: | - firebase appdistribution:distribute `ls apks/verifier/build/outputs/apk/prod/release/verifier-*.apk` \ - --app 1:168257592968:android:79363a1282863aac26493d \ - --groups testers - - name: Slack Notification uses: rtCamp/action-slack-notify@v2 diff --git a/.github/workflows/ui_tests_on_demand.yml b/.github/workflows/ui_tests_on_demand.yml deleted file mode 100644 index 22eab174c..000000000 --- a/.github/workflows/ui_tests_on_demand.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: UI Tests on demand - -on: - workflow_dispatch: - -jobs: - build-and-test: - runs-on: ubuntu-latest - steps: - - name: Set version number - run: | - version=$(( $GITHUB_RUN_NUMBER + 4000 )) - echo VERSION_NUMBER=$version >> $GITHUB_ENV - - uses: actions/checkout@v3 - - id: setup - uses: ./.github/actions/setup - - name: Assemble - run: ./gradlew holder:assemAccDebug holder:assemAccDebugAndroidTest diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index fad35aac3..000000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "nl-covid19-coronacheck-mobile-core"] - path = mobilecore-src - url = https://github.com/minvws/nl-covid19-coronacheck-mobile-core -[submodule "mobilecore-src"] - path = mobilecore-src - url = https://github.com/minvws/nl-covid19-coronacheck-mobile-core.git -[submodule "rdo"] - path = rdo - url = https://github.com/minvws/nl-rdo-app-android-modules.git diff --git a/README.md b/README.md index 69edd4975..378e30478 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ See [minvws](https://github.com/minvws)/**[nl-covid19-coronacheck-app-coordinati ## About the Apps -The codebase builds two different app products: +The codebase was building two different app products: ### [CoronaCheck](https://play.google.com/store/apps/details?id=nl.rijksoverheid.ctr.holder) -**CoronaCheck** (referred to internally as the *Holder* app) is the official app of the Netherlands for showing coronavirus entry passes. With this digital tool, you can create a certificate with QR code of your negative test, vaccination, or recovery. This allows access to certain venues and activities abroad. Or at the border. +**CoronaCheck** (referred to internally as the *Holder* app) was the official app of the Netherlands for showing coronavirus entry passes. With this digital tool, you could create a certificate with QR code of your negative test, vaccination, or recovery. This allowed access to certain venues and activities abroad. Or at the border. ### [Scanner voor CoronaCheck](https://play.google.com/store/apps/details?id=nl.rijksoverheid.ctr.verifier) -**CoronaCheck Scanner** (referred to internally as the *Verifier* app) is the official scanner app of the Netherlands for coronavirus entry passes. With this digital tool, you can verify if visitors have a valid certificate of their negative test, vaccination, or recovery. You do this by scanning their QR code. This way, you can safely give access to your venue or activity. +**CoronaCheck Scanner** (referred to internally as the *Verifier* app) was the official scanner app of the Netherlands for coronavirus entry passes. With this digital tool, you could verify if visitors have a valid certificate of their negative test, vaccination, or recovery. You did this by scanning their QR code. This way, you could safely give access to your venue or activity. ### App Requirements @@ -34,115 +34,8 @@ The apps can run on devices that meet the following requirements. #### CoronaCheck -The app works like this: - -*First make sure you are vaccinated or have tested for coronavirus.* - -* With the CoronaCheck app you can create a certificate based on your vaccination or coronavirus test results. You do this by retrieving your details via DigiD or via the retrieval code you received if you got tested at a test location other than the GGD. - -* You can create a vaccination certificate from your vaccination, create a test certificate from your negative coronavirus test result, or create a recovery certificate from your positive coronavirus test result. - -* The QR code of your certificate may be checked at the entrance of venues and activities. And also at international borders. This is proof that you have been vaccinated, have had coronavirus or did not have coronavirus at the time of testing - -This is a general overview of the features that are available in the app: - -* **Onboarding**: When the app starts or the first time, the user is informed of the functionality of the app and views the privacy declaration. - -* **Dashboard**: an overview of the user's certificates. Depending on the active [disclosure policy](#disclosure-policies), there can a tab switcher to change between Domestic and International certificates, or else it's hidden and the user can only view International certificates. - -* **View QR codes**: View the QR code(s) for a selected certificate. - -* **Fuzzy-matching**: For when a user has multiple names in the app, which the backend no longer permits. Before the implementation of server-side fuzzy matching they existed together, but now there needs to be a way for the user to choose which name permutations (+ associated events) to keep, and which to discard. This feature also has an "onboarding" flow explaining the choice the user has to make. This feature is presented to the user as-needed, and is not otherwise accessible. - -* **Menu**: - - * **Add Certificate**: - - * Vaccination: can be retrieved via authentication with DigiD - * Positive Test: can be retrieved via authentication with DigiD - * Negative Test: can be added via authentication with DigiD or by entering a retrieval code from a third-party test provider. - - For those without a DigiD account, the user can request a certificate from the CoronaCheck Helpdesk or (if the user doesn't have a BSN) directly from the GGD. - - * **Scan to add certificate**: the user can import a paper copy of a various types of certificate by scanning it using the phone's camera. - - * **Add visitor pass**: for users who were vaccinated outside the EU and are visiting the Netherlands, they can obtain a "vaccine approval" code and use it - together with a negative test - to create a visitor pass in the app. - - * **Frequently asked questions**: a webview - - * **About this app**: - - * Privacy statement: a webview - * Accessibility: a webview - * Colophon: a webview - * Stored data: shows the event data imported from GGD, RIVM, from commerical test providers, or scanned manually by the user. They can be deleted from here. - * Reset the app: wipes the app's encrypted database and user preferences, restoring the app to a "first-run" state. The user has to manually start the app afterwards. - - To aid in development/testing of the app, there are some extra menu items when compiling for Test/Acceptance: - - * ***Open Scanner**: open the CoronaCheck Scanner app via a universal-link* - - * ***A list of disclosure policies** (1G, 3G, etc) which can be manually activated to override the remote configuration disclosure policy.* - -##### Disclosure policies - -Depending on the active disclosure policy (which is set by the remote config), the Dutch certificates are handled differently in the app: - -* **0G**: no Dutch certificates displayed, only international certificates. - -* **1G** access: the user can only use a negative test to enter places which require a coronavirus pass. The app only displays the QRs of negative test certificates. -* **3G** access: the user can enter anywhere (that requires a coronavirus pass) with a proof of vaccination, recovery, or a negative test. So all certificates are available. -* **1G + 3G**: some venues are operating with 1G rules, others with 3G. Thus the app displays separate certificates for 3G and for 1G access. - -#### CoronaCheck Scanner - -The app works like this: - -* With CoronaCheck Scanner you can scan visitors' QR codes with your smartphone camera. Visitors can show their QR code in the CoronaCheck app, or on paper. Tourists can use an app or a printed QR code from their own country. -* A number of details appear on your screen, allowing you to verify - using their proof of - identity - if the QR code really belongs to this visitor. -* If the QR code is valid, and the details are the same as on the proof of identity, a check - mark will appear on the screen and you can give access to the visitor. - -This is how the app uses personal details: - -* Visitors' details may only be used to verify the coronavirus entry pass -* Visitors' details are not centrally stored anywhere -* Visitors' location details are neither used nor saved - -This is a general overview of the features that are available in the app: - -* **Onboarding**: When the app starts or the first time, the user is informed of the purpose of the app and accepts the Acceptable Use Policy. -* **Landing screen**: explains briefly what the app does. -* **"About Scanning" onboarding**: informs the user how to scan & verify a certificate. -* **New Policy screen**: explains the current disclosure policy -* **Scan QR code**: camera view which allows the user to scan a QR code. The device's flashlight can be toggled. -* **Result screen**: shows a green checkmark or a red cross depending on the result of the scan. - -* **Menu**: - * **How it works**: replay the "About Scanning" onboarding. - * **Support**: opens a webview. - * **Scan Setting**: allows the user to choose the active verification policy (feature is only available when multiple verification policies are permitted by the remote config). - * **About this app:** - * Acceptable use policy: opens a webview - * Accessibility: a webview - * Colophon: a webview - * Reset the app: wipes the database, user preferences and keychain entries, restoring the app to a "first-run" state. This is only available when compiling for Test/Acceptance. - * Scan setting log: keeps track of the type of access used during scanning. A civil enforcement officer may request access to this log. For privacy reasons, only scan settings used in the last 60 minutes are saved on this phone. No personal information is saved. (feature is only available when multiple verification policies are permitted by the remote config). - -##### Verification Policies - -Related to Disclosure Policies above, but for the Scanner. The verification policy set (by the remote configuration) determines which types of QR codes can be scanned. - -* **1G** access: the scanner can only approve QR codes representing valid negative tests. -* **3G** access: the scanner can approve QR codes representing valid vaccination, recovery, and negative tests. -* **1G + 3G**: some venues are operating with 1G rules, others with 3G. The scanner can be set to 1G or 3G mode, depending on the venue where it's intended to be used. - -### Remote Configuration - -Feature flags / configuration values are loaded dynamically from the backend ([CoronaCheck](https://holder-api.coronacheck.nl/v8/holder/config), [CoronaCheck Scanner](https://holder-api.coronacheck.nl/v8/verifier/config)). The `payload` value is base64 encoded. - -*Note: the API is versioned: /v8/, /v9/ etc. Check the [holder build.gradle](/holder/build.gradle) and the [scanner build gradle](/verifier/build.gradle) for the currently used version.* +The app does not work anymore, it just opens informing the user about the current deactivated status, with a link to a website offering the last available information for the corona passes. +To check previous features of the app, check out one of the previous releases/tags. ### Third party dependencies @@ -178,48 +71,9 @@ Dependencies management is handled with [Gradle's Version Catalog](https://docs. * [firebase-action](https://github.com/littlerobots/firebase-action) for sending builds to firebase for internal testing, setting up credentials via a service account and disabling firebase analytics -### In house dependencies - -#### MobileCore - -The Android and iOS apps share a [core library](https://github.com/minvws/nl-covid19-coronacheck-mobile-core), written in Go, which is responsible for producing the QR-code image, and for validating scanned QR-codes. Build its .aar according to the [instructions](/mobilecore). - -The library is also built by the [project's GitHub actions workflow](/.github/workflows/ci.yml), to be transparent it is used uncompromised - -#### Web PDF Tools - -To export the certificates to PDF, we reuse the [web pdf tools library](https://github.com/minvws/nl-covid19-coronacheck-web-pdf-tools) from the CoronaCheck website. - -`scripts/build_web_pdf_tools.sh` builds, compacts and saves that library. - -#### RDO modules - -* [rdo modules](https://github.com/minvws/nl-rdo-app-android-modules): modules that were extracted from CoronaCheck for reuse. - -## Development - -### Getting started - -Like mentioned above, the app is dependent on a library written in Go. An artifact named `mobilecore.aar` must be present [in this path](/mobilecore) in order for the project to compile. -The project is also dependent to [rdo modules](https://github.com/minvws/nl-rdo-app-android-modules) in order to compile. -All of them are added as git submodules. - -Steps to add them: - -1. `git submodule init` -2. `git submodule update` -3. Install the latest [go](https://go.dev/doc/install) -4. `./gradlew :mobilecore:buildCore` - -Moreover, again like mentioned before, the project is dependent on javascript file to generate PDFs. Execute the `build_web_pdf_tools.sh` in scripts -folder to build it from its repo. Execute the following command from the root of the project: -`sh scripts/build_web_pdf_tools.sh` -(npm installed is required) - ### Project structure CoronaCheck can be built using the `holder` module. -Scanner can be built using the `verifier` module. Both come in 5 flavours: @@ -233,44 +87,18 @@ Note that test and acceptance environments are accessible only inside VWS truste Other project modules used by both apps: -* [api](/api) http client setup * [appconfig](/appconfig) remote configuration management * [design](/design) common styles and components -* [introduction](/introduction) onboarding and privacy consent components -* [qrscanner](/qrscanner) camera scanning qr codes * [shared](/shared) common models and utility classes Finally, there are couple of bash and python [scripts](/scripts): -* The original development team used [Lokalise](https://lokalise.com/) to manage the dutch and english copies of the apps. A [download script](/scripts/download_copy.py) is used to sync the local copies to the remote ones in Lokalise and an [upload script](/scripts/upload_copy.py) can be used to sync the remote ones to the local ones. To use them add an environment variable named `LOKALISE_API_KEY` with value a [lokalise api token](https://docs.lokalise.com/en/articles/1929556-api-tokens). -* [Update the current screenshots](/scripts/record_screenshots.sh) used in the CoronaCheck screenshot testing [folder](/holder/screenshots). * [Sync the public repo to the private repo](/scripts/sync_public_repo.sh) used by the internal development team. -## Release Procedure - -The release process is the same for CoronaCheck and for CoronaCheck Scanner. - -`apk` and `aab` artifacts are generated by GitHub actions every time `main` or `release/*` branches have a new commit. - -We release test, acceptance and production-like builds internally to Firebase App Distribution. These are triggered whenever there is a commit made to the main branch (ie by merging a pull request). -To use firebase, a [firebase service account json key](https://firebase.google.com/docs/admin/setup#:~:text=To%20authenticate%20a%20service%20account,confirm%20by%20clicking%20Generate%20Key.) must be added in github's secrets, named `FIREBASE_SERVICE_ACCOUNT`. - -Initial releases (4.6, 4.7 etc) are released from artifacts generated by the main branch. Hotfix releases artifacts are generated by release branches (eg `release/4.7.1`) -Once the team is satisfied with the quality of the builds on Firebase, a production build can be sent to the play store. - -We perform a manual regression test on the build to make sure the production-ready binary performs as expected. - -Once the build is approved by Google, we release the approved build manually using a phased rollout to give us the opportunity to spot any crashes that might be detected, or bugs that might be reported. The rollout starts at 5% and goes to 10%, then 25%, then 50% and finally 100%. - -At this point a final tag should be made, with this format: `Holder-4.7.0` and `Verifier-3.0.2` - -Now that the release is completed, the private git repository should be "synced" with the public repository by running [this script](scripts/sync_public_repo.sh). It also pushes the releases tags. - ## Contribution process -The development team works on the repository in a private fork (for reasons of compliance with existing processes) and shares its work as often as possible. +The development team used to work on the repository in a private fork (for reasons of compliance with existing processes) and was sharing its work as often as possible. -If you plan to make non-trivial changes, we recommend to open an issue beforehand where we can discuss your planned changes. -This increases the chance that we might be able to use your contribution (or it avoids doing work if there are reasons why we wouldn't be able to use it). +No development is taking place anymore so contribution is not possible. -Note that all commits should be signed using a GPG key. +Note that all commits were signed using a GPG key. diff --git a/api/.gitignore b/api/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/api/build.gradle b/api/build.gradle deleted file mode 100644 index fa8bacb8c..000000000 --- a/api/build.gradle +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -plugins { - alias(libs.plugins.android.library) - alias(libs.plugins.kotlin.android) - alias(libs.plugins.ksp) - alias(libs.plugins.navigation.safeargs.kotlin) - alias(libs.plugins.kotlin.parcelize) -} - -android { - namespace 'nl.rijksoverheid.ctr.api' - flavorDimensions "default" - buildFeatures { - viewBinding true - } - - defaultConfig { - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - coreLibraryDesugaringEnabled true - } -} - -kotlin { - sourceSets { - main.kotlin.srcDirs += 'build/generated/ksp/main/kotlin' - test.kotlin.srcDirs += 'build/generated/ksp/test/kotlin' - } -} - -dependencies { - implementation(project(":shared")) - implementation(project(":modules:httpsecurity")) - - implementation libs.retrofit.moshi - implementation libs.moshi.kotlin - implementation libs.koin.android - implementation libs.timber - implementation(platform(libs.okhttp.bom)) - implementation libs.okhttp - implementation libs.okhttp.tls - implementation libs.okhttp.logging.interceptor - api libs.certificatetransparency.android - - ksp libs.moshi.codegen - - kspTest libs.moshi.codegen - testImplementation libs.junit - testImplementation libs.okhttp.mockWebServer - testImplementation libs.coroutines.android -} diff --git a/api/consumer-rules.pro b/api/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/api/proguard-rules.pro b/api/proguard-rules.pro deleted file mode 100644 index f1b424510..000000000 --- a/api/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/ApiModule.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/ApiModule.kt deleted file mode 100644 index 7709d120d..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/ApiModule.kt +++ /dev/null @@ -1,107 +0,0 @@ -package nl.rijksoverheid.ctr.api - -import com.appmattus.certificatetransparency.CTLogger -import com.appmattus.certificatetransparency.VerificationResult -import com.appmattus.certificatetransparency.certificateTransparencyTrustManager -import com.appmattus.certificatetransparency.loglist.LogListDataSourceFactory -import com.squareup.moshi.Moshi -import java.util.concurrent.TimeUnit -import javax.net.ssl.X509TrustManager -import nl.rijksoverheid.ctr.api.interceptors.CacheOverrideInterceptor -import nl.rijksoverheid.ctr.api.interceptors.SignedResponseInterceptor -import nl.rijksoverheid.ctr.api.json.Base64JsonAdapter -import nl.rijksoverheid.ctr.api.json.JsonObjectJsonAdapter -import nl.rijksoverheid.ctr.api.json.LocalDateJsonAdapter -import nl.rijksoverheid.ctr.api.json.OffsetDateTimeJsonAdapter -import nl.rijksoverheid.ctr.shared.models.Environment -import okhttp3.ConnectionSpec -import okhttp3.HttpUrl -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import okhttp3.tls.HandshakeCertificates -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import timber.log.Timber - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -fun apiModule( - baseUrl: HttpUrl, - signatureCertificateCnMatch: String, - coronaCheckApiChecks: Boolean, - testProviderApiChecks: Boolean -) = module { - single { - OkHttpClient.Builder() - .addNetworkInterceptor(CacheOverrideInterceptor()) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .followRedirects(false) - .apply { - if (BuildConfig.DEBUG) { - addInterceptor(HttpLoggingInterceptor { - Timber.tag("OkHttp").d(it) - }.setLevel(HttpLoggingInterceptor.Level.BODY)) - } - if (coronaCheckApiChecks) { - val handshakeCertificates = HandshakeCertificates.Builder() - .addPlatformTrustedCertificates() - .build() - sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - transparentTrustManager(handshakeCertificates.trustManager) - ) - } - if (!BuildConfig.DEBUG) { - connectionSpecs(listOf(ConnectionSpec.MODERN_TLS)) - } - } - .addInterceptor( - SignedResponseInterceptor( - signatureCertificateCnMatch = signatureCertificateCnMatch, - testProviderApiChecks = testProviderApiChecks, - isAcc = Environment.get(androidContext()) == Environment.Acc, - clock = get() - ) - ).build() - } - - single { - Retrofit.Builder() - .baseUrl(baseUrl) - .client(get()) - .addConverterFactory(MoshiConverterFactory.create(get())) - .build() - } - - single { - Moshi.Builder() - .add(Base64JsonAdapter()) - .add(JsonObjectJsonAdapter()) - .add(OffsetDateTimeJsonAdapter()) - .add(LocalDateJsonAdapter()) - } -} - -private fun transparentTrustManager(trustManager: X509TrustManager) = - certificateTransparencyTrustManager(trustManager) { - if (BuildConfig.DEBUG) { - setLogger(object : CTLogger { - override fun log(host: String, result: VerificationResult) { - Timber.tag("certificate transparency") - .d("host: $host, verification result: $result") - } - }) - } - - setLogListService(LogListDataSourceFactory.createLogListService( - baseUrl = "https://www.gstatic.com/ct/log_list/v3/" - )) - } diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/exceptions/CoronaCheckHttpException.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/exceptions/CoronaCheckHttpException.kt deleted file mode 100644 index ef282b66a..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/exceptions/CoronaCheckHttpException.kt +++ /dev/null @@ -1,10 +0,0 @@ -package nl.rijksoverheid.ctr.api.exceptions - -import nl.rijksoverheid.ctr.shared.models.CoronaCheckErrorResponse -import retrofit2.HttpException -import retrofit2.Response - -class CoronaCheckHttpException( - response: Response<*>, - val responseError: CoronaCheckErrorResponse -) : HttpException(response) diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/factory/NetworkRequestResultFactory.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/factory/NetworkRequestResultFactory.kt deleted file mode 100644 index 1706f2ad6..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/factory/NetworkRequestResultFactory.kt +++ /dev/null @@ -1,92 +0,0 @@ -package nl.rijksoverheid.ctr.api.factory - -import java.io.IOException -import java.net.ConnectException -import java.net.SocketTimeoutException -import java.net.UnknownHostException -import nl.rijksoverheid.ctr.shared.models.CoronaCheckErrorResponse -import nl.rijksoverheid.ctr.shared.models.MijnCnErrorResponse -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import nl.rijksoverheid.ctr.shared.models.Step -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil -import okhttp3.ResponseBody -import retrofit2.Converter -import retrofit2.HttpException - -/** - * This class should be used for every network request - */ -class NetworkRequestResultFactory( - private val errorResponseBodyConverter: Converter, - private val androidUtil: AndroidUtil, - private val mijnCnErrorResponseBodyConverter: Converter -) { - - private companion object { - const val BES_PROVIDER = "BES" - } - - suspend fun createResult( - step: Step, - provider: String? = null, - interceptHttpError: (suspend (e: HttpException) -> R?)? = null, - networkCall: suspend () -> R - ): NetworkRequestResult { - return try { - if (!androidUtil.isNetworkAvailable()) { - NetworkRequestResult.Failed.ClientNetworkError(step) - } else { - val response = networkCall.invoke() - NetworkRequestResult.Success(response) - } - } catch (httpException: HttpException) { - try { - // We intercept here if a HttpException is expected - val result = interceptHttpError?.invoke(httpException) - result?.let { - return NetworkRequestResult.Success(it) - } - - provider?.let { - // If this is a call to a provider we return a ProviderHttpError - return NetworkRequestResult.Failed.ProviderHttpError( - step, httpException, it - ) - } - - // Check if there is a error body - val errorBody = httpException.response()?.errorBody() - ?: return NetworkRequestResult.Failed.CoronaCheckHttpError(step, httpException, provider) - - // Check if the error body is a [CoronaCheckErrorResponse] - val errorResponse = if (provider == BES_PROVIDER) { - mijnCnErrorResponseBodyConverter.convert(errorBody)?.let { - CoronaCheckErrorResponse(it.model.error, it.model.code) - } - } else { - errorResponseBodyConverter.convert(errorBody) - } ?: return NetworkRequestResult.Failed.CoronaCheckHttpError( - step, httpException, provider - ) - - return NetworkRequestResult.Failed.CoronaCheckWithErrorResponseHttpError( - step, httpException, errorResponse, provider - ) - } catch (e: Exception) { - return NetworkRequestResult.Failed.CoronaCheckHttpError( - step, httpException, provider - ) - } - } catch (e: IOException) { - when { - e is SocketTimeoutException || e is UnknownHostException || e is ConnectException -> { - NetworkRequestResult.Failed.ServerNetworkError(step, e) - } - provider != null -> NetworkRequestResult.Failed.ProviderError(step, e, provider) - else -> NetworkRequestResult.Failed.Error(step, e) - } - } catch (e: Exception) { - NetworkRequestResult.Failed.Error(step, e) - } - } -} diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/interceptors/CacheOverrideInterceptor.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/interceptors/CacheOverrideInterceptor.kt deleted file mode 100644 index 1de9daee3..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/interceptors/CacheOverrideInterceptor.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.api.interceptors - -import okhttp3.Interceptor -import okhttp3.Response -import retrofit2.Invocation - -class CacheOverrideInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val invocation = chain.request().tag(Invocation::class.java) - val annotation = invocation?.method()?.getAnnotation(CacheOverride::class.java) - val response = chain.proceed(chain.request()) - return if (annotation != null) { - if (response.isSuccessful && response.cacheResponse == null) { - response.newBuilder() - .removeHeader("cache-control") - .removeHeader("pragma") - .addHeader("cache-control", annotation.cacheHeaderValue).build() - } else { - response - } - } else { - response - } - } -} - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.FUNCTION) -annotation class CacheOverride(val cacheHeaderValue: String) diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/interceptors/SignedResponseInterceptor.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/interceptors/SignedResponseInterceptor.kt deleted file mode 100644 index ed65dfb82..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/interceptors/SignedResponseInterceptor.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.api.interceptors - -import android.util.Base64 -import com.squareup.moshi.JsonClass -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import java.io.ByteArrayInputStream -import java.io.IOException -import java.time.Clock -import nl.rijksoverheid.ctr.api.json.Base64JsonAdapter -import nl.rijksoverheid.ctr.api.signing.certificates.PRIVATE_ROOT_CA -import nl.rijksoverheid.ctr.api.signing.certificates.ROOT_CA_G3 -import nl.rijksoverheid.ctr.api.signing.http.SignedRequest -import nl.rijksoverheid.rdo.modules.httpsecurity.cms.CMSSignatureValidatorBuilder -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import okio.Buffer -import retrofit2.Invocation - -private val responseAdapter by lazy { - Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .add(Base64JsonAdapter()) - .build() - .adapter(SignedResponse::class.java) -} - -class SignedResponseInterceptor( - signatureCertificateCnMatch: String, - private val testProviderApiChecks: Boolean, - private val isAcc: Boolean, - private val clock: Clock -) : Interceptor { - private val defaultValidator = CMSSignatureValidatorBuilder.build( - certificatesPem = listOf(PRIVATE_ROOT_CA), - cnMatchingString = signatureCertificateCnMatch, - clock = clock - ) - - override fun intercept(chain: Interceptor.Chain): Response { - val expectedSigningCertificate = chain.request().tag(SigningCertificate::class.java) - val wrapResponse = expectedSigningCertificate != null - - val response = chain.proceed(chain.request()) - - // if not marked with SignedRequest, return the response - chain.request() - .tag(Invocation::class.java) - ?.method() - ?.getAnnotation(SignedRequest::class.java) ?: return response - - val body = try { - response.peekBody(Long.MAX_VALUE).bytes() - } catch (exception: IOException) { - return response - } - - try { - val signedResponse = responseAdapter.fromJson(Buffer().apply { write(body) }) - - if (signedResponse?.payload == null || signedResponse.signature == null) { - return response.newBuilder().body("Empty signature".toResponseBody()) - .code(500) - .message("Expected response signature").build().also { response.close() } - } - - val validator = if (expectedSigningCertificate != null) { - val trustedCertificates = listOf(ROOT_CA_G3, PRIVATE_ROOT_CA) - CMSSignatureValidatorBuilder.build( - certificatesPem = trustedCertificates, - signingCertificateBytes = expectedSigningCertificate.certificateBytes, - clock = clock - ) - } else { - defaultValidator - } - - if (testProviderApiChecks) { - validator.validate( - signature = signedResponse.signature, - content = ByteArrayInputStream(signedResponse.payload) - ) - } - - return response.newBuilder() - .body( - (if (wrapResponse) wrapResponse( - body, - signedResponse.payload - ) else signedResponse.payload).toResponseBody("application/json".toMediaType()) - ) - .build().also { response.close() } - } catch (e: Exception) { - // When something is wrong in parsing a unsuccessful request, cascade down the - // request as usual (so that HttpExceptions get picked up for example) - return response.newBuilder().body(body.toResponseBody()) - .code(response.code).build().also { response.close() } - } - } - - private fun wrapResponse(signedBody: ByteArray, response: ByteArray): ByteArray { - val buffer = Buffer() - val writer = JsonWriter.of(buffer) - writer.beginObject() - writer.name("rawResponse") - writer.value(Base64.encodeToString(signedBody, Base64.NO_WRAP)) - writer.name("model") - writer.value(Buffer().apply { write(response) }) - writer.endObject() - writer.flush() - return buffer.readByteArray() - } -} - -@JsonClass(generateAdapter = true) -internal class SignedResponse( - val payload: ByteArray?, - val signature: ByteArray? -) - -/** - * Holder class for the signing certificate - */ -class SigningCertificate(val certificateBytes: List) diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/json/Base64JsonAdapter.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/json/Base64JsonAdapter.kt deleted file mode 100644 index 1623dbfdb..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/json/Base64JsonAdapter.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2020 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ -package nl.rijksoverheid.ctr.api.json - -import android.util.Base64 -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson - -class Base64JsonAdapter { - @FromJson - fun fromBase64(value: String): ByteArray = Base64.decode(value, Base64.DEFAULT) - - @ToJson - fun toBase64(value: ByteArray) = Base64.encodeToString(value, Base64.NO_WRAP) -} diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/json/JsonObjectJsonAdapter.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/json/JsonObjectJsonAdapter.kt deleted file mode 100644 index 3a6a73db5..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/json/JsonObjectJsonAdapter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package nl.rijksoverheid.ctr.api.json - -import com.squareup.moshi.FromJson -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson -import okio.Buffer -import org.json.JSONException -import org.json.JSONObject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class JsonObjectJsonAdapter { - @FromJson - fun fromJson(reader: JsonReader): JSONObject? { - // Here we're expecting the JSON object, it is processed as Map by Moshi - return (reader.readJsonValue() as? Map<*, *>)?.let { data -> - try { - JSONObject(data) - } catch (e: JSONException) { - throw IllegalStateException("Could not parse json") - } - } - } - - @ToJson - fun toJson(writer: JsonWriter, value: JSONObject?) { - value?.let { writer.value(Buffer().writeUtf8(value.toString())) } - } -} diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/json/LocalDateJsonAdapter.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/json/LocalDateJsonAdapter.kt deleted file mode 100644 index b3d2157b3..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/json/LocalDateJsonAdapter.kt +++ /dev/null @@ -1,22 +0,0 @@ -package nl.rijksoverheid.ctr.api.json - -import com.squareup.moshi.FromJson -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson -import java.time.LocalDate - -class LocalDateJsonAdapter : JsonAdapter() { - - @FromJson - override fun fromJson(reader: JsonReader): LocalDate? { - val date = reader.nextString() - return LocalDate.parse(date) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: LocalDate?) { - writer.value(value?.toString()) - } -} diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/json/OffsetDateTimeJsonAdapter.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/json/OffsetDateTimeJsonAdapter.kt deleted file mode 100644 index ae6963f40..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/json/OffsetDateTimeJsonAdapter.kt +++ /dev/null @@ -1,24 +0,0 @@ -package nl.rijksoverheid.ctr.api.json - -import com.squareup.moshi.FromJson -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonReader -import com.squareup.moshi.JsonWriter -import com.squareup.moshi.ToJson -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter - -class OffsetDateTimeJsonAdapter : JsonAdapter() { - - @FromJson - override fun fromJson(reader: JsonReader): OffsetDateTime? { - val date = reader.nextString() - return OffsetDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME) - } - - @ToJson - override fun toJson(writer: JsonWriter, value: OffsetDateTime?) { - val date: String? = value?.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) - writer.value(date) - } -} diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/signing/certificates/Certificates.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/signing/certificates/Certificates.kt deleted file mode 100644 index 070db7ac4..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/signing/certificates/Certificates.kt +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.api.signing.certificates - -const val ROOT_CA_G3 = """-----BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX -DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP -cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW -IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX -xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy -KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR -9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az -5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 -6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 -Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP -bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt -BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt -XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd -INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp -LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 -Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp -gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh -/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw -0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A -fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq -4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR -1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ -QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM -94B7IWcnMFk= ------END CERTIFICATE----- -""" - -const val PRIVATE_ROOT_CA = """-----BEGIN CERTIFICATE----- -MIIFhDCCA2ygAwIBAgIEAJimITANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMTMwMQYDVQQDDCpTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUHJpdmF0ZSBSb290IENBIC0gRzEwHhcNMTMxMTE0 -MTM0ODU1WhcNMjgxMTEzMjMwMDAwWjBiMQswCQYDVQQGEwJOTDEeMBwGA1UECgwV -U3RhYXQgZGVyIE5lZGVybGFuZGVuMTMwMQYDVQQDDCpTdGFhdCBkZXIgTmVkZXJs -YW5kZW4gUHJpdmF0ZSBSb290IENBIC0gRzEwggIiMA0GCSqGSIb3DQEBAQUAA4IC -DwAwggIKAoICAQDaIMh56ynwnEhE7Ey54KpX5j1XDoxbHDCgXctute55RjmG2hy6 -fuq++q/dCSsj38Pi/KYn/PN13EF05k39IRvakb0AQNVyHifNKXfta6Tzi5QcM4BK -09DB4Ckb6TdZTNUtWyEcAtRblYaVSQ4Xr5QODNqu2FGQucraVXqCIx81azlOE2Jb -Zli9AZKn94pP57A11dUYhxMsh70YosJEKVB8Ue4ROksHhb/nnOISG+2y9FD5M8u8 -jYhp00TGZGVu5z0IFgtqX0i8GmrH0ub9AWjf/iU4MWjGVRSq0cwUHEeKRj/UD9a8 -xIEn9TxIfYj+6+s4tn9dW/4PV5jc6iGJx6ExTPfOR7VHpxS4XujrZb5Ba/+oj/ON -dOfR0JSm2itCytbtjQBBL0oocIIqaqOna1cufHkcn9VleF7Zvz/8njQIpAU4J4nJ -4pE5pQ3k4ORAGNnq5R9hAqqUQGDlo3Uj8PBou0nPzQ7JNgGkN+my/lGr4rceUNK/ -8CoGnYFUH+UyFtJkvlLlEkb688/IdNdGgY+vuXCAB6xfKlJjAGChFUBb6swbNeNc -tVEdUj7Weg4Jt5gXu78C2mjs9x5lcHOgMO4ZmvYJ3Ejp4k3nNa45HOIVkYrfQrrB -HzBhR0BuReAagurcbtUjJFd7BtufGVLfU3CUn1l6u3/9eG4DGH6pq+dSKQIDAQAB -o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU -Kv25Kx76w4SHBtuB/4aXdQ3rAYswDQYJKoZIhvcNAQELBQADggIBAEvpmXMOOKdQ -wUPysrsdIkGJUFF+dvmsJDiOuAqV0A1nNTooL3esvDLEZAWZwKTOwRomnHzeCfS/ -QxRKTkVX21pfrHf9ufDKykpzjl9uAILTS76FJ6//R0RTIPMrzknQpG2fCLR5DFEb -HWU/jWAxGmncfx6HQYl/azHaWbv0dhZOUjPdkGAQ6EPvHcyNU9yMkETdw0X6ioxq -zMwkGM893oBrMmtduiqIf3/H6HTXoRKAc+/DXZIq/pAc6eVMa6x43kokluaam9L7 -8yDrlHbGd2VYAr/HZ0TjDZTtI2t2/ySTb7JjC8wL8rSqxYmLpNrnhZzPW87sl2OC -FC3re3ZhtJkIHNP85jj1gqewTC7DCW6llZdB3hBzfHWby0EX2RlcwgaMfNBEV5U0 -IogccdXV+S6zWK4F+yBr0sXUrdbdMFu+g3I9CbXxt0q4eVJtoaun4M2Z+bZMqZvy -9FryBdSfhpgmJqwFz2luOhPOVCblCPhLrUeewrvuBXoZQWt1ZjuHfwJZ1dgjszVE -qwY9S0SdqCg2ZlL9s3vDIrrd3wLWrcHLQMd9gwsppNv9c7JfIJdlcZLTmF9EuL6e -CvVVrqBVqLHjva4erqYol6K/jbSfUtRCy8IlFU7LYu1KLehZKYvj3vekj3Cn08Aq -ljr/Q8Pw+OfUZTzKg4PVDQVfFqKtyosv ------END CERTIFICATE----- -""" - -const val DIGICERT_BTC_ROOT_CA = """-----BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- -""" - -const val ROOT_UZI = """-----BEGIN CERTIFICATE----- -MIIFUjCCAzqgAwIBAgIIKdrUuna6upwwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UE -BhMCTkwxDTALBgNVBAoMBENJQkcxKTAnBgNVBAMMIFRFU1QgWm9yZyBDU1AgUHJp -dmF0ZSBSb290IENBIEcxMB4XDTE3MDMxNjA5NTcyN1oXDTI4MTExNDAwMDAwMFow -RzELMAkGA1UEBhMCTkwxDTALBgNVBAoMBENJQkcxKTAnBgNVBAMMIFRFU1QgWm9y -ZyBDU1AgUHJpdmF0ZSBSb290IENBIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEAzjpo/NlOEqJ+eGSyiVZvHbjWb2/P87k32xsVfdmAXLtrp7wIXATO -2HSaRyE2oiDZgHTWSYsEVcjWwQkFXlk9sFAHBHEv3AjgT35o9Fr94gBpxEzIsRae -59jwGGwyH7ayY8TflTs6lBYS45309UpMI0NvnNPhVrdmZlL090vlkIGxS5gFGQ3K -pg6HgYMnmY2Hd3hOK6rF//68pEtMfLQN1hmY3ewKPWj8djveLRofP9UQufV+P0dl -iHc6w3Fe59Z/7/jPhI47swuVdtX/DZPyZoCW/1rE9gffE34IhOAXgemmOF7MRwnj -GAlRtvQaJxpOBDjiAGlFC1BqPlzuzQV6v5O+lC+S3Oab1NGpxDvT2VEg92nP7C5B -F0xqP3PfII5UwZ7iYUgH0yvsW0hSHElKmdnt4l5vCKZKEwf6zPtqOPZR9okxGXEC -lu1sQhI/Huli5jAAzremq17YdVNhcmwTUcI9/AlRLs9O1GvEMcmFRm09LbBbU6ZC -jr+vnx+XrVVf8Z2peop1D69kQ7Hlr5xb1+hSL2F7jd/HbRxngQirTRTTQI7onQmA -OjkB+iGM1/6I5f1iIWgzJ0WZklUB9juEqASbAcTJcsnuN+YIeu4/n+iOegnayguI -sAJh8A3ieLdD+Bx0yUvmws2zgDmgM7Hxm0VwGBF8MqdskzGkIXhL3AMCAwEAAaNC -MEAwHQYDVR0OBBYEFNP04CKvd1CmINKdgWVSppPERVOVMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQC0AciAKTxvjFBh -NlUwPd3kKgqumc4OC12Rjjr9KZKAx6lHIsdR4LuPGtD7FGiftw9oN5R+JggR1GtN -IilsKyv9WhE4wsgo8kFlhZJ6lYqJZuujRj43RmaZrb7iQivZFiZ/ArogtEaEuCSy -R2BpVywxW90GrTJxmU8FMNPnqc5cboMwEKfiv+UgioqEHXtAChia0Ajv6mBWxR6k -a7kQQjUqetfgz8R0roJS0P7wLyjvbh6E4n9HoKTCw7X7XluhZiGnLFlbxD+T6jEk -2IYpLheykA6tqT1nT5LQOgj+upcUkWjZ6YxDvDdvBZb3Uw2GiQymuj5XIx6em8Dp -vzW1hYxbmFddr2ZGAzQjpt2TBVSK4HLRh1EAVo7v2ut0mz9IiEefsAG+JvqMqIS4 -EeUhdoPz0SKTK2AttYR1Fc+HASHgF/rIDZaxjpP7F0GjXirvTDayekX0V9q6P6sP -Q6GekIgt5bX/LOuwk8loITCdmCkbH/nfeZIPDnD3og78cA9XzMb/ejC1oPPcToKi -dvoqyuRL1Pxa17LyXc3gOkD3m8mZ8RW8lyCb+9nAaXatkZ/xQYVqJvaKwvDBRJp5 -pDn/ip6lN9Ip3WdPIjuj1wzpZM87y5mYPBbVTV0Jx94b2IWUZUtJeOcxBE6cGtUA -9j4m4xNo/7DFxzjtRLkG97Gm08yC8w== ------END CERTIFICATE----- -""" diff --git a/api/src/main/java/nl/rijksoverheid/ctr/api/signing/http/SignedRequest.kt b/api/src/main/java/nl/rijksoverheid/ctr/api/signing/http/SignedRequest.kt deleted file mode 100644 index f1919ec74..000000000 --- a/api/src/main/java/nl/rijksoverheid/ctr/api/signing/http/SignedRequest.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.api.signing.http - -@Retention(AnnotationRetention.RUNTIME) -@Target(AnnotationTarget.FUNCTION) -annotation class SignedRequest diff --git a/api/src/test/java/nl/rijksoverheid/ctr/api/CacheOverrideInterceptorTest.kt b/api/src/test/java/nl/rijksoverheid/ctr/api/CacheOverrideInterceptorTest.kt deleted file mode 100644 index 8042e175a..000000000 --- a/api/src/test/java/nl/rijksoverheid/ctr/api/CacheOverrideInterceptorTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.api - -import java.io.File -import kotlinx.coroutines.runBlocking -import nl.rijksoverheid.ctr.api.interceptors.CacheOverrideInterceptor -import okhttp3.Cache -import okhttp3.OkHttpClient -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Before -import org.junit.Test -import retrofit2.Retrofit - -private const val RESPONSE = "Server response" - -class CacheOverrideInterceptorTest { - private lateinit var mockWebServer: MockWebServer - private lateinit var cache: Cache - private lateinit var tmpDir: File - private lateinit var testApi: TestApi - - @Before - fun setup() { - mockWebServer = MockWebServer() - tmpDir = File.createTempFile("cache", "dir") - tmpDir.delete() - cache = Cache(tmpDir, 1024 * 1024) - testApi = Retrofit.Builder().client( - OkHttpClient.Builder().addInterceptor(CacheOverrideInterceptor()).build() - ).baseUrl(mockWebServer.url("/")).build().create(TestApi::class.java) - } - - @After - fun tearDown() { - mockWebServer.shutdown() - } - - @Test - fun `CacheOverride overrides cache headers from server`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody(RESPONSE) - .addHeader("cache-control", "no-cache") - .addHeader("pragma", "no-cache") - ) - mockWebServer.enqueue( - MockResponse() - .setBody(RESPONSE) - .addHeader("cache-control", "no-cache") - .addHeader("pragma", "no-cache") - ) - - val normalResponse = testApi.normalRequest() - val cacheOverrideResponse = testApi.cacheOverridden() - - assertEquals("no-cache", normalResponse.headers()["cache-control"]) - assertEquals("no-cache", normalResponse.headers()["pragma"]) - assertEquals("public,max-age=0", cacheOverrideResponse.headers()["cache-control"]) - assertNull(cacheOverrideResponse.headers()["pragma"]) - } -} diff --git a/api/src/test/java/nl/rijksoverheid/ctr/api/TestApi.kt b/api/src/test/java/nl/rijksoverheid/ctr/api/TestApi.kt deleted file mode 100644 index e18abfe9f..000000000 --- a/api/src/test/java/nl/rijksoverheid/ctr/api/TestApi.kt +++ /dev/null @@ -1,15 +0,0 @@ -package nl.rijksoverheid.ctr.api - -import nl.rijksoverheid.ctr.api.interceptors.CacheOverride -import okhttp3.ResponseBody -import retrofit2.Response -import retrofit2.http.GET - -interface TestApi { - @GET("/") - @CacheOverride("public,max-age=0") - suspend fun cacheOverridden(): Response - - @GET("/") - suspend fun normalRequest(): Response -} diff --git a/api/src/test/java/nl/rijksoverheid/ctr/api/factory/NetworkRequestResultFactoryTest.kt b/api/src/test/java/nl/rijksoverheid/ctr/api/factory/NetworkRequestResultFactoryTest.kt deleted file mode 100644 index 1987b75fe..000000000 --- a/api/src/test/java/nl/rijksoverheid/ctr/api/factory/NetworkRequestResultFactoryTest.kt +++ /dev/null @@ -1,221 +0,0 @@ -package nl.rijksoverheid.ctr.api.factory - -import android.net.ConnectivityManager -import com.squareup.moshi.JsonClass -import com.squareup.moshi.Moshi -import java.time.OffsetDateTime -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.runBlocking -import nl.rijksoverheid.ctr.shared.models.CoronaCheckErrorResponse -import nl.rijksoverheid.ctr.shared.models.MijnCnErrorResponse -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import nl.rijksoverheid.ctr.shared.models.Step -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil -import okhttp3.OkHttpClient -import okhttp3.ResponseBody -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import okhttp3.mockwebserver.SocketPolicy -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import retrofit2.Converter -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import retrofit2.http.GET - -class NetworkRequestResultFactoryTest { - - private val TestStep = Step(1) - private lateinit var mockWebServer: MockWebServer - private lateinit var testApi: TestApi - private lateinit var errorResponseBodyConverter: Converter - private lateinit var mijnCnErrorConverter: Converter - - @Before - fun setup() { - mockWebServer = MockWebServer() - - val okHttpClient = OkHttpClient.Builder() - .readTimeout(1, TimeUnit.SECONDS) - - val retrofit = Retrofit.Builder().client(okHttpClient.build()) - .baseUrl(mockWebServer.url("/")) - .addConverterFactory(MoshiConverterFactory.create(Moshi.Builder().build())) - .build() - errorResponseBodyConverter = retrofit.responseBodyConverter(CoronaCheckErrorResponse::class.java, emptyArray()) - mijnCnErrorConverter = retrofit.responseBodyConverter(MijnCnErrorResponse::class.java, emptyArray()) - testApi = retrofit.create(TestApi::class.java) - } - - @Test - fun `createResult returns Success if request successful`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"hello\":\"world\"}") - .setResponseCode(200) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(true), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - TestStep - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Success) - } - - @Test - fun `createResult returns HttpError if request failed with normal body`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"hello\":\"world\"}") - .setResponseCode(404) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(true), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - TestStep - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Failed.CoronaCheckHttpError) - } - - @Test - fun `createResult returns ProviderHttpError if request to provider failed with normal body`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"hello\":\"world\"}") - .setResponseCode(404) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(true), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - step = TestStep, - provider = "GGD" - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Failed.ProviderHttpError) - } - - @Test - fun `createResult returns CoronaCheckWithErrorResponseHttpError if request failed with specific body`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"status\":\"1\",\"code\":1000}") - .setResponseCode(404) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(true), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - TestStep - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Failed.CoronaCheckWithErrorResponseHttpError) - } - - @Test - fun `createResult returns NetworkError if request has no response`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"hello\":\"world\"}") - .setResponseCode(200) - .setSocketPolicy(SocketPolicy.NO_RESPONSE) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(true), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - TestStep - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Failed.ServerNetworkError) - } - - @Test - fun `createResult returns Error if failed to parse`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"world\":\"hello\"}") - .setResponseCode(200) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(true), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - TestStep - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Failed.Error) - } - - @Test - fun `createResult returns ClientServerError if no internet connection`() = runBlocking { - mockWebServer.enqueue( - MockResponse() - .setBody("{\"world\":\"hello\"}") - .setResponseCode(200) - ) - - val networkRequestResultFactory = NetworkRequestResultFactory(errorResponseBodyConverter, getAndroidUtil(false), mijnCnErrorConverter) - - val result = networkRequestResultFactory.createResult( - TestStep - ) { - testApi.request() - } - - assertTrue(result is NetworkRequestResult.Failed.ClientNetworkError) - } - - interface TestApi { - @GET("/") - suspend fun request(): TestObject - } - - @JsonClass(generateAdapter = true) - data class TestObject(val hello: String) - - private fun getAndroidUtil(isNetworkAvailable: Boolean): AndroidUtil { - return object : AndroidUtil { - override fun isSmallScreen(): Boolean { - return false - } - - override fun getMasterKeyAlias(): String { - return "" - } - - override fun isNetworkAvailable(): Boolean { - return isNetworkAvailable - } - - override fun getConnectivityManager(): ConnectivityManager { - TODO("Not yet implemented") - } - - override fun generateRandomKey(): String { - return "" - } - - override fun getFirstInstallTime(): OffsetDateTime { - return OffsetDateTime.now() - } - } - } -} diff --git a/appconfig/build.gradle b/appconfig/build.gradle index 1bbda6b1d..9668173e6 100644 --- a/appconfig/build.gradle +++ b/appconfig/build.gradle @@ -48,8 +48,6 @@ dependencies { coreLibraryDesugaring libs.desugar implementation(project(":design")) implementation(project(":shared")) - implementation(project(':mobilecore')) - implementation(project(":api")) api libs.lifecycle.viewmodel api libs.lifecycle.livedata api libs.retrofit diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigModule.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigModule.kt index 432d6dd6f..3eef3ac89 100644 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigModule.kt +++ b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigModule.kt @@ -8,8 +8,6 @@ package nl.rijksoverheid.ctr.appconfig -import android.content.Context -import nl.rijksoverheid.ctr.appconfig.api.AppConfigApi import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManagerImpl import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager @@ -18,26 +16,11 @@ import nl.rijksoverheid.ctr.appconfig.persistence.AppUpdatePersistenceManager import nl.rijksoverheid.ctr.appconfig.persistence.AppUpdatePersistenceManagerImpl import nl.rijksoverheid.ctr.appconfig.persistence.RecommendedUpdatePersistenceManager import nl.rijksoverheid.ctr.appconfig.persistence.RecommendedUpdatePersistenceManagerImpl -import nl.rijksoverheid.ctr.appconfig.repositories.ConfigRepository -import nl.rijksoverheid.ctr.appconfig.repositories.ConfigRepositoryImpl -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigFreshnessUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigFreshnessUseCaseImpl -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigUseCaseImpl -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCaseImpl -import nl.rijksoverheid.ctr.appconfig.usecases.ClockDeviationUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ClockDeviationUseCaseImpl -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCaseImpl -import nl.rijksoverheid.ctr.appconfig.usecases.PersistConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.PersistConfigUseCaseImpl -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.OkHttpClient +import nl.rijksoverheid.ctr.appconfig.usecases.DeleteConfigUseCase +import nl.rijksoverheid.ctr.appconfig.usecases.DeleteConfigUseCaseImpl import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module -import retrofit2.Retrofit /** * Configure app config dependencies @@ -45,56 +28,20 @@ import retrofit2.Retrofit * @param path Path for the config api, for example "holder" to fetch the config from /holder/config * @param versionCode version code */ -fun appConfigModule(cdnUrl: String, path: String, versionCode: Int) = module { - factory { ConfigRepositoryImpl(get()) } - factory { AppConfigUseCaseImpl(get(), get(), get(), get()) } +fun appConfigModule() = module { factory { AppConfigPersistenceManagerImpl(get()) } factory { AppUpdatePersistenceManagerImpl(get()) } factory { AppConfigStorageManagerImpl(androidContext().filesDir.path) } - factory { - CachedAppConfigUseCaseImpl( - get(), - androidContext().filesDir.path, - get(), - isVerifierApp(androidContext()) - ) - } - factory { - PersistConfigUseCaseImpl( - get(), + factory { + DeleteConfigUseCaseImpl( androidContext().filesDir.path ) } - single { ClockDeviationUseCaseImpl(get(), get()) } - single { AppConfigFreshnessUseCaseImpl(get(), get(), get()) } factory { RecommendedUpdatePersistenceManagerImpl(get()) } - single { - val okHttpClient = get(OkHttpClient::class).newBuilder().build() - val retrofit = get(Retrofit::class) - val baseUrl = cdnUrl.toHttpUrl().newBuilder().addPathSegments("$path/").build() - retrofit.newBuilder().baseUrl(baseUrl).client(okHttpClient).build() - .create(AppConfigApi::class.java) - } - - single { ConfigResultUseCaseImpl(get(), get()) } - viewModel { AppConfigViewModelImpl( - get(), - get(), - get(), - get(), - get(), - androidContext().filesDir.path, - isVerifierApp(androidContext()), - versionCode, - get(), - get(), get() ) } } - -fun isVerifierApp(applicationContext: Context): Boolean = - applicationContext.packageName.contains("verifier") diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModel.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModel.kt index bf56f1ac4..3749ee9ad 100644 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModel.kt +++ b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModel.kt @@ -11,124 +11,25 @@ package nl.rijksoverheid.ctr.appconfig import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import java.net.UnknownHostException import kotlinx.coroutines.launch import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.appconfig.models.AppUpdateData -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import nl.rijksoverheid.ctr.appconfig.persistence.AppUpdatePersistenceManager -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.AppStatusUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.factories.ErrorCodeStringFactory -import nl.rijksoverheid.ctr.shared.factories.OnboardingFlow +import nl.rijksoverheid.ctr.appconfig.usecases.DeleteConfigUseCase abstract class AppConfigViewModel : ViewModel() { val appStatusLiveData = MutableLiveData() - - abstract fun refresh( - mobileCoreWrapper: MobileCoreWrapper, - force: Boolean = false, - afterRefresh: () -> Unit = {} - ) - abstract fun saveNewFeaturesFinished() - abstract fun saveNewTerms() } class AppConfigViewModelImpl( - private val appConfigUseCase: AppConfigUseCase, - private val appStatusUseCase: AppStatusUseCase, - private val configResultUseCase: ConfigResultUseCase, - private val appConfigStorageManager: AppConfigStorageManager, - private val cachedAppConfigUseCase: CachedAppConfigUseCase, - private val filesDirPath: String, - private val isVerifierApp: Boolean, - private val versionCode: Int, - private val appUpdatePersistenceManager: AppUpdatePersistenceManager, - private val errorCodeStringFactory: ErrorCodeStringFactory, - private val appUpdateData: AppUpdateData + private val deleteConfigUseCase: DeleteConfigUseCase ) : AppConfigViewModel() { - private fun updateAppStatus(appStatus: AppStatus) { - if (appStatusLiveData.value != appStatus) { - appStatusLiveData.postValue(appStatus) - } - } - - override fun refresh( - mobileCoreWrapper: MobileCoreWrapper, - force: Boolean, - afterRefresh: () -> Unit - ) { - // update the app status from the last fetched config - // only if it is valid (so don't use the default one) - if (cachedAppConfigUseCase.isCachedAppConfigValid()) { - val appStatus = appStatusUseCase.checkIfActionRequired( - versionCode, - cachedAppConfigUseCase.getCachedAppConfig() - ) - updateAppStatus(appStatus) - } - - if (!force && !appConfigUseCase.canRefresh(cachedAppConfigUseCase)) { - return - } + init { viewModelScope.launch { - val configResult = configResultUseCase.fetch() - afterRefresh() - val appStatus = appStatusUseCase.get(configResult, versionCode) - - val configFilesArePresentInFilesFolder = - appConfigStorageManager.areConfigFilesPresentInFilesFolder() - if (!configFilesArePresentInFilesFolder || !cachedAppConfigUseCase.isCachedAppConfigValid()) { - if (configResult is ConfigResult.Error && configResult.error.e !is UnknownHostException) { - return@launch appStatusLiveData.postValue( - AppStatus.LaunchError( - errorCodeStringFactory.get( - OnboardingFlow, - listOf(configResult.error) - ) - ) - ) - } else { - return@launch appStatusLiveData.postValue(AppStatus.Error) - } + try { + deleteConfigUseCase() + } catch (exception: Exception) { + // no op } - - val initializationError = if (isVerifierApp) { - mobileCoreWrapper.initializeVerifier(filesDirPath) - } else { - mobileCoreWrapper.initializeHolder(filesDirPath) - } - - if (initializationError != null) { - return@launch appStatusLiveData.postValue(AppStatus.Error) - } - - updateAppStatus(appStatus) } } - - override fun saveNewFeaturesFinished() { - appUpdateData.newFeatureVersion?.let { appUpdatePersistenceManager.saveNewFeaturesSeen(it) } - updateAppStatus( - appStatusUseCase.checkIfActionRequired( - versionCode, - cachedAppConfigUseCase.getCachedAppConfig() - ) - ) - } - - override fun saveNewTerms() { - appUpdatePersistenceManager.saveNewTermsSeen(appUpdateData.newTerms.version) - updateAppStatus( - appStatusUseCase.checkIfActionRequired( - versionCode, - cachedAppConfigUseCase.getCachedAppConfig() - ) - ) - } } diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppLockedFragment.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppLockedFragment.kt index 20e4962a7..c480479f2 100644 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppLockedFragment.kt +++ b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppLockedFragment.kt @@ -8,19 +8,13 @@ package nl.rijksoverheid.ctr.appconfig -import android.annotation.SuppressLint -import android.content.Intent import android.os.Bundle import android.view.View import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.core.text.HtmlCompat import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs import nl.rijksoverheid.ctr.appconfig.databinding.FragmentAppLockedBinding -import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.ext.enableHtmlLinks import nl.rijksoverheid.ctr.design.utils.IntentUtil import nl.rijksoverheid.ctr.shared.utils.AndroidUtil import org.koin.android.ext.android.inject @@ -31,9 +25,7 @@ class AppLockedFragment : Fragment(R.layout.fragment_app_locked) { private val androidUtil: AndroidUtil by inject() private val intentUtil: IntentUtil by inject() - private val cachedAppConfigUseCase: CachedAppConfigUseCase by inject() - @SuppressLint("StringFormatInvalid") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -43,84 +35,15 @@ class AppLockedFragment : Fragment(R.layout.fragment_app_locked) { binding.illustration.visibility = View.GONE } - when (val appStatus = args.appStatus) { - is AppStatus.Deactivated -> { - binding.bind( - R.string.app_status_deactivated_title, - R.string.app_status_deactivated_message, - R.string.app_status_deactivated_action, - R.drawable.illustration_app_status_deactivated - ) { - intentUtil.openUrl( - requireContext(), getString( - if (isVerifierApp(requireContext())) { - R.string.verifier_deactivation_url - } else { - R.string.holder_deactivation_url - } - ) - ) - } - } - is AppStatus.Archived -> { - binding.bind( - R.string.holder_archiveMode_title, - R.string.holder_archiveMode_description, - R.string.holder_archiveMode_button, - R.drawable.illustration_app_status_deactivated - ) { - intentUtil.openUrl( - requireContext(), getString(R.string.holder_archiveMode_link) - ) - } - } - is AppStatus.UpdateRequired -> { - binding.bind( - R.string.app_status_update_required_title, - R.string.app_status_update_required_message, - R.string.app_status_update_required_action, - R.drawable.illustration_app_status_update_required - ) { - intentUtil.openPlayStore(requireContext()) - } - } - is AppStatus.LaunchError -> { - val helpdeskPhoneNumber = - cachedAppConfigUseCase.getCachedAppConfig().contactInfo.phoneNumber - binding.bind( - R.string.appstatus_launchError_title, - getString( - R.string.appstatus_launchError_body, - helpdeskPhoneNumber, - helpdeskPhoneNumber, - appStatus.errorMessage - ), - R.string.appstatus_launchError_button, - R.drawable.illustration_app_status_launch_error - ) { - activity?.finish() - } - binding.message - } - is AppStatus.Error -> { - binding.bind( - R.string.app_status_internet_required_title, - R.string.app_status_internet_required_message, - R.string.app_status_internet_required_action, - R.drawable.illustration_app_status_internet_required - ) { - val launchIntent = - requireContext().packageManager.getLaunchIntentForPackage(requireContext().packageName) - launchIntent?.let { - launchIntent.flags = - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK - requireContext().startActivity(launchIntent) - } - } - } - else -> { - /* nothing */ - } + binding.bind( + R.string.app_status_deactivated_title, + R.string.app_status_deactivated_message, + R.string.app_status_deactivated_action, + R.drawable.illustration_app_status_deactivated + ) { + intentUtil.openUrl( + requireContext(), getString(R.string.holder_deactivation_url) + ) } } } @@ -140,24 +63,3 @@ private fun FragmentAppLockedBinding.bind( onClick() } } - -private fun FragmentAppLockedBinding.bind( - @StringRes title: Int, - message: String, - @StringRes action: Int, - @DrawableRes illustration: Int, - onClick: () -> Unit -) { - this.title.setText(title) - this.message.text = HtmlCompat.fromHtml( - message, - HtmlCompat.FROM_HTML_MODE_COMPACT - ) - this.message.enableHtmlLinks() - this.message.textAlignment = View.TEXT_ALIGNMENT_CENTER - this.action.setText(action) - this.illustration.setImageResource(illustration) - this.action.setOnClickListener { - onClick() - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppStatusFragment.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppStatusFragment.kt index 01886d28c..6a0578892 100644 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppStatusFragment.kt +++ b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/AppStatusFragment.kt @@ -20,28 +20,10 @@ class AppStatusFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - when (val status = args.appStatus) { - is AppStatus.UpdateRequired, - is AppStatus.Deactivated, - is AppStatus.Archived, - is AppStatus.LaunchError -> navigateSafety( - AppStatusFragmentDirections.actionAppLocked(status) - ) - is AppStatus.Error -> navigateSafety( - AppStatusFragmentDirections.actionAppLocked(status) - ) - is AppStatus.NewFeatures -> { - navigateSafety( - AppStatusFragmentDirections.actionNavNewFeatures(status.appUpdateData) - ) - } - is AppStatus.ConsentNeeded -> { - navigateSafety( - AppStatusFragmentDirections.actionNewTerms(status.appUpdateData) - ) - } - is AppStatus.NoActionRequired, - is AppStatus.UpdateRecommended -> Unit - } + val status = args.appStatus ?: AppStatus.Deactivated + + navigateSafety( + AppStatusFragmentDirections.actionAppLocked(status) + ) } } diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/api/AppConfigApi.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/api/AppConfigApi.kt deleted file mode 100644 index 3ecacf867..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/api/AppConfigApi.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.api - -import nl.rijksoverheid.ctr.api.signing.http.SignedRequest -import okhttp3.ResponseBody -import org.json.JSONObject -import retrofit2.Response -import retrofit2.http.GET - -interface AppConfigApi { - @GET("config") - @SignedRequest - suspend fun getConfig(): Response - - @GET("public_keys") - @SignedRequest - suspend fun getPublicKeys(): Response -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeatureItemFragment.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeatureItemFragment.kt deleted file mode 100644 index a791476f2..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeatureItemFragment.kt +++ /dev/null @@ -1,68 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.app_update - -import android.os.Bundle -import android.view.View -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import nl.rijksoverheid.ctr.appconfig.R -import nl.rijksoverheid.ctr.appconfig.databinding.FragmentNewFeatureItemBinding -import nl.rijksoverheid.ctr.appconfig.models.NewFeatureItem -import nl.rijksoverheid.ctr.shared.ext.getParcelableCompat -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil -import org.koin.android.ext.android.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class NewFeatureItemFragment : Fragment(R.layout.fragment_new_feature_item) { - - companion object { - private const val EXTRA_NEW_FEATURE_ITEM = "EXTRA_NEW_FEATURE_ITEM" - - fun getInstance(newFeatureItem: NewFeatureItem): NewFeatureItemFragment { - val fragment = - NewFeatureItemFragment() - val bundle = Bundle() - bundle.putParcelable(EXTRA_NEW_FEATURE_ITEM, newFeatureItem) - fragment.arguments = bundle - return fragment - } - } - - private val androidUtil: AndroidUtil by inject() - - private val item: NewFeatureItem by lazy { - arguments?.getParcelableCompat( - EXTRA_NEW_FEATURE_ITEM - ) ?: throw Exception("Failed to get item") - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentNewFeatureItemBinding.bind(view) - - binding.title.text = getString(item.titleResource) - binding.title.contentDescription = getString(item.subtitleResource) + getString(item.titleResource) - binding.subTitle.text = getString(item.subtitleResource) - item.subTitleColor?.let { - binding.subTitle.setTextColor(ContextCompat.getColor(requireContext(), it)) - } - binding.description.setHtmlText(getString(item.description)) - - if (androidUtil.isSmallScreen()) { - binding.image.visibility = View.GONE - } else { - binding.image.visibility = View.VISIBLE - if (item.imageResource != 0) { - binding.image.setImageResource(item.imageResource) - } - item.backgroundColor?.let { - binding.image.setBackgroundColor(ContextCompat.getColor(requireContext(), it)) - } - } - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesFragment.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesFragment.kt deleted file mode 100644 index e76340484..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesFragment.kt +++ /dev/null @@ -1,157 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.app_update - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.View -import android.widget.ScrollView -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.navigation.findNavController -import androidx.navigation.fragment.navArgs -import androidx.viewpager2.widget.ViewPager2 -import nl.rijksoverheid.ctr.appconfig.AppConfigViewModel -import nl.rijksoverheid.ctr.appconfig.R -import nl.rijksoverheid.ctr.appconfig.databinding.FragmentNewFeaturesBinding -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety -import nl.rijksoverheid.ctr.shared.ext.getNavigationIconView -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.utils.Accessibility.setAccessibilityFocus -import org.koin.androidx.viewmodel.ext.android.sharedViewModel - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class NewFeaturesFragment : Fragment(R.layout.fragment_new_features) { - - private val args: NewFeaturesFragmentArgs by navArgs() - private val appConfigViewModel: AppConfigViewModel by sharedViewModel() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentNewFeaturesBinding.bind(view) - - val adapter = NewFeaturesPagerAdapter( - childFragmentManager, - lifecycle, - args.appUpdateData.newFeatures - ) - - if (args.appUpdateData.newFeatures.isNotEmpty()) { - binding.indicators.initIndicator(adapter.itemCount) - initViewPager(binding, adapter) - } - - bindViews(binding, adapter) - } - - private fun bindViews( - binding: FragmentNewFeaturesBinding, - adapter: NewFeaturesPagerAdapter - ) { - binding.run { - toolbar.setNavigationOnClickListener { - viewPager.currentItem = viewPager.currentItem - 1 - } - button.setOnClickListener { - val currentItem = viewPager.currentItem - if (currentItem == adapter.itemCount - 1) finishFlow() else showNextPage(currentItem, binding) - } - setButtonText(binding, viewPager.currentItem) - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : - OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - val currentItem = viewPager.currentItem - if (currentItem == 0) { - val canPop = findNavControllerSafety()?.popBackStack() ?: false - if (!canPop) { - requireActivity().finish() - } - } else { - viewPager.currentItem = viewPager.currentItem - 1 - } - } - }) - } - } - - private fun FragmentNewFeaturesBinding.showNextPage( - currentItem: Int, - binding: FragmentNewFeaturesBinding - ) { - val nextIndex = currentItem + 1 - viewPager.currentItem = nextIndex - setButtonText(binding, nextIndex) - toolbar.getNavigationIconView()?.setAccessibilityFocus() - } - - private fun finishFlow() { - args.appUpdateData.savePolicyChange() - appConfigViewModel.saveNewFeaturesFinished() - - navigateToMain() - } - - private fun navigateToTerms() { - navigateSafety( - R.id.nav_new_features, - NewFeaturesFragmentDirections.actionNewTerms(args.appUpdateData) - ) - } - - private fun navigateToMain() { - requireActivity().findNavController(R.id.main_nav_host_fragment) - .navigate(R.id.action_main) - } - - private fun initViewPager( - binding: FragmentNewFeaturesBinding, - adapter: NewFeaturesPagerAdapter - ) { - binding.viewPager.offscreenPageLimit = args.appUpdateData.newFeatures.size - binding.viewPager.adapter = adapter - val hideToolbar = adapter.itemCount == 1 - if (hideToolbar) { - binding.toolbar.visibility = View.INVISIBLE - } - binding.viewPager.registerOnPageChangeCallback(object : - ViewPager2.OnPageChangeCallback() { - @SuppressLint("StringFormatInvalid") - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - if (!hideToolbar) { - binding.toolbar.visibility = if (position == 0) View.INVISIBLE else View.VISIBLE - } - binding.indicators.updateSelected(position) - - setButtonText(binding, position) - - // Apply bottom elevation if the view inside the viewpager is scrollable - val scrollView = - childFragmentManager.fragments.getOrNull(position)?.view?.findViewById(R.id.scroll) - if (scrollView?.canScrollVertically(1) == true) { - binding.bottom.cardElevation = - resources.getDimensionPixelSize(R.dimen.scroll_view_button_elevation) - .toFloat() - } else { - binding.bottom.cardElevation = 0f - } - } - }) - } - - private fun setButtonText(binding: FragmentNewFeaturesBinding, position: Int) { - val newFeature = args.appUpdateData.newFeatures.getOrNull(position) ?: return - binding.button.text = getString( - if (position == args.appUpdateData.newFeatures.size - 1) { - newFeature.lastButtonResource - } else { - newFeature.buttonResource - } - ) - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesPagerAdapter.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesPagerAdapter.kt deleted file mode 100644 index d7b14ce28..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesPagerAdapter.kt +++ /dev/null @@ -1,30 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.app_update - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Lifecycle -import androidx.viewpager2.adapter.FragmentStateAdapter -import nl.rijksoverheid.ctr.appconfig.models.NewFeatureItem - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class NewFeaturesPagerAdapter( - fragmentManager: FragmentManager, - lifecycle: Lifecycle, - private val items: List -) : FragmentStateAdapter(fragmentManager, lifecycle) { - - override fun getItemCount(): Int { - return items.size - } - - override fun createFragment(position: Int): Fragment { - val item = items[position] - return NewFeatureItemFragment.getInstance(item) - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewTermsFragment.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewTermsFragment.kt deleted file mode 100644 index bae137c8d..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/app_update/NewTermsFragment.kt +++ /dev/null @@ -1,76 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.app_update - -import android.os.Bundle -import android.view.View -import androidx.core.view.doOnPreDraw -import androidx.fragment.app.Fragment -import androidx.navigation.findNavController -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.appconfig.AppConfigViewModel -import nl.rijksoverheid.ctr.appconfig.R -import nl.rijksoverheid.ctr.appconfig.databinding.FragmentNewTermsBinding -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.sharedViewModel - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class NewTermsFragment : Fragment(R.layout.fragment_new_terms) { - - private val args: NewTermsFragmentArgs by navArgs() - private val appConfigViewModel: AppConfigViewModel by sharedViewModel() - private val dialogUtil: DialogUtil by inject() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentNewTermsBinding.bind(view) - - binding.scroll.doOnPreDraw { - if (binding.scroll.canScrollVertically(1)) { - binding.bottom.cardElevation = - resources.getDimensionPixelSize(R.dimen.scroll_view_button_elevation) - .toFloat() - } else { - binding.bottom.cardElevation = 0f - } - } - - if (args.appUpdateData.newTerms.needsConsent) { - binding.positiveButton.visibility = View.VISIBLE - binding.positiveButton.text = - getString(R.string.new_terms_consent_needed_positive_button) - binding.negativeButton.visibility = View.VISIBLE - binding.negativeButton.text = - getString(R.string.new_terms_consent_needed_negative_button) - binding.negativeButton.setOnClickListener { - presentNeedToConsentDialog() - } - } else { - binding.positiveButton.visibility = View.VISIBLE - binding.positiveButton.text = getString(R.string.new_terms_consent_button) - binding.negativeButton.visibility = View.GONE - } - - binding.positiveButton.setOnClickListener { - appConfigViewModel.saveNewTerms() - requireActivity().findNavController(R.id.main_nav_host_fragment) - .navigate(R.id.action_main) - } - } - - private fun presentNeedToConsentDialog() { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.new_terms_no_consent_dialog_title, - message = getString(R.string.new_terms_no_consent_dialog_message), - positiveButtonText = R.string.new_terms_no_consent_dialog_button, - positiveButtonCallback = {} - ) - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppStatus.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppStatus.kt index e27bb6d0f..7401f5ad0 100644 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppStatus.kt +++ b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppStatus.kt @@ -33,10 +33,4 @@ sealed class AppStatus : Parcelable { @Parcelize object NoActionRequired : AppStatus(), Parcelable - - @Parcelize - data class NewFeatures(val appUpdateData: AppUpdateData) : AppStatus(), Parcelable - - @Parcelize - data class ConsentNeeded(val appUpdateData: AppUpdateData) : AppStatus(), Parcelable } diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppUpdateData.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppUpdateData.kt deleted file mode 100644 index 3616559e1..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/AppUpdateData.kt +++ /dev/null @@ -1,33 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.models - -import android.os.Parcelable -import java.io.Serializable -import kotlinx.parcelize.IgnoredOnParcel -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class AppUpdateData( - val newFeatures: List = listOf(), - val newTerms: NewTerms, - val newFeatureVersion: Int? = null, - val hideConsent: Boolean = false -) : Parcelable { - - @IgnoredOnParcel - var savePolicyChangeSerialized: Serializable? = null - private set - - fun setSavePolicyChange(f: () -> Unit) { - savePolicyChangeSerialized = f as Serializable - } - - @Suppress("UNCHECKED_CAST") - fun savePolicyChange() = (savePolicyChangeSerialized as? () -> Unit)?.invoke() -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/ExternalReturnAppData.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/ExternalReturnAppData.kt deleted file mode 100644 index 894656b67..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/ExternalReturnAppData.kt +++ /dev/null @@ -1,18 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.models - -import android.content.Intent -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class ExternalReturnAppData( - val appName: String, - val intent: Intent -) : Parcelable diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/NewFeatureItem.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/NewFeatureItem.kt deleted file mode 100644 index 63aff3f5f..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/NewFeatureItem.kt +++ /dev/null @@ -1,27 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.models - -import android.os.Parcelable -import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.appconfig.R - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class NewFeatureItem( - @DrawableRes val imageResource: Int, - @StringRes val titleResource: Int, - @StringRes val description: Int, - @ColorRes val subTitleColor: Int? = null, - @ColorRes val backgroundColor: Int? = null, - @StringRes val subtitleResource: Int = R.string.new_in_app_subtitle, - @StringRes val buttonResource: Int = R.string.onboarding_next, - @StringRes val lastButtonResource: Int = R.string.general_toMyOverview -) : Parcelable diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/NewTerms.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/NewTerms.kt deleted file mode 100644 index 4034b0a97..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/models/NewTerms.kt +++ /dev/null @@ -1,17 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.models - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class NewTerms( - val version: Int, - val needsConsent: Boolean -) : Parcelable diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/ConfigHttpException.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/ConfigHttpException.kt deleted file mode 100644 index acef17baa..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/ConfigHttpException.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.appconfig.repositories - -import retrofit2.HttpException -import retrofit2.Response - -class ConfigHttpException(response: Response<*>) : HttpException(response) diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/ConfigRepository.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/ConfigRepository.kt deleted file mode 100644 index cae26b802..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/ConfigRepository.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.repositories - -import nl.rijksoverheid.ctr.appconfig.api.AppConfigApi -import nl.rijksoverheid.ctr.appconfig.models.ConfigResponse - -interface ConfigRepository { - suspend fun getConfig(): ConfigResponse - suspend fun getPublicKeys(): String -} - -class ConfigRepositoryImpl(private val api: AppConfigApi) : ConfigRepository { - override suspend fun getConfig(): ConfigResponse { - val response = api.getConfig() - val responseBody = response.body()?.string() - - if (!response.isSuccessful || responseBody == null) { - throw ConfigHttpException(response) - } - - return ConfigResponse( - body = responseBody.toString(), - headers = response.headers() - ) - } - - override suspend fun getPublicKeys(): String { - val response = api.getPublicKeys() - val responseBody = response.body() - - if (!response.isSuccessful || responseBody == null) { - throw PublicKeysHttpException(response) - } - - return responseBody.toString() - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/PublicKeysHttpException.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/PublicKeysHttpException.kt deleted file mode 100644 index ab7f5f6dd..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/repositories/PublicKeysHttpException.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.appconfig.repositories - -import org.json.JSONObject -import retrofit2.HttpException -import retrofit2.Response - -class PublicKeysHttpException(response: Response) : HttpException(response) diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigFreshnessUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigFreshnessUseCase.kt deleted file mode 100644 index c749f4055..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigFreshnessUseCase.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import java.time.Clock -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager - -interface AppConfigFreshnessUseCase { - fun getAppConfigLastFetchedSeconds(): Long - fun getAppConfigMaxValidityTimestamp(): Long - fun shouldShowConfigFreshnessWarning(): Boolean -} - -class AppConfigFreshnessUseCaseImpl( - val appConfigPersistenceManager: AppConfigPersistenceManager, - val clock: Clock, - val cachedAppConfigUseCase: CachedAppConfigUseCase -) : AppConfigFreshnessUseCase { - override fun getAppConfigLastFetchedSeconds(): Long { - return appConfigPersistenceManager.getAppConfigLastFetchedSeconds() - } - - override fun getAppConfigMaxValidityTimestamp(): Long { - return OffsetDateTime.ofInstant( - Instant.ofEpochSecond(getAppConfigLastFetchedSeconds()), - ZoneOffset.UTC - ).plusSeconds(cachedAppConfigUseCase.getCachedAppConfig().configTtlSeconds.toLong()) - .toEpochSecond() - } - - override fun shouldShowConfigFreshnessWarning(): Boolean { - val lastFetched = getAppConfigLastFetchedSeconds() - val now = OffsetDateTime.now(clock) - val config = cachedAppConfigUseCase.getCachedAppConfig() - val configAlmostExpired = lastFetched <= now.minusSeconds(config.configTtlSeconds.toLong()) - .plusSeconds(config.configAlmostOutOfDateWarningSeconds.toLong()).toEpochSecond() - return lastFetched > 0 && configAlmostExpired - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigUseCase.kt deleted file mode 100644 index c629d4b83..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigUseCase.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import java.io.IOException -import java.time.Clock -import java.time.OffsetDateTime -import kotlin.math.abs -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.models.ServerTime -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.repositories.ConfigHttpException -import nl.rijksoverheid.ctr.appconfig.repositories.ConfigRepository -import nl.rijksoverheid.ctr.shared.factories.SharedStep -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import retrofit2.HttpException - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -interface AppConfigUseCase { - suspend fun get(): ConfigResult - fun canRefresh(cachedAppConfigUseCase: CachedAppConfigUseCase): Boolean -} - -class AppConfigUseCaseImpl( - private val clock: Clock, - private val appConfigPersistenceManager: AppConfigPersistenceManager, - private val configRepository: ConfigRepository, - private val clockDeviationUseCase: ClockDeviationUseCase -) : AppConfigUseCase { - override suspend fun get(): ConfigResult = withContext(Dispatchers.IO) { - try { - val config = configRepository.getConfig() - val success = ConfigResult.Success( - appConfig = config.body, - publicKeys = configRepository.getPublicKeys() - ) - - // the final server response timestamp is the timestamp of when the response firstly - // generated plus the amount of seconds passed since then (date and age headers respectively) - val serverDateMillis = config.headers.getDate("date")?.time ?: clock.millis() - val serverAgeSeconds = config.headers["Age"]?.toInt() ?: 0 - val serverAgeMillis = serverAgeSeconds * 1000 - val serverTime = ServerTime.Available( - serverTimeMillis = serverDateMillis + serverAgeMillis, - localTimeMillis = clock.millis() - ) - clockDeviationUseCase.store(serverTime) - appConfigPersistenceManager.saveAppConfigLastFetchedSeconds( - OffsetDateTime.now(clock).toEpochSecond() - ) - success - } catch (e: IOException) { - ConfigResult.Error( - NetworkRequestResult.Failed.ServerNetworkError( - step = SharedStep.ConfigurationNetworkRequest, - e = e - ) - ) - } catch (e: HttpException) { - ConfigResult.Error( - NetworkRequestResult.Failed.CoronaCheckHttpError( - step = if (e is ConfigHttpException) { - SharedStep.ConfigurationNetworkRequest - } else { - SharedStep.PublicKeysNetworkRequest - }, - e = e - ) - ) - } - } - - /** - * never refresh more than once per configMinimumInterval (1 hour on prod) - * in order not to track users by network requests - */ - override fun canRefresh(cachedAppConfigUseCase: CachedAppConfigUseCase): Boolean { - val lastTimeRefreshed = appConfigPersistenceManager.getAppConfigLastFetchedSeconds() - val minimumRefreshInterval = - cachedAppConfigUseCase.getCachedAppConfig().configMinimumIntervalSeconds - val nowSeconds = OffsetDateTime.now(clock).toEpochSecond() - - // Check absolute value to handle clock deviations - return abs(nowSeconds - lastTimeRefreshed) > minimumRefreshInterval - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppStatusUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppStatusUseCase.kt deleted file mode 100644 index d2cf6febb..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/AppStatusUseCase.kt +++ /dev/null @@ -1,18 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.usecases - -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface AppStatusUseCase { - suspend fun get(config: ConfigResult, currentVersionCode: Int): AppStatus - fun isAppActive(currentVersionCode: Int): Boolean - fun checkIfActionRequired(currentVersionCode: Int, appConfig: AppConfig): AppStatus -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/CachedAppConfigUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/CachedAppConfigUseCase.kt deleted file mode 100644 index 4c70bcc0d..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/CachedAppConfigUseCase.kt +++ /dev/null @@ -1,91 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.usecases - -import com.squareup.moshi.Moshi -import java.io.File -import java.security.MessageDigest -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.appconfig.api.model.HolderConfig -import nl.rijksoverheid.ctr.appconfig.api.model.VerifierConfig -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import nl.rijksoverheid.ctr.shared.ext.toObject -import org.json.JSONObject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -interface CachedAppConfigUseCase { - fun isCachedAppConfigValid(): Boolean - fun getCachedAppConfig(): AppConfig - fun getCachedAppConfigOrNull(): AppConfig? - fun getCachedAppConfigHash(): String -} - -class CachedAppConfigUseCaseImpl constructor( - private val appConfigStorageManager: AppConfigStorageManager, - private val filesDirPath: String, - private val moshi: Moshi, - private val isVerifierApp: Boolean -) : CachedAppConfigUseCase { - private val configFile = File(filesDirPath, "config.json") - private val publicKeysFile = File(filesDirPath, "public_keys.json") - - private val defaultConfig = if (isVerifierApp) { - VerifierConfig.default() - } else { - HolderConfig.default() - } - - override fun isCachedAppConfigValid(): Boolean { - return try { - if (isVerifierApp) { - appConfigStorageManager.getFileAsBufferedSource(configFile)?.toObject(moshi) is VerifierConfig - appConfigStorageManager.getFileAsBufferedSource(publicKeysFile)?.toObject(moshi)?.has("eu_keys") == true - } else { - appConfigStorageManager.getFileAsBufferedSource(configFile)?.toObject(moshi) is HolderConfig - } - } catch (exc: Exception) { - false - } - } - - override fun getCachedAppConfig(): AppConfig { - return getCachedAppConfigOrNull() ?: defaultConfig - } - - override fun getCachedAppConfigOrNull(): AppConfig? { - if (!configFile.exists()) { - return null - } - - return try { - val config = if (isVerifierApp) { - appConfigStorageManager.getFileAsBufferedSource(configFile) - ?.toObject(moshi) as? VerifierConfig - } else { - appConfigStorageManager.getFileAsBufferedSource(configFile) - ?.toObject(moshi) as? HolderConfig - } - return config - } catch (exc: Exception) { - null - } - } - - override fun getCachedAppConfigHash(): String { - val json = try { - appConfigStorageManager.getFileAsBufferedSource(configFile)?.replace("\\/", "/") ?: return "" - } catch (exc: Exception) { - return "" - } - val bytes = json.toByteArray() - val md = MessageDigest.getInstance("SHA-256") - val digest = md.digest(bytes) - // Return first 7 characters of hash - return digest.fold("") { str, it -> str + "%02x".format(it) }.subSequence(0, 7).toString() - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ClockDeviationUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ClockDeviationUseCase.kt deleted file mode 100644 index e202cc4b3..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ClockDeviationUseCase.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import android.os.SystemClock -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import java.time.Clock -import java.util.concurrent.TimeUnit -import kotlin.math.abs -import nl.rijksoverheid.ctr.appconfig.models.ServerTime -import nl.rijksoverheid.ctr.shared.livedata.Event - -abstract class ClockDeviationUseCase { - val serverTimeSyncedLiveData: LiveData> = MutableLiveData() - - abstract fun store(serverTime: ServerTime) - abstract fun hasDeviation(): Boolean - abstract fun calculateServerTimeOffsetMillis(): Long -} - -class ClockDeviationUseCaseImpl( - private val clock: Clock, - private val cachedAppConfigUseCase: CachedAppConfigUseCase -) : ClockDeviationUseCase() { - - private var cachedServerTime: ServerTime = ServerTime.NotAvailable - - override fun store(serverTime: ServerTime) { - this.cachedServerTime = serverTime - (serverTimeSyncedLiveData as MutableLiveData).postValue(Event(Unit)) - } - - override fun hasDeviation(): Boolean { - val thresholdInSeconds = cachedAppConfigUseCase.getCachedAppConfig().clockDeviationThresholdSeconds - val serverTimeOffsetSeconds = TimeUnit.MILLISECONDS.toSeconds(abs(calculateServerTimeOffsetMillis())) - return serverTimeOffsetSeconds >= thresholdInSeconds - } - - /** - * Gets the calculated offset from server time - * A negative offset means the device clock is behind, a positive offset means the device is ahead - * @return the offset in millis - */ - override fun calculateServerTimeOffsetMillis(): Long { - when (val serverInfo = cachedServerTime) { - is ServerTime.NotAvailable -> { - return 0L - } - is ServerTime.Available -> { - val currentUptime = SystemClock.elapsedRealtime() - val currentMillis = clock.instant().toEpochMilli() - - val systemStartMillis = currentMillis - currentUptime - val responseStartMillis = serverInfo.localTimeMillis - serverInfo.uptime - - val responseTimeDelta = serverInfo.localTimeMillis - serverInfo.serverTimeMillis - val systemUptimeDelta = systemStartMillis - responseStartMillis - - return systemUptimeDelta + responseTimeDelta - } - } - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ConfigResultUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ConfigResultUseCase.kt deleted file mode 100644 index 170fb00d4..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ConfigResultUseCase.kt +++ /dev/null @@ -1,40 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.usecases - -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface ConfigResultUseCase { - suspend fun fetch(): ConfigResult -} - -class ConfigResultUseCaseImpl( - private val appConfigUseCase: AppConfigUseCase, - private val persistConfigUseCase: PersistConfigUseCase -) : ConfigResultUseCase { - - private val mutex = Mutex() - - override suspend fun fetch(): ConfigResult { - // allow only one config/public keys refresh at a time - // cause we store them writing to files and a parallel - // operation could break them eventually - mutex.withLock { - val configResult = appConfigUseCase.get() - if (configResult is ConfigResult.Success) { - persistConfigUseCase.persist( - appConfigContents = configResult.appConfig, - publicKeyContents = configResult.publicKeys - ) - } - return configResult - } - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/DeleteConfigUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/DeleteConfigUseCase.kt new file mode 100644 index 000000000..e5204160c --- /dev/null +++ b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/DeleteConfigUseCase.kt @@ -0,0 +1,34 @@ +package nl.rijksoverheid.ctr.appconfig.usecases + +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager +import nl.rijksoverheid.ctr.appconfig.persistence.StorageResult + +/* + * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. + * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 + * + * SPDX-License-Identifier: EUPL-1.2 + * + */ + +interface DeleteConfigUseCase { + suspend operator fun invoke() +} + +class DeleteConfigUseCaseImpl( + private val filesDirPath: String +) : DeleteConfigUseCase { + + override suspend operator fun invoke() { + withContext(Dispatchers.IO) { + val configFile = File(filesDirPath, "config.json") + val publicKeysFile = File(filesDirPath, "public_keys.json") + + configFile.delete() + publicKeysFile.delete() + } + } +} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/PersistConfigUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/PersistConfigUseCase.kt deleted file mode 100644 index 9ebe9b0cb..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/PersistConfigUseCase.kt +++ /dev/null @@ -1,43 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.usecases - -import java.io.File -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import nl.rijksoverheid.ctr.appconfig.persistence.StorageResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -interface PersistConfigUseCase { - suspend fun persist(appConfigContents: String, publicKeyContents: String): StorageResult -} - -class PersistConfigUseCaseImpl( - private val appConfigStorageManager: AppConfigStorageManager, - private val filesDirPath: String -) : PersistConfigUseCase { - - override suspend fun persist(appConfigContents: String, publicKeyContents: String) = - withContext(Dispatchers.IO) { - - val publicKeysFile = File(filesDirPath, "public_keys.json") - val publicKeysStorageResult = appConfigStorageManager.storageFile(publicKeysFile, publicKeyContents) - if (publicKeysStorageResult is StorageResult.Error) { - return@withContext publicKeysStorageResult - } - - val configFile = File(filesDirPath, "config.json") - val configStorageResult = appConfigStorageManager.storageFile(configFile, appConfigContents) - if (configStorageResult is StorageResult.Error) { - return@withContext configStorageResult - } - - return@withContext StorageResult.Success - } -} diff --git a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ReturnToExternalAppUseCase.kt b/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ReturnToExternalAppUseCase.kt deleted file mode 100644 index 60fa75b61..000000000 --- a/appconfig/src/main/java/nl/rijksoverheid/ctr/appconfig/usecases/ReturnToExternalAppUseCase.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * - * * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * * - * * SPDX-License-Identifier: EUPL-1.2 - * * - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import android.content.Intent -import android.net.Uri -import nl.rijksoverheid.ctr.appconfig.models.ExternalReturnAppData - -interface ReturnToExternalAppUseCase { - - /** - * Determine whether the given uri from an external app is valid to return to - * - * @param[uri] the uri given by an external app when this app is entered through deep link. - * @return Return app data with app name and intent or null when given uri is illegal. - * - */ - fun get(uri: String): ExternalReturnAppData? -} - -class ReturnToExternalAppUseCaseImpl( - val cachedAppConfigUseCase: CachedAppConfigUseCase -) : ReturnToExternalAppUseCase { - - override fun get(uri: String): ExternalReturnAppData? { - return cachedAppConfigUseCase.getCachedAppConfig().deeplinkDomains - .firstOrNull { uri.contains("https://${it.url}") } - ?.let { - ExternalReturnAppData( - appName = it.name, - intent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(uri) } - ) - } - } -} diff --git a/appconfig/src/main/res/navigation/app_status_nav_graph.xml b/appconfig/src/main/res/navigation/app_status_nav_graph.xml index 8988c3dc5..b8c266b1b 100644 --- a/appconfig/src/main/res/navigation/app_status_nav_graph.xml +++ b/appconfig/src/main/res/navigation/app_status_nav_graph.xml @@ -11,19 +11,9 @@ - - - - + app:argType="nl.rijksoverheid.ctr.appconfig.models.AppStatus" + app:nullable="true" + android:defaultValue="@null"/> - - - - - - - - - - - - - - - - diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModelTest.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModelTest.kt deleted file mode 100644 index f751df0f6..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/AppConfigViewModelTest.kt +++ /dev/null @@ -1,172 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig - -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import java.io.IOException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.UnconfinedTestDispatcher -import kotlinx.coroutines.test.setMain -import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.AppStatusUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.factories.SharedStep -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import okio.BufferedSource -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import retrofit2.HttpException - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class AppConfigViewModelTest { - - @get:Rule - val rule = InstantTaskExecutorRule() - - private val filesDirPath = "/files" - - private val appConfigUseCase = mockk(relaxed = true).apply { - every { canRefresh(any()) } returns true - } - private val appStatusUseCase: AppStatusUseCase = mockk(relaxed = true) - private val configResultUseCase: ConfigResultUseCase = mockk(relaxed = true) - private val appConfigStorageManager: AppConfigStorageManager = mockk(relaxed = true) - private val cachedAppConfigUseCase: CachedAppConfigUseCase = mockk(relaxed = true) - - private fun appConfigViewModel(isVerifier: Boolean = false) = AppConfigViewModelImpl( - appConfigUseCase = appConfigUseCase, - appStatusUseCase = appStatusUseCase, - configResultUseCase = configResultUseCase, - appConfigStorageManager = appConfigStorageManager, - cachedAppConfigUseCase = cachedAppConfigUseCase, - filesDirPath = filesDirPath, - isVerifierApp = isVerifier, - versionCode = 0, - appUpdateData = mockk(), - appUpdatePersistenceManager = mockk(), - errorCodeStringFactory = mockk(relaxed = true) - ) - - private val mobileCoreWrapper: MobileCoreWrapper = mockk(relaxed = true) - - @Before - fun setup() { - Dispatchers.setMain(UnconfinedTestDispatcher()) - } - - @Test - fun `given a happy flow on holder, then calls correct usecases only`() = runBlocking { - val appConfigContents = "app config contents" - - val publicKeys = mockk() - val publicKeysContents = "file contents" - coEvery { publicKeys.readUtf8() } returns publicKeysContents - - coEvery { appConfigUseCase.get() } answers { - ConfigResult.Success( - appConfig = appConfigContents, - publicKeys = publicKeysContents - ) - } - coEvery { appConfigStorageManager.areConfigFilesPresentInFilesFolder() } returns true - coEvery { appStatusUseCase.get(any(), any()) } answers { AppStatus.NoActionRequired } - coEvery { mobileCoreWrapper.initializeHolder(filesDirPath) } returns null - coEvery { cachedAppConfigUseCase.isCachedAppConfigValid() } returns true - - appConfigViewModel().refresh(mobileCoreWrapper) - - coVerify { configResultUseCase.fetch() } - coVerify { mobileCoreWrapper.initializeHolder(filesDirPath) } - coVerify(exactly = 0) { mobileCoreWrapper.initializeVerifier(filesDirPath) } - } - - @Test - fun `given a happy flow on verifier, then calls correct usecases only`() = runBlocking { - val appConfigContents = "app config contents" - - val publicKeys = mockk() - val publicKeysContents = "file contents" - coEvery { publicKeys.readUtf8() } returns publicKeysContents - - coEvery { appConfigUseCase.get() } answers { - ConfigResult.Success( - appConfig = appConfigContents, - publicKeys = publicKeysContents - ) - } - coEvery { appConfigStorageManager.areConfigFilesPresentInFilesFolder() } returns true - coEvery { appStatusUseCase.get(any(), any()) } answers { AppStatus.NoActionRequired } - coEvery { mobileCoreWrapper.initializeVerifier(filesDirPath) } returns null - coEvery { cachedAppConfigUseCase.isCachedAppConfigValid() } returns true - - appConfigViewModel(true).refresh(mobileCoreWrapper) - - coVerify { configResultUseCase.fetch() } - coVerify(exactly = 0) { mobileCoreWrapper.initializeHolder(any()) } - coVerify { mobileCoreWrapper.initializeVerifier(filesDirPath) } - } - - @Test - fun `refresh calls emits status to livedata`() = runBlocking { - coEvery { appConfigUseCase.get() } answers { - ConfigResult.Error(mockk()) - } - - coEvery { configResultUseCase.fetch() } answers { - ConfigResult.Error( - NetworkRequestResult.Failed.Error( - step = SharedStep.ConfigurationNetworkRequest, - e = HttpException(mockk(relaxed = true)) - ) - ) - } - - coEvery { appStatusUseCase.get(any(), any()) } answers { AppStatus.Error } - - val viewModel = appConfigViewModel() - viewModel.refresh(mobileCoreWrapper) - - assertTrue(viewModel.appStatusLiveData.value is AppStatus.LaunchError) - } - - @Test - fun `refresh with no config files in verifier app emits internet required status`() = - runBlocking { - val publicKeys = mockk() - val publicKeysContents = "file contents" - coEvery { publicKeys.readUtf8() } returns publicKeysContents - - coEvery { configResultUseCase.fetch() } answers { - ConfigResult.Error( - NetworkRequestResult.Failed.ServerNetworkError( - step = SharedStep.ConfigurationNetworkRequest, - e = IOException() - ) - ) - } - - coEvery { appStatusUseCase.get(any(), any()) } answers { AppStatus.NoActionRequired } - coEvery { appConfigStorageManager.areConfigFilesPresentInFilesFolder() } returns false - - val viewModel = appConfigViewModel(true) - viewModel.refresh(mobileCoreWrapper) - - assertTrue(viewModel.appStatusLiveData.value is AppStatus.LaunchError) - } -} diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/Fakes.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/Fakes.kt deleted file mode 100644 index fa730ac36..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/Fakes.kt +++ /dev/null @@ -1,75 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig - -import java.security.MessageDigest -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.appconfig.api.model.HolderConfig -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -fun fakeAppConfigPersistenceManager( - lastFetchedSeconds: Long = 0L -) = object : AppConfigPersistenceManager { - - override fun getAppConfigLastFetchedSeconds(): Long { - return lastFetchedSeconds - } - - override fun saveAppConfigLastFetchedSeconds(seconds: Long) { - } -} - -fun fakeCachedAppConfigUseCase( - appConfig: AppConfig = fakeAppConfig() -) = object : CachedAppConfigUseCase { - - override fun isCachedAppConfigValid(): Boolean { - TODO("Not yet implemented") - } - - override fun getCachedAppConfig(): AppConfig { - return appConfig - } - - override fun getCachedAppConfigOrNull(): AppConfig? { - return appConfig - } - - override fun getCachedAppConfigHash(): String { - val bytes = getCachedAppConfig().toString().toByteArray() - val md = MessageDigest.getInstance("SHA-256") - val digest = md.digest(bytes) - // Return first 7 characters of hash - return digest.fold("", { str, it -> str + "%02x".format(it) }).subSequence(0, 7).toString() - } -} - -fun fakeAppConfig( - minimumVersion: Int = 1, - appDeactivated: Boolean = false, - informationURL: String = "", - configTtlSeconds: Int = 0, - maxValidityHours: Int = 0, - holderConfigAlmostOutOfDateWarningSeconds: Int = 0 -) = HolderConfig.default( - holderMinimumVersion = minimumVersion, - holderAppDeactivated = appDeactivated, - holderInformationURL = informationURL, - configTTL = configTtlSeconds, - holderConfigAlmostOutOfDateWarningSeconds = holderConfigAlmostOutOfDateWarningSeconds, - maxValidityHours = maxValidityHours, - euLaunchDate = "", - credentialRenewalDays = 0, - domesticCredentialValidity = 0, - testEventValidityHours = 0, - recoveryEventValidityDays = 0, - temporarilyDisabled = false, - requireUpdateBefore = 0, - ggdEnabled = true -) diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigFreshnessUseCaseImplTest.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigFreshnessUseCaseImplTest.kt deleted file mode 100644 index 4f23611a3..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigFreshnessUseCaseImplTest.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import java.time.Clock -import java.time.Instant -import java.time.ZoneId -import java.util.concurrent.TimeUnit -import nl.rijksoverheid.ctr.appconfig.fakeAppConfig -import nl.rijksoverheid.ctr.appconfig.fakeAppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.fakeCachedAppConfigUseCase -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@RunWith(RobolectricTestRunner::class) -class AppConfigFreshnessUseCaseImplTest { - - @Test - fun `config almost expired`() { - val clock = Clock.fixed(Instant.parse("2022-04-25T09:00:00.00Z"), ZoneId.of("UTC")) - val appConfigFreshnessUseCase = AppConfigFreshnessUseCaseImpl( - fakeAppConfigPersistenceManager( - lastFetchedSeconds = TimeUnit.SECONDS.convert( - clock.millis(), - TimeUnit.MILLISECONDS - ) - 8 - ), - clock, - fakeCachedAppConfigUseCase( - appConfig = fakeAppConfig( - configTtlSeconds = 10, - holderConfigAlmostOutOfDateWarningSeconds = 5 - ) - ) - ) - assertTrue(appConfigFreshnessUseCase.shouldShowConfigFreshnessWarning()) - } - - @Test - fun `config not expiring`() { - val clock = Clock.fixed(Instant.parse("2022-04-25T09:00:00.00Z"), ZoneId.of("UTC")) - val appConfigFreshnessUseCase = AppConfigFreshnessUseCaseImpl( - fakeAppConfigPersistenceManager( - lastFetchedSeconds = TimeUnit.SECONDS.convert( - clock.millis(), - TimeUnit.MILLISECONDS - ) - 4 - ), - clock, - fakeCachedAppConfigUseCase( - appConfig = fakeAppConfig( - configTtlSeconds = 10, - holderConfigAlmostOutOfDateWarningSeconds = 5 - ) - ) - ) - assertFalse(appConfigFreshnessUseCase.shouldShowConfigFreshnessWarning()) - } - - @Test - fun `config starts expiring`() { - val clock = Clock.fixed(Instant.parse("2022-04-25T09:00:00.00Z"), ZoneId.of("UTC")) - val appConfigFreshnessUseCase = AppConfigFreshnessUseCaseImpl( - fakeAppConfigPersistenceManager( - lastFetchedSeconds = TimeUnit.SECONDS.convert( - clock.millis(), - TimeUnit.MILLISECONDS - ) - 5 - ), - clock, - fakeCachedAppConfigUseCase( - appConfig = fakeAppConfig( - configTtlSeconds = 10, - holderConfigAlmostOutOfDateWarningSeconds = 5 - ) - ) - ) - assertTrue(appConfigFreshnessUseCase.shouldShowConfigFreshnessWarning()) - } -} diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigUseCaseImplTest.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigUseCaseImplTest.kt deleted file mode 100644 index 61722339d..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/AppConfigUseCaseImplTest.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import java.io.IOException -import java.time.Clock -import java.time.Instant -import java.time.ZoneId -import kotlinx.coroutines.runBlocking -import nl.rijksoverheid.ctr.appconfig.api.AppConfigApi -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.repositories.ConfigRepositoryImpl -import nl.rijksoverheid.ctr.appconfig.repositories.PublicKeysHttpException -import okhttp3.ResponseBody -import okhttp3.ResponseBody.Companion.toResponseBody -import org.json.JSONObject -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import retrofit2.Response - -@RunWith(RobolectricTestRunner::class) -class AppConfigUseCaseImplTest { - - private val appConfig = Response.success("{}".toResponseBody()) - - private val publicKeys = Response.success(JSONObject()) - - private val clock = Clock.fixed(Instant.ofEpochSecond(0), ZoneId.of("UTC")) - private val appConfigPersistenceManager = mockk(relaxed = true) - private val clockDeviationUseCase = mockk(relaxed = true) - - private val cachedAppConfigUseCase = mockk().apply { - every { getCachedAppConfig() } returns object : - AppConfig(true, "", 1, 3600, 100, emptyList(), 1, 1, listOf(), 30, 300, mockk()) {} - } - - @Test - fun `config returns Success when both calls succeed`() = runBlocking { - val fakeApi = object : AppConfigApi { - override suspend fun getConfig(): Response = appConfig - override suspend fun getPublicKeys(): Response = publicKeys - } - val configRepository = ConfigRepositoryImpl(api = fakeApi) - val appConfigUseCase = - AppConfigUseCaseImpl( - clock, - appConfigPersistenceManager, - configRepository, - clockDeviationUseCase - ) - assertEquals( - ConfigResult.Success( - appConfig = "{}", - publicKeys = "{}" - ), - appConfigUseCase.get() - ) - coVerify { appConfigPersistenceManager.saveAppConfigLastFetchedSeconds(0) } - } - - @Test - fun `config returns Error when config call fails`() = runBlocking { - val fakeApi = object : AppConfigApi { - override suspend fun getConfig(): Response { - throw IOException() - } - - override suspend fun getPublicKeys(): Response = publicKeys - } - val configRepository = ConfigRepositoryImpl(api = fakeApi) - val appConfigUseCase = - AppConfigUseCaseImpl( - clock, - appConfigPersistenceManager, - configRepository, - clockDeviationUseCase - ) - assertTrue( - appConfigUseCase.get() is ConfigResult.Error - ) - coVerify(exactly = 0) { appConfigPersistenceManager.saveAppConfigLastFetchedSeconds(0) } - } - - @Test - fun `config returns Error when public keys call fails`() = runBlocking { - val fakeApi = object : AppConfigApi { - override suspend fun getConfig(): Response = appConfig - override suspend fun getPublicKeys(): Response { - throw PublicKeysHttpException(publicKeys) - } - } - val configRepository = ConfigRepositoryImpl(api = fakeApi) - val appConfigUseCase = - AppConfigUseCaseImpl( - clock, - appConfigPersistenceManager, - configRepository, - clockDeviationUseCase - ) - - assertTrue( - appConfigUseCase.get() is ConfigResult.Error - ) - coVerify(exactly = 0) { appConfigPersistenceManager.saveAppConfigLastFetchedSeconds(0) } - } - - @Test - fun `given config refreshed 60 seconds ago with 100 seconds minimum interval, when try to refresh it, then it does not refresh`() { - val now = Instant.parse("2021-10-20T12:00:00.00Z") - val appConfigLastFetchedSeconds = now.minusSeconds(60).epochSecond - val clock = Clock.fixed(now, ZoneId.of("UTC")) - every { appConfigPersistenceManager.getAppConfigLastFetchedSeconds() } returns appConfigLastFetchedSeconds - val appConfigUseCase = - AppConfigUseCaseImpl( - clock, - appConfigPersistenceManager, - mockk(), - clockDeviationUseCase - ) - - val canRefresh = appConfigUseCase.canRefresh(cachedAppConfigUseCase) - - assertFalse(canRefresh) - } - - @Test - fun `given config refreshed 600 seconds ago with 100 seconds minimum interval, when try to refresh it, then it does not refresh`() { - val now = Instant.parse("2021-10-20T12:00:00.00Z") - val appConfigLastFetchedSeconds = now.minusSeconds(600).epochSecond - val clock = Clock.fixed(now, ZoneId.of("UTC")) - every { appConfigPersistenceManager.getAppConfigLastFetchedSeconds() } returns appConfigLastFetchedSeconds - val appConfigUseCase = - AppConfigUseCaseImpl( - clock, - appConfigPersistenceManager, - mockk(), - clockDeviationUseCase - ) - - val canRefresh = appConfigUseCase.canRefresh(cachedAppConfigUseCase) - - assertTrue(canRefresh) - } -} diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/CachedAppConfigUseCaseImplTest.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/CachedAppConfigUseCaseImplTest.kt deleted file mode 100644 index 4102806be..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/CachedAppConfigUseCaseImplTest.kt +++ /dev/null @@ -1,103 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.usecases - -import com.squareup.moshi.Moshi -import io.mockk.every -import io.mockk.mockk -import java.io.File -import java.nio.file.Paths -import nl.rijksoverheid.ctr.api.json.OffsetDateTimeJsonAdapter -import nl.rijksoverheid.ctr.appconfig.api.model.HolderConfig -import nl.rijksoverheid.ctr.appconfig.api.model.VerifierConfig -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test - -class CachedAppConfigUseCaseImplTest { - - private val moshi = Moshi.Builder().add(OffsetDateTimeJsonAdapter()).build() - private val appConfigStorageManager = mockk(relaxed = true) - - private fun mockWithFileContents(fileContents: String) { - every { appConfigStorageManager.getFileAsBufferedSource(any()) } returns fileContents - } - - private val configFilePath = Paths.get(File(this.javaClass.classLoader!!.getResource("config.json").file).absolutePath).parent.toString() - - @Test - fun `valid file returns true`() { - mockWithFileContents(HolderConfig.default().toJson(moshi)) - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, "", moshi, false) - - assertTrue(cachedAppConfigUseCase.isCachedAppConfigValid()) - } - - @Test - fun `different parseable file type returns false`() { - mockWithFileContents("{\"bar\":\"foo\"}") - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, "", moshi, false) - - assertFalse(cachedAppConfigUseCase.isCachedAppConfigValid()) - } - - @Test - fun `invalid file returns false`() { - mockWithFileContents("{\"androidMinimumVersion\":1025,\"appDeactivated\":false,\"informationURL\":\"https://coronacheck.nl\",\"requireUpdateBefore\":1620781181,\"temporarilyDisabled\":false,\"recoveryEventValidity\":7300,\"testEventValidity\":40,\"domesticCredentialValidity\":24,\"credentialRenewalDays\":5,\"configTTL\":259200,\"maxValidityHours\":40,\"vaccinationEventValidity\":14600,\"euLaunchDate\":\"2021-07-01T00:00:00Z\",\"hpkC") - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, "", moshi, false) - - assertFalse(cachedAppConfigUseCase.isCachedAppConfigValid()) - } - - @Test - fun `when it's the verifier app, app config should be verifier config`() { - mockWithFileContents(VerifierConfig.default(verifierInformationURL = "test").toJson(moshi)) - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, configFilePath, moshi, true) - - cachedAppConfigUseCase.getCachedAppConfig().run { - assertTrue(this is VerifierConfig) - assertEquals("test", informationURL) - } - } - - @Test - fun `when it's the holder app, app config should be holder config`() { - mockWithFileContents(HolderConfig.default(holderInformationURL = "test").toJson(moshi)) - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, configFilePath, moshi, false) - - cachedAppConfigUseCase.getCachedAppConfig().run { - assertTrue(this is HolderConfig) - assertEquals(informationURL, "test") - } - } - - @Test - fun `get default app config when file can't be parsed`() { - every { appConfigStorageManager.getFileAsBufferedSource(any()) } returns null - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, "", moshi, false) - - assertEquals(cachedAppConfigUseCase.getCachedAppConfig(), HolderConfig.default()) - } - - @Test - fun `given a config payload, check if config hash is correct`() { - every { appConfigStorageManager.getFileAsBufferedSource(any()) } returns "{\"androidMinimumVersion\":1,\"androidRecommendedVersion\":2046,\"androidMinimumVersionMessage\":\"Om de app te gebruiken heb je de laatste versie uit de store nodig.\",\"playStoreURL\":\"https:\\/\\/play.google.com\\/store\\/apps\\/details?id=nl.rijksoverheid.ctr.verifier\",\"iosMinimumVersion\":\"1.0.0\",\"iosRecommendedVersion\":\"2.2.0\",\"iosMinimumVersionMessage\":\"Om de app te gebruiken heb je de laatste versie uit de store nodig.\",\"iosAppStoreURL\":\"https:\\/\\/apps.apple.com\\/nl\\/app\\/scanner-voor-coronacheck\\/id1549842661\",\"appDeactivated\":false,\"configTTL\":300,\"configMinimumIntervalSeconds\":60,\"upgradeRecommendationInterval\":24,\"maxValidityHours\":40,\"clockDeviationThresholdSeconds\":30,\"informationURL\":\"https:\\/\\/coronacheck.nl\",\"defaultEvent\":\"cce4158f-582f-49c0-9d4d-611ce3866999\",\"universalLinkDomains\":[{\"url\":\"web.acc.coronacheck.nl\",\"name\":\"CoronaCheck app\"}],\"domesticVerificationRules\":{\"qrValidForSeconds\":60,\"proofIdentifierDenylist\":{\"STFNx7A24ZI1u5WDX8X9BA==\":true}},\"europeanVerificationRules\":{\"testAllowedTypes\":[\"LP217198-3\",\"LP6464-4\"],\"testValidityHours\":25,\"vaccinationValidityDelayBasedOnVaccinationDate\":true,\"vaccinationValidityDelayIntoForceDate\":\"2021-07-06\",\"vaccinationValidityDelayDays\":14,\"vaccinationJanssenValidityDelayDays\":28,\"vaccinationJanssenValidityDelayIntoForceDate\":\"2021-07-24\",\"vaccineAllowedProducts\":[\"EU\\/1\\/20\\/1528\",\"EU\\/1\\/20\\/1507\",\"EU\\/1\\/21\\/1529\",\"EU\\/1\\/20\\/1525\",\"Covishield\",\"BBIBP-CorV\",\"CoronaVac\"],\"recoveryValidFromDays\":11,\"recoveryValidUntilDays\":180,\"proofIdentifierDenylist\":{\"7EXmXBhfyBZJgt1dki0cfQ==\":true}}}" - - val cachedAppConfigUseCase = - CachedAppConfigUseCaseImpl(appConfigStorageManager, "", moshi, false) - - assertEquals("5f253c8", cachedAppConfigUseCase.getCachedAppConfigHash()) - } -} diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/ClockDeviationUseCaseImplTest.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/ClockDeviationUseCaseImplTest.kt deleted file mode 100644 index c252fc3fd..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/ClockDeviationUseCaseImplTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.appconfig.usecases - -import java.time.Clock -import java.time.Duration -import nl.rijksoverheid.ctr.appconfig.fakeCachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.models.ServerTime -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@RunWith(RobolectricTestRunner::class) -class ClockDeviationUseCaseImplTest { - private val defaultClock = Clock.systemUTC() - - @Test - fun `Clock deviation usecase returns false if clock is correct`() { - val clockDeviationUseCase = ClockDeviationUseCaseImpl( - defaultClock, fakeCachedAppConfigUseCase() - ) - clockDeviationUseCase.store( - ServerTime.Available( - serverTimeMillis = defaultClock.millis(), - localTimeMillis = defaultClock.millis() - ) - ) - val hasDeviation = clockDeviationUseCase.hasDeviation() - assertFalse(hasDeviation) - } - - @Test - fun `Clock deviation usecase returns true if clock is off by more than threshold in the future`() { - val deviatedClock = Clock.offset(defaultClock, Duration.ofMinutes(10L)) - val clockDeviationUseCase = ClockDeviationUseCaseImpl( - deviatedClock, fakeCachedAppConfigUseCase() - ) - clockDeviationUseCase.store( - ServerTime.Available( - serverTimeMillis = defaultClock.millis(), - localTimeMillis = deviatedClock.millis() - ) - ) - val hasDeviation = clockDeviationUseCase.hasDeviation() - assertTrue(hasDeviation) - } - - @Test - fun `Clock deviation usecase returns true if clock is off by more than threshold in the past`() { - val deviatedClock = Clock.offset(defaultClock, Duration.ofMinutes(-10L)) - val clockDeviationUseCase = ClockDeviationUseCaseImpl( - deviatedClock, fakeCachedAppConfigUseCase() - ) - clockDeviationUseCase.store( - ServerTime.Available( - serverTimeMillis = defaultClock.millis(), - localTimeMillis = deviatedClock.millis() - ) - ) - val hasDeviation = clockDeviationUseCase.hasDeviation() - assertTrue(hasDeviation) - } - - @Test - fun `Clock deviation usecase returns false if clock is off by less than threshold`() { - val deviatedClock = Clock.offset(defaultClock, Duration.ofSeconds(10L)) - val clockDeviationUseCase = ClockDeviationUseCaseImpl( - deviatedClock, fakeCachedAppConfigUseCase() - ) - clockDeviationUseCase.store( - ServerTime.Available( - serverTimeMillis = defaultClock.millis(), - localTimeMillis = deviatedClock.millis() - ) - ) - val hasDeviation = clockDeviationUseCase.hasDeviation() - assertFalse(hasDeviation) - } -} diff --git a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/PersistConfigUseCaseImplTest.kt b/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/PersistConfigUseCaseImplTest.kt deleted file mode 100644 index dfa0caa7c..000000000 --- a/appconfig/src/test/java/nl/rijksoverheid/ctr/appconfig/usecases/PersistConfigUseCaseImplTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.usecases - -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import kotlinx.coroutines.runBlocking -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import okio.BufferedSource -import org.junit.Test - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class PersistConfigUseCaseImplTest { - - private val appConfigPersistenceManager: AppConfigPersistenceManager = mockk(relaxed = true) - private val appConfigStorageManager: AppConfigStorageManager = mockk(relaxed = true) - - @Test - fun `Usecase persists configs locally`() = runBlocking { - val appConfig = "{}" - - val publicKeys = mockk() - coEvery { publicKeys.readUtf8() } returns "file contents" - - val usecase = PersistConfigUseCaseImpl( - appConfigStorageManager = appConfigStorageManager, - filesDirPath = "" - ) - - usecase.persist( - appConfigContents = appConfig, - publicKeyContents = publicKeys.readUtf8() - ) - - coVerify { appConfigStorageManager.storageFile(any(), any()) } - } -} diff --git a/holder/build.gradle b/holder/build.gradle index 41b597d4e..83ad6aa14 100644 --- a/holder/build.gradle +++ b/holder/build.gradle @@ -11,7 +11,7 @@ def appVersionCode = 1000000 if (System.getenv("GITHUB_REPOSITORY_OWNER") == "minvws") { appVersionCode = System.getenv("VERSION_NUMBER") != null ? System.getenv("VERSION_NUMBER").toInteger() : 1000000 } -version = "5.0.1" +version = "5.1.0" archivesBaseName = "holder-v${version}-${appVersionCode}" android { @@ -47,17 +47,6 @@ android { testInstrumentationRunner "com.karumi.shot.ShotTestRunner" testInstrumentationRunnerArguments clearPackageData: 'true' - buildConfigField "String", "SIGNATURE_CERTIFICATE_CN_MATCH", '".coronatester.nl"' - buildConfigField "boolean", "FEATURE_CORONA_CHECK_API_CHECKS", "true" - buildConfigField "boolean", "FEATURE_TEST_PROVIDER_API_CHECKS", "true" - buildConfigField "String", "OPEN_ID_REDIRECT_URL", "\"https://coronacheck.nl/app/auth2\"" - buildConfigField "String", "BASE_API_URL", "\"https://holder-api.coronacheck.nl/v9/\"" - buildConfigField "String", "MAX_BASE_URL", "\"https://max.coronacheck.nl\"" - buildConfigField "String", "OPEN_ID_CLIENT_ID", "\"cc_app\"" - buildConfigField "String", "DEEPLINK_SCANNER_TEST_URL", "\"\"" - buildConfigField "String", "MIJNCN_BASEURL", "\"https://emax.coronacheck.nl\"" - buildConfigField "String", "PAP_BASE_URL", "\"https://qrcodezonderdigid.ggdghor.nl\"" - buildConfigField "String", "CDN_API_URL", "\"https://holder-api-cdn.coronacheck.nl/v9/\"" manifestPlaceholders = [appLabel: "@string/app_name", deepLinkHost: "coronacheck.nl", digidSchema: "coronacheck"] } @@ -73,14 +62,6 @@ android { flavorDimensions "environment" productFlavors { acc { - buildConfigField "String", "BASE_API_URL", "\"https://holder-api.acc.coronacheck.nl/v9/\"" - buildConfigField "String", "CDN_API_URL", "\"https://holder-api-cdn.acc.coronacheck.nl/v9/\"" - buildConfigField "String", "MAX_BASE_URL", "\"https://max.acc.coronacheck.nl\"" - buildConfigField "String", "OPEN_ID_CLIENT_ID", "\"cc_app\"" - buildConfigField "String", "OPEN_ID_REDIRECT_URL", "\"https://web.acc.coronacheck.nl/app/auth2\"" - buildConfigField "String", "DEEPLINK_SCANNER_TEST_URL", "\"https://web.acc.coronacheck.nl/verifier/scan?returnUri=https://web.acc.coronacheck.nl/app/open?returnUri=scanner-test\"" - buildConfigField "String", "MIJNCN_BASEURL", "\"https://emax.acc.coronacheck.nl\"" - buildConfigField "String", "PAP_BASE_URL", "\"https://acceptatie.qrcodezonderdigid.ggdghor.nl\"" dimension "environment" versionNameSuffix "-acc" applicationIdSuffix ".acc" @@ -88,16 +69,8 @@ android { apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' matchingFallbacks = ['mlkit'] - buildConfigField "boolean", "FEATURE_TEST_PROVIDER_API_CHECKS", "true" - buildConfigField "boolean", "FEATURE_CORONA_CHECK_API_CHECKS", "true" } fdroidAcc { - buildConfigField "String", "BASE_API_URL", "\"https://holder-api.acc.coronacheck.nl/v8/\"" - buildConfigField "String", "CDN_API_URL", "\"https://holder-api-cdn.acc.coronacheck.nl/v8/\"" - buildConfigField "String", "MAX_BASE_URL", "\"https://max.acc.coronacheck.nl\"" - buildConfigField "String", "OPEN_ID_CLIENT_ID", "\"cc_app\"" - buildConfigField "String", "OPEN_ID_REDIRECT_URL", "\"coronacheck-acc://auth/login\"" - buildConfigField "String", "PAP_BASE_URL", "\"https://acceptatie.qrcodezonderdigid.ggdghor.nl\"" dimension "environment" versionNameSuffix "-fdroid" applicationIdSuffix ".fdroid.acc" @@ -157,14 +130,7 @@ dependencies { implementation project(":shared") implementation project(":appconfig") implementation project(":design") - implementation project(":introduction") - implementation project(":api") - implementation project(':mobilecore') - implementation project(":modules:luhncheck") - implementation project(":modules:qrgenerator") - implementation project(":modules:openidconnect") - - implementation libs.openid.appauth + implementation libs.bundles.groupie implementation libs.navigation.fragment implementation libs.navigation.ui @@ -175,7 +141,6 @@ dependencies { implementation libs.lottie debugImplementation libs.fragment.testing debugImplementation libs.androidx.test.monitor - implementation libs.zxing.core implementation(platform(libs.okhttp.bom)) implementation libs.okhttp.logging.interceptor implementation libs.okhttp @@ -194,9 +159,7 @@ dependencies { testImplementation libs.mockk androidTestImplementation libs.mockk.android testImplementation libs.koin.test.junit4 - testImplementation(libs.robolectric) { - exclude group: 'org.bouncycastle' - } + testImplementation libs.robolectric testImplementation libs.androidx.test.runner testImplementation libs.okhttp.mockWebServer testImplementation(libs.barista) { diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesFragmentScreenshotTest.kt deleted file mode 100644 index 3751bc66d..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/appconfig/app_update/NewFeaturesFragmentScreenshotTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -package nl.rijksoverheid.ctr.appconfig.app_update - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.FragmentScenario -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.appconfig.models.AppUpdateData -import nl.rijksoverheid.ctr.appconfig.models.NewFeatureItem -import nl.rijksoverheid.ctr.appconfig.models.NewTerms -import nl.rijksoverheid.ctr.holder.R -import org.junit.Test -import org.junit.runner.RunWith - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@RunWith(AndroidJUnit4::class) -class NewFeaturesFragmentScreenshotTest : ScreenshotTest { - @Test - fun newFeaturesFragment_TwoPages_Screenshot() { - val fragmentScenario = launchFragment(pagesSize = 2) - - compareScreenshot(fragmentScenario.waitForFragment()) - } - - @Test - fun newFeaturesFragment_OnePage_Screenshot() { - val fragmentScenario = launchFragment(pagesSize = 1) - - compareScreenshot(fragmentScenario.waitForFragment()) - } - - private fun launchFragment(pagesSize: Int): FragmentScenario { - val newFeatures = buildList { - for (i in 0 until pagesSize) { - add(NewFeatureItem( - imageResource = R.drawable.illustration_new_in_the_app_archive_mode, - subTitleColor = R.color.link, - titleResource = R.string.holder_newintheapp_archiveMode_title, - description = R.string.holder_newintheapp_archiveMode_body - )) - } - } - return launchFragmentInContainer( - bundleOf( - "app_update_data" to AppUpdateData( - newFeatures = newFeatures, - newTerms = NewTerms( - version = 2, - needsConsent = false - ), - newFeatureVersion = 8, - hideConsent = true - ) - ), - themeResId = R.style.TestAppTheme - ) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/design/menu/about/AboutThisAppFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/design/menu/about/AboutThisAppFragmentScreenshotTest.kt deleted file mode 100644 index 883eeafc2..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/design/menu/about/AboutThisAppFragmentScreenshotTest.kt +++ /dev/null @@ -1,64 +0,0 @@ -package nl.rijksoverheid.ctr.design.menu.about - -import android.content.Context -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import org.junit.Test -import org.junit.runner.RunWith - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@RunWith(AndroidJUnit4::class) -class AboutThisAppFragmentScreenshotTest : ScreenshotTest { - @Test - fun aboutThisAppFragment_Screenshot() { - val context = ApplicationProvider.getApplicationContext() - val fragmentScenario = launchFragmentInContainer( - bundleOf( - "data" to AboutThisAppData( - description = context.getString(R.string.about_this_app_description), - versionCode = "5236", - versionName = "5.0-The-End.", - configVersionHash = "configVersionHash", - configVersionTimestamp = 1655222474, - sections = listOf( - AboutThisAppData.AboutThisAppSection( - header = R.string.about_this_app_read_more, - items = mutableListOf( - AboutThisAppData.Url( - text = context.getString(R.string.privacy_statement), - url = context.getString(R.string.url_privacy_statement) - ), - AboutThisAppData.Url( - text = context.getString(R.string.about_this_app_accessibility), - url = context.getString(R.string.url_accessibility) - ), - AboutThisAppData.Url( - text = context.getString(R.string.about_this_app_colofon), - url = context.getString(R.string.about_this_app_colofon_url) - ), - AboutThisAppData.Destination( - text = context.getString(R.string.holder_menu_storedEvents), - destinationId = AboutThisAppFragmentDirections.actionSavedEvents().actionId - ) - ) - ) - ) - ) - ), - themeResId = R.style.TestAppTheme - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/AndroidTestUtils.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/AndroidTestUtils.kt deleted file mode 100644 index 91fcc7b54..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/AndroidTestUtils.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder - -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneId - -object AndroidTestUtils { - - fun getOffsetDateTime(dateString: String): OffsetDateTime = - OffsetDateTime.ofInstant(Instant.parse(dateString), ZoneId.of("UTC")) -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/Fakes.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/Fakes.kt deleted file mode 100644 index f4966ed8d..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/Fakes.kt +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder - -import androidx.lifecycle.MutableLiveData -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.appconfig.AppConfigViewModel -import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.holder.dashboard.DashboardViewModel -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardSync -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardTabItem -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.models.VerificationPolicy -import nl.rijksoverheid.ctr.shared.models.VerificationResult -import org.json.JSONArray -import org.json.JSONObject - -fun fakeGreenCardEntity( - id: Int = 0, - walletId: Int = 0, - type: GreenCardType = GreenCardType.Eu -) = GreenCardEntity(id, walletId, type) - -fun fakeOriginEntity( - id: Int = 0, - greenCardId: Long = 1L, - type: OriginType = OriginType.Vaccination, - eventTime: OffsetDateTime = OffsetDateTime.now(), - expirationTime: OffsetDateTime = OffsetDateTime.now(), - validFrom: OffsetDateTime = OffsetDateTime.now(), - doseNumber: Int? = null -) = OriginEntity(id, greenCardId, type, eventTime, expirationTime, validFrom, doseNumber) - -fun fakeCredentialEntity( - id: Long = 0, - greenCardId: Long = 1L, - data: ByteArray = "".toByteArray(), - credentialVersion: Int = 1, - validFrom: OffsetDateTime = OffsetDateTime.now(), - expirationTime: OffsetDateTime = OffsetDateTime.now(), - category: String? = null -) = CredentialEntity(id, greenCardId, data, credentialVersion, validFrom, expirationTime, category) - -fun fakeAppConfigViewModel(appStatus: AppStatus = AppStatus.NoActionRequired) = - object : AppConfigViewModel() { - override fun refresh( - mobileCoreWrapper: MobileCoreWrapper, - force: Boolean, - afterRefresh: () -> Unit - ) { - appStatusLiveData.value = appStatus - } - - override fun saveNewFeaturesFinished() { - } - - override fun saveNewTerms() { - } - } - -fun fakeDashboardViewModel(tabItems: List = listOf(fakeDashboardTabItem)) = - object : DashboardViewModel() { - override fun refresh(dashboardSync: DashboardSync) { - (dashboardTabItemsLiveData as MutableLiveData>) - .postValue(tabItems) - } - - override fun removeOrigin(originEntity: OriginEntity) { - } - - override fun dismissBlockedEventsInfo() { - } - - override fun dismissFuzzyMatchedEventsInfo() { - } - - override fun scrollUpdate(canScrollVertically: Boolean, greenCardType: GreenCardType) { - } - - override fun showMigrationDialog() { - } - - override fun deleteMigrationData() { - } - } - -val fakeDashboardTabItem = DashboardTabItem( - title = R.string.travel_button_domestic, - greenCardType = GreenCardType.Eu, - items = listOf() -) - -val fakeMobileCoreWrapper = object : MobileCoreWrapper { - override fun createCredentials(body: ByteArray): String { - TODO("Not yet implemented") - } - - override fun readCredential(credentials: ByteArray): ByteArray { - TODO("Not yet implemented") - } - - override fun createCommitmentMessage( - secretKey: ByteArray, - prepareIssueMessage: ByteArray - ): String { - TODO("Not yet implemented") - } - - override fun generateHolderSk(): String { - TODO("Not yet implemented") - } - - override fun readEuropeanCredential(credential: ByteArray): JSONObject { - return JSONObject().apply { - val v0 = JSONObject().apply { - put("dn", 1) - put("sd", 1) - put("co", "NL") - } - val v = JSONArray() - v.put(0, v0) - val dcc = JSONObject().apply { - put("v", v) - } - put("dcc", dcc) - } - } - - override fun initializeHolder(configFilesPath: String): String? { - TODO("Not yet implemented") - } - - override fun initializeVerifier(configFilesPath: String): String? { - TODO("Not yet implemented") - } - - override fun verify(credential: ByteArray, policy: VerificationPolicy): VerificationResult { - TODO("Not yet implemented") - } - - override fun isDcc(credential: ByteArray): Boolean { - TODO("Not yet implemented") - } - - override fun isForeignDcc(credential: ByteArray): Boolean { - TODO("Not yet implemented") - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/certificate_created/CertificateCreatedFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/certificate_created/CertificateCreatedFragmentScreenshotTest.kt deleted file mode 100644 index eeeabf179..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/certificate_created/CertificateCreatedFragmentScreenshotTest.kt +++ /dev/null @@ -1,27 +0,0 @@ -package nl.rijksoverheid.ctr.holder.certificate_created - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class CertificateCreatedFragmentScreenshotTest : ScreenshotTest { - @Test - fun certificateCreatedFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - bundleOf( - "toolbarTitle" to "Certificate created", - "title" to "Vaccination and recovery certificates created", - "description" to "A Dutch vaccination certificate has been created.

Your positive test result was suitable for creating a recovery certificate as well." - ), - themeResId = R.style.TestAppTheme - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/choose_provider/ChooseProviderFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/choose_provider/ChooseProviderFragmentScreenshotTest.kt deleted file mode 100644 index 7538e89b1..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/choose_provider/ChooseProviderFragmentScreenshotTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package nl.rijksoverheid.ctr.holder.choose_provider - -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ChooseProviderFragmentTest : ScreenshotTest { - @Test - fun chooseProviderFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - themeResId = R.style.TestAppTheme - ) - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/could_not_create_qr/CouldNotCreateQrFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/could_not_create_qr/CouldNotCreateQrFragmentScreenshotTest.kt deleted file mode 100644 index 1f786e9e6..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/could_not_create_qr/CouldNotCreateQrFragmentScreenshotTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package nl.rijksoverheid.ctr.holder.could_not_create_qr - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class CouldNotCreateQrFragmentScreenshotTest : ScreenshotTest { - @Test - fun certificateCreatedFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - bundleOf( - "toolbarTitle" to "Retrieve test result", - "title" to "No negative test result", - "description" to "No negative test result available.", - "buttonTitle" to "To my certificates" - ), - themeResId = R.style.TestAppTheme - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardFragmentScreenshotTest.kt deleted file mode 100644 index 5a13a8b16..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardFragmentScreenshotTest.kt +++ /dev/null @@ -1,285 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard - -import android.content.Intent -import android.os.Bundle -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.FragmentScenario -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.core.app.ActivityScenario -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.ActivityScenarioUtils.waitForActivity -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.AndroidTestUtils -import nl.rijksoverheid.ctr.holder.HolderMainActivity -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardTabItem -import nl.rijksoverheid.ctr.holder.dashboard.models.GreenCardEnabledState -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginState -import nl.rijksoverheid.ctr.holder.fakeCredentialEntity -import nl.rijksoverheid.ctr.holder.fakeDashboardViewModel -import nl.rijksoverheid.ctr.holder.fakeMobileCoreWrapper -import nl.rijksoverheid.ctr.holder.fakeOriginEntity -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.core.context.loadKoinModules -import org.koin.dsl.module - -@RunWith(AndroidJUnit4::class) -class DashboardFragmentScreenshotTest : ScreenshotTest { - - @get:Rule - val rule = InstantTaskExecutorRule() - - @Test - fun Invalid_ServerError_FirstTime_DomesticGreenCardWithOneGPolicy_Screenshot() { - val greenCardEntity = GreenCardEntity( - walletId = 1, - type = GreenCardType.Eu - ) - - val vaccinationOriginEntity = fakeOriginEntity( - type = OriginType.Vaccination, - validFrom = AndroidTestUtils.getOffsetDateTime("2020-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val recoveryOriginEntity = fakeOriginEntity( - type = OriginType.Recovery, - validFrom = AndroidTestUtils.getOffsetDateTime("2020-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val testOriginEntity = fakeOriginEntity( - type = OriginType.Test, - validFrom = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val credentialEntity = fakeCredentialEntity() - - val cardItem = DashboardItem.CardsItem( - cards = listOf( - DashboardItem.CardsItem.CardItem( - greenCard = GreenCard( - greenCardEntity = greenCardEntity, - origins = listOf( - vaccinationOriginEntity, - recoveryOriginEntity, - testOriginEntity - ), - credentialEntities = listOf(credentialEntity) - ), - originStates = listOf( - OriginState.Valid(vaccinationOriginEntity), - OriginState.Valid(recoveryOriginEntity), - OriginState.Valid(testOriginEntity) - ), - credentialState = DashboardItem.CardsItem.CredentialState.NoCredential, - databaseSyncerResult = DatabaseSyncerResult.Failed.ServerError.FirstTime( - AppErrorResult( - HolderStep.GetCredentialsNetworkRequest, - IllegalStateException("") - ) - ), - greenCardEnabledState = GreenCardEnabledState.Enabled - ) - ) - ) - - val fragmentScenario = startFragment( - items = listOf(cardItem) - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } - - @Test - fun Invalid_ServerError_MultipleTimes_DomesticGreenCardWithOneGPolicy_Screenshot() { - val greenCardEntity = GreenCardEntity( - walletId = 1, - type = GreenCardType.Eu - ) - - val vaccinationOriginEntity = fakeOriginEntity( - type = OriginType.Vaccination, - validFrom = AndroidTestUtils.getOffsetDateTime("2020-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val recoveryOriginEntity = fakeOriginEntity( - type = OriginType.Recovery, - validFrom = AndroidTestUtils.getOffsetDateTime("2020-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val testOriginEntity = fakeOriginEntity( - type = OriginType.Test, - validFrom = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val credentialEntity = fakeCredentialEntity() - - val cardItem = DashboardItem.CardsItem( - cards = listOf( - DashboardItem.CardsItem.CardItem( - greenCard = GreenCard( - greenCardEntity = greenCardEntity, - origins = listOf( - vaccinationOriginEntity, - recoveryOriginEntity, - testOriginEntity - ), - credentialEntities = listOf(credentialEntity) - ), - originStates = listOf( - OriginState.Valid(vaccinationOriginEntity), - OriginState.Valid(recoveryOriginEntity), - OriginState.Valid(testOriginEntity) - ), - credentialState = DashboardItem.CardsItem.CredentialState.NoCredential, - databaseSyncerResult = DatabaseSyncerResult.Failed.ServerError.MultipleTimes( - AppErrorResult( - HolderStep.GetCredentialsNetworkRequest, - IllegalStateException("") - ) - ), - greenCardEnabledState = GreenCardEnabledState.Enabled - ) - ) - ) - - val fragmentScenario = startFragment( - items = listOf(cardItem) - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } - - @Test - fun Invalid_ServerError_MultipleTimes_TwoEuGreenCardsWithOneGPolicy_Screenshot() { - val greenCardEntity = GreenCardEntity( - walletId = 1, - type = GreenCardType.Eu - ) - - val vaccinationOriginEntity = fakeOriginEntity( - type = OriginType.Vaccination, - validFrom = AndroidTestUtils.getOffsetDateTime("2020-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val recoveryOriginEntity = fakeOriginEntity( - type = OriginType.Recovery, - validFrom = AndroidTestUtils.getOffsetDateTime("2020-01-01T00:00:00.00Z"), - expirationTime = AndroidTestUtils.getOffsetDateTime("2030-01-01T00:00:00.00Z") - ) - - val credentialEntity = fakeCredentialEntity() - - val vaccinationCardItem = DashboardItem.CardsItem( - cards = listOf( - DashboardItem.CardsItem.CardItem( - greenCard = GreenCard( - greenCardEntity = greenCardEntity, - origins = listOf(vaccinationOriginEntity), - credentialEntities = listOf(credentialEntity) - ), - originStates = listOf( - OriginState.Valid(vaccinationOriginEntity) - ), - credentialState = DashboardItem.CardsItem.CredentialState.NoCredential, - databaseSyncerResult = DatabaseSyncerResult.Failed.ServerError.MultipleTimes( - AppErrorResult( - HolderStep.GetCredentialsNetworkRequest, - IllegalStateException("") - ) - ), - greenCardEnabledState = GreenCardEnabledState.Enabled - ) - ) - ) - - val recoveryCardItem = - DashboardItem.CardsItem( - cards = listOf( - DashboardItem.CardsItem.CardItem( - greenCard = GreenCard( - greenCardEntity = greenCardEntity, - origins = listOf(recoveryOriginEntity), - credentialEntities = listOf(credentialEntity) - ), - originStates = listOf( - OriginState.Valid(recoveryOriginEntity) - ), - credentialState = DashboardItem.CardsItem.CredentialState.HasCredential( - credentialEntity - ), - databaseSyncerResult = DatabaseSyncerResult.Failed.ServerError.MultipleTimes( - AppErrorResult( - HolderStep.GetCredentialsNetworkRequest, - IllegalStateException("") - ) - ), - greenCardEnabledState = GreenCardEnabledState.Enabled - ) - ) - ) - - val fragmentScenario = startFragment( - items = listOf(vaccinationCardItem, recoveryCardItem) - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } - - fun startActivity(args: Bundle = Bundle()): HolderMainActivity { - val intent = - Intent(ApplicationProvider.getApplicationContext(), HolderMainActivity::class.java) - intent.putExtras(args) - val scenario = ActivityScenario.launch(intent) - return scenario!!.waitForActivity() - } - - private fun startFragment( - items: List - ): FragmentScenario { - val tabItem = DashboardTabItem( - title = R.string.app_name, - greenCardType = items.first().cards.first().greenCard.greenCardEntity.type, - items = items - ) - - loadKoinModules( - module { - viewModel { fakeDashboardViewModel(listOf(tabItem)) } - factory { fakeMobileCoreWrapper } - } - ) - - return launchFragmentInContainer( - fragmentArgs = bundleOf("returnUri" to "test"), - themeResId = R.style.TestAppTheme - ) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/input_token/CommercialTestInputTokenFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/input_token/CommercialTestInputTokenFragmentScreenshotTest.kt deleted file mode 100644 index 117462b81..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/input_token/CommercialTestInputTokenFragmentScreenshotTest.kt +++ /dev/null @@ -1,25 +0,0 @@ -package nl.rijksoverheid.ctr.holder.input_token - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class CommercialTestInputTokenFragmentScreenshotTest : ScreenshotTest { - @Test - fun commercialTestInputTokenFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - bundleOf( - "token" to "Token" - ), - themeResId = R.style.TestAppTheme - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/your_events/YourEventExplanationFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/your_events/YourEventExplanationFragmentScreenshotTest.kt deleted file mode 100644 index 51e78e04e..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/your_events/YourEventExplanationFragmentScreenshotTest.kt +++ /dev/null @@ -1,32 +0,0 @@ -package nl.rijksoverheid.ctr.holder.your_events - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreen -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class YourEventExplanationFragmentScreenshotTest : ScreenshotTest { - @Test - fun yourEventExplanationFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - bundleOf( - "data" to arrayOf( - InfoScreen( - title = "Details", - description = "These details about your vaccination were retrieved from MVWS-TEST:

Name: van Geer, Corrie
Date of birth: 1 January 1960

Disease targeted: COVID-19
Vaccine: Pfizer (Comirnaty)
Vaccine type: SARS-CoV-2 mRNA vaccine
Vaccine manufacturer: Biontech Manufacturing GmbH
Date of vaccination: 16 March 2022
Member state of vaccination: Netherlands
Unique vaccination identifier: 3ca0c918-5a20-4033-8fc3-8334cd5c63af
" - ) - ), - "toolbarTitle" to "Details" - ), - themeResId = R.style.TestAppTheme - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragmentScreenshotTest.kt deleted file mode 100644 index ae0cec74f..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragmentScreenshotTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package nl.rijksoverheid.ctr.holder.your_events - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import java.time.LocalDate -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class YourEventsFragmentScreenshotTest : ScreenshotTest { - @Test - fun yourEventsFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - bundleOf( - "toolbarTitle" to "Retrieved vaccinations", - "type" to YourEventsFragmentType.RemoteProtocol3Type( - remoteEvents = mapOf( - RemoteProtocol( - providerIdentifier = "ZZZ", - protocolVersion = "3.0", - status = RemoteProtocol.Status.COMPLETE, - holder = RemoteProtocol.Holder( - infix = "van", - firstName = "Corrie", - lastName = "Geer", - birthDate = "1960-01-01" - ), - events = listOf( - RemoteEventVaccination( - type = "vaccination", - unique = "3ca0c918-5a20-4033-8fc3-8334cd5c63af", - vaccination = RemoteEventVaccination.Vaccination( - date = LocalDate.parse("2022-03-16"), - hpkCode = "2924528", - type = "", - brand = "", - completedByMedicalStatement = false, - completedByPersonalStatement = false, - completionReason = null, - doseNumber = null, - totalDoses = null, - manufacturer = "", - country = "NL" - ) - ), - RemoteEventVaccination( - type = "vaccination", - unique = "c9e2f21b-50c9-407d-9ef9-53e534ad6aa2", - vaccination = RemoteEventVaccination.Vaccination( - date = LocalDate.parse("2022-02-14"), - hpkCode = "2924528", - type = "", - brand = "", - completedByMedicalStatement = false, - completedByPersonalStatement = false, - completionReason = null, - doseNumber = null, - totalDoses = null, - manufacturer = "", - country = "NL" - ) - ) - ) - ) to "".toByteArray() - ), - eventProviders = listOf(EventProvider("ZZZ", "TEST")) - ), - "flow" to HolderFlow.Vaccination - ), - themeResId = R.style.TestAppTheme - ) - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/androidTest/java/nl/rijksoverheid/ctr/introduction/onboarding/OnboardingFragmentScreenshotTest.kt b/holder/src/androidTest/java/nl/rijksoverheid/ctr/introduction/onboarding/OnboardingFragmentScreenshotTest.kt deleted file mode 100644 index 20e0125eb..000000000 --- a/holder/src/androidTest/java/nl/rijksoverheid/ctr/introduction/onboarding/OnboardingFragmentScreenshotTest.kt +++ /dev/null @@ -1,42 +0,0 @@ -package nl.rijksoverheid.ctr.introduction.onboarding - -import androidx.core.os.bundleOf -import androidx.fragment.app.testing.launchFragmentInContainer -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.karumi.shot.FragmentScenarioUtils.waitForFragment -import com.karumi.shot.ScreenshotTest -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.introduction.onboarding.models.OnboardingItem -import nl.rijksoverheid.ctr.introduction.status.models.IntroductionData -import org.junit.Test -import org.junit.runner.RunWith - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@RunWith(AndroidJUnit4::class) -class OnboardingFragmentScreenshotTest : ScreenshotTest { - - @Test - fun onboardingFragment_Screenshot() { - val fragmentScenario = launchFragmentInContainer( - bundleOf("introduction_data" to IntroductionData( - listOf( - OnboardingItem( - R.drawable.illustration_onboarding_1, - R.string.onboarding_screen_1_title, - R.string.onboarding_screen_1_description - ) - ) - ) - ), - themeResId = R.style.TestAppTheme - ) - - compareScreenshot(fragmentScenario.waitForFragment()) - } -} diff --git a/holder/src/main/AndroidManifest.xml b/holder/src/main/AndroidManifest.xml index 4ed7144b5..f7ef1a381 100644 --- a/holder/src/main/AndroidManifest.xml +++ b/holder/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -21,79 +20,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.dialog_no_internet_connection_title, - message = getString(R.string.dialog_no_internet_connection_description), - positiveButtonText = onButtonClickWithRetryTitle(), - positiveButtonCallback = { - onButtonClickWithRetryAction() - }, - negativeButtonText = R.string.dialog_close, - negativeButtonCallback = this::onButtonClickClose - ) - } - is NetworkRequestResult.Failed.ServerNetworkError -> { - val errorCodeString = errorCodeStringFactory.get( - flow = getFlow(), - errorResults = listOf(errorResult) - ) - - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.dialog_no_internet_connection_title), - description = getString( - R.string.dialog_no_internet_connection_description_errorcode, - errorCodeString - ), - buttonTitle = getString(R.string.back_to_overview), - buttonAction = ErrorResultFragmentData.ButtonAction.Destination(R.id.action_my_overview), - urlData = ErrorResultFragmentData.UrlData( - urlButtonTitle = getString(R.string.error_something_went_wrong_outage_button), - urlButtonUrl = getString(R.string.error_something_went_wrong_outage_button_url) - ) - ) - ) - } - is MissingOriginErrorResult -> { - val errorCodeString = getString( - R.string.holder_event_missingorigin_error_description, - errorCodeStringFactory.get(getFlow(), listOf(errorResult)) - ) - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.rule_engine_no_origin_title), - description = "$customerErrorDescription$errorCodeString", - buttonTitle = getString(R.string.back_to_overview), - buttonAction = ErrorResultFragmentData.ButtonAction.Destination(R.id.action_my_overview) - ) - ) - } - else -> { - val errorCodeString = errorCodeStringFactory.get( - flow = getFlow(), - errorResults = listOf(errorResult) - ) - if (is429HttpError(errorResult) || errorResult is OpenIdErrorResult.ServerBusy) { - // On HTTP 429 or server busy error we make an exception and show a too busy screen - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.error_too_busy_title), - description = getString( - R.string.error_too_busy_description, - errorCodeString - ), - buttonTitle = getString(R.string.back_to_overview), - buttonAction = ErrorResultFragmentData.ButtonAction.Destination(R.id.action_my_overview) - ) - ) - } else { - val errorDescription = customerErrorDescription - ?: if (errorResult is NetworkRequestResult.Failed.CoronaCheckHttpError) { - getString( - R.string.error_something_went_wrong_http_error_description, - errorCodeString - ) - } else { - getString( - R.string.error_something_went_wrong_making_proof_description, - errorCodeString - ) - } - - val data = ErrorResultFragmentData( - title = getString(R.string.error_something_went_wrong_title), - description = errorDescription, - urlData = ErrorResultFragmentData.UrlData( - urlButtonTitle = getString(R.string.error_something_went_wrong_outage_button), - urlButtonUrl = getString(R.string.error_something_went_wrong_outage_button_url) - ), - buttonTitle = getString(R.string.back_to_overview), - buttonAction = ErrorResultFragmentData.ButtonAction.Destination(R.id.action_my_overview) - ) - presentError(data) - } - } - } - } - - private fun is429HttpError(errorResult: ErrorResult) = - errorResult is NetworkRequestResult.Failed.CoronaCheckHttpError && errorResult.e.code() == 429 - - fun presentError(data: ErrorResultFragmentData) { - navigateSafety( - R.id.action_error_result, - ErrorResultFragment.getBundle(data) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/Bindings.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/Bindings.kt deleted file mode 100644 index 7acb385d5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/Bindings.kt +++ /dev/null @@ -1,62 +0,0 @@ -package nl.rijksoverheid.ctr.holder.ui.create_qr - -import android.view.View -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.IncludeContentButtonBinding -import nl.rijksoverheid.ctr.holder.databinding.ItemPaperProofExplanationBinding -import nl.rijksoverheid.ctr.shared.utils.Accessibility - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -fun IncludeContentButtonBinding.bind( - @StringRes title: Int, - subtitle: String?, - @DrawableRes logo: Int? = null, - onClick: () -> Unit -) { - providerTitle.setText(title) - providerSubtitle.text = subtitle - - if (subtitle.isNullOrEmpty()) { - providerSubtitle.visibility = View.GONE - providerTitle.setPadding( - providerTitle.paddingLeft, - providerTitle.context.resources.getDimensionPixelSize(R.dimen.test_provider_title_without_subtitle_padding), - providerTitle.paddingRight, - providerTitle.context.resources.getDimensionPixelSize(R.dimen.test_provider_title_without_subtitle_padding) - ) - root.contentDescription = providerTitle.text - logo?.let { providerTitle.setCompoundDrawablesWithIntrinsicBounds(0, 0, it, 0) } - } else { - root.contentDescription = String.format("%s. %s", providerTitle.text, providerSubtitle.text) - logo?.let { providerSubtitle.setCompoundDrawablesWithIntrinsicBounds(0, 0, it, 0) } - } - - Accessibility.button(root) - - root.setOnClickListener { - onClick() - } -} - -fun IncludeContentButtonBinding.setEnabled(enabled: Boolean) { - root.isClickable = enabled - root.isEnabled = enabled -} - -fun ItemPaperProofExplanationBinding.bind( - @DrawableRes icon: Int, - @StringRes title: Int, - @StringRes subtitle: Int -) { - iconView.setImageResource(icon) - titleView.setText(title) - subtitleView.setText(subtitle) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderApplication.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderApplication.kt index 244da3621..e89aa87b2 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderApplication.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderApplication.kt @@ -1,43 +1,19 @@ package nl.rijksoverheid.ctr.holder -import android.util.Log -import androidx.work.Configuration -import androidx.work.WorkerFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.api.apiModule import nl.rijksoverheid.ctr.appconfig.appConfigModule -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager import nl.rijksoverheid.ctr.design.designModule -import nl.rijksoverheid.ctr.holder.dashboard.dashboardModule -import nl.rijksoverheid.ctr.holder.fuzzy_matching.fuzzyMatchingModule import nl.rijksoverheid.ctr.holder.modules.appModule -import nl.rijksoverheid.ctr.holder.modules.cardUtilsModule import nl.rijksoverheid.ctr.holder.modules.errorsModule -import nl.rijksoverheid.ctr.holder.modules.eventsUseCasesModule -import nl.rijksoverheid.ctr.holder.modules.greenCardUseCasesModule -import nl.rijksoverheid.ctr.holder.modules.holderAppStatusModule -import nl.rijksoverheid.ctr.holder.modules.holderIntroductionModule -import nl.rijksoverheid.ctr.holder.modules.holderMobileCoreModule import nl.rijksoverheid.ctr.holder.modules.holderPreferenceModule -import nl.rijksoverheid.ctr.holder.modules.qrsModule -import nl.rijksoverheid.ctr.holder.modules.repositoriesModule -import nl.rijksoverheid.ctr.holder.modules.responsesModule -import nl.rijksoverheid.ctr.holder.modules.retrofitModule import nl.rijksoverheid.ctr.holder.modules.storageModule -import nl.rijksoverheid.ctr.holder.modules.testProvidersUseCasesModule import nl.rijksoverheid.ctr.holder.modules.utilsModule import nl.rijksoverheid.ctr.holder.modules.viewModels -import nl.rijksoverheid.ctr.introduction.introductionModule import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.WalletEntity -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveCTBUseCase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper import nl.rijksoverheid.ctr.shared.SharedApplication import nl.rijksoverheid.ctr.shared.sharedModule -import okhttp3.HttpUrl.Companion.toHttpUrl -import org.koin.android.ext.android.inject import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import org.koin.core.module.Module @@ -49,13 +25,7 @@ import org.koin.core.module.Module * SPDX-License-Identifier: EUPL-1.2 * */ -open class HolderApplication : SharedApplication(), Configuration.Provider { - - private val holderDatabase: HolderDatabase by inject() - private val holderWorkerFactory: WorkerFactory by inject() - private val appConfigStorageManager: AppConfigStorageManager by inject() - private val mobileCoreWrapper: MobileCoreWrapper by inject() - private val remoteCTBUseCase: RemoveCTBUseCase by inject() +open class HolderApplication : SharedApplication() { private val coroutineScope = CoroutineScope(Dispatchers.IO) open fun coroutineScopeBlock(block: suspend () -> Unit) { @@ -64,20 +34,10 @@ open class HolderApplication : SharedApplication(), Configuration.Provider { private val holderModules = listOf( storageModule, - greenCardUseCasesModule, - eventsUseCasesModule, - testProvidersUseCasesModule, utilsModule, viewModels, - cardUtilsModule, - repositoriesModule, - qrsModule, appModule, errorsModule(BuildConfig.FLAVOR), - retrofitModule(BuildConfig.BASE_API_URL, BuildConfig.CDN_API_URL), - responsesModule, - fuzzyMatchingModule, - dashboardModule ).toTypedArray() override fun onCreate() { @@ -87,54 +47,19 @@ open class HolderApplication : SharedApplication(), Configuration.Provider { androidContext(this@HolderApplication) modules( *holderModules, - holderIntroductionModule, - holderAppStatusModule, - apiModule( - BuildConfig.BASE_API_URL.toHttpUrl(), - BuildConfig.SIGNATURE_CERTIFICATE_CN_MATCH, - BuildConfig.FEATURE_CORONA_CHECK_API_CHECKS, - BuildConfig.FEATURE_TEST_PROVIDER_API_CHECKS - ), sharedModule, - appConfigModule(BuildConfig.CDN_API_URL, "holder", BuildConfig.VERSION_CODE), - introductionModule, + appConfigModule(), *getAdditionalModules().toTypedArray(), designModule ) } - // Create default wallet in database if empty coroutineScopeBlock { - if (holderDatabase.walletDao().getAll().isEmpty()) { - holderDatabase.walletDao().insert( - WalletEntity( - id = 1, - label = "main" - ) - ) - } - remoteCTBUseCase.execute() - } - - if (appConfigStorageManager.areConfigFilesPresentInFilesFolder()) { - mobileCoreWrapper.initializeHolder(applicationContext.filesDir.path) + HolderDatabase.deleteDatabase(this) } } override fun getAdditionalModules(): List { - return listOf(holderPreferenceModule, holderMobileCoreModule) - } - - override fun getWorkManagerConfiguration(): Configuration { - return Configuration.Builder().apply { - setMinimumLoggingLevel( - if (BuildConfig.DEBUG) { - Log.DEBUG - } else { - Log.ERROR - } - ) - setWorkerFactory(holderWorkerFactory) - }.build() + return listOf(holderPreferenceModule) } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivity.kt index 6e836ace2..a0c32aa03 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivity.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivity.kt @@ -8,38 +8,10 @@ package nl.rijksoverheid.ctr.holder -import android.content.Intent -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkRequest import android.os.Bundle import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.coroutineScope -import androidx.lifecycle.lifecycleScope -import androidx.navigation.NavController -import androidx.navigation.fragment.NavHostFragment -import nl.rijksoverheid.ctr.appconfig.AppConfigViewModel -import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.design.utils.DialogButtonData -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.design.utils.IntentUtil -import nl.rijksoverheid.ctr.design.utils.SharedDialogFragment -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepository import nl.rijksoverheid.ctr.holder.databinding.ActivityMainBinding -import nl.rijksoverheid.ctr.holder.ui.device_rooted.DeviceRootedViewModel -import nl.rijksoverheid.ctr.holder.ui.device_secure.DeviceSecureViewModel -import nl.rijksoverheid.ctr.holder.ui.priority_notification.PriorityNotificationViewModel -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.holder.workers.WorkerManagerUtil -import nl.rijksoverheid.ctr.introduction.IntroductionViewModel -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.ext.disableSplashscreenExitAnimation -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel /* * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. @@ -50,37 +22,6 @@ import org.koin.androidx.viewmodel.ext.android.viewModel */ class HolderMainActivity : AppCompatActivity() { - private val introductionViewModel: IntroductionViewModel by viewModel() - private val appConfigViewModel: AppConfigViewModel by viewModel() - private val deviceRootedViewModel: DeviceRootedViewModel by viewModel() - private val deviceSecureViewModel: DeviceSecureViewModel by viewModel() - private val priorityNotificationViewModel: PriorityNotificationViewModel by viewModel() - private val dialogUtil: DialogUtil by inject() - private val mobileCoreWrapper: MobileCoreWrapper by inject() - private val intentUtil: IntentUtil by inject() - private var isFreshStart: Boolean = true // track if this is a fresh start of the app - private val androidUtil: AndroidUtil by inject() - private val workerManagerUtil: WorkerManagerUtil by inject() - private val coronaCheckRepository: CoronaCheckRepository by inject() - private val featureFlagUseCase: HolderFeatureFlagUseCase by inject() - - private val connectivityChangeCallback = - object : ConnectivityManager.NetworkCallback() { - private var refreshConfig = false - override fun onAvailable(network: Network) { - if (refreshConfig) { - appConfigViewModel.refresh(mobileCoreWrapper, true) - refreshConfig = false - } - } - - override fun onUnavailable() { - refreshConfig = true - } - } - private val networkChangeFilter = - NetworkRequest.Builder().build() // blank filter for all networks - override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme_DayNight) super.onCreate(savedInstanceState) @@ -93,152 +34,5 @@ class HolderMainActivity : AppCompatActivity() { WindowManager.LayoutParams.FLAG_SECURE ) } - - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment - val navController = navHostFragment.navController - - introductionViewModel.introductionRequiredLiveData.observe(this, EventObserver { - navigateToIntroduction(navController) - }) - - appConfigViewModel.appStatusLiveData.observe(this) { - handleAppStatus(it, navController) - } - - deviceRootedViewModel.deviceRootedLiveData.observe(this, EventObserver { - if (it) { - dialogUtil.presentDialog( - context = this, - title = R.string.dialog_rooted_device_title, - message = getString(R.string.dialog_rooted_device_message), - positiveButtonText = R.string.dialog_rooted_device_positive_button, - positiveButtonCallback = { }, - onDismissCallback = { deviceRootedViewModel.setHasDismissedRootedDeviceDialog() } - ) - } - }) - - deviceSecureViewModel.deviceSecureLiveData.observe(this, EventObserver { - if (!it) { - dialogUtil.presentDialog( - context = this, - title = R.string.dialog_device_secure_warning_title, - message = getString(R.string.dialog_device_secure_warning_description), - positiveButtonText = R.string.dialog_device_secure_positive_button, - positiveButtonCallback = { }, - onDismissCallback = { - deviceSecureViewModel.setHasDismissedUnsecureDeviceDialog( - true - ) - } - ) - } - }) - - priorityNotificationViewModel.showPriorityNotificationLiveData.observe(this, EventObserver { - SharedDialogFragment.show( - supportFragmentManager, DialogFragmentData( - text = it, - positiveButtonData = DialogButtonData.Dismiss(R.string.ok) - ) - ) - }) - - disableSplashscreenExitAnimation() - } - - private fun navigateToIntroduction( - navController: NavController - ) { - navController.navigate(RootNavDirections.actionIntroduction()) - } - - private fun handleAppStatus( - appStatus: AppStatus, - navController: NavController - ) { - - if (appStatus == AppStatus.Deactivated || featureFlagUseCase.isInArchiveMode()) { - workerManagerUtil.cancelRefreshCredentialsJob(this) - } else { - // schedule background refresh for existing greencards - lifecycleScope.launchWhenCreated { - workerManagerUtil.scheduleRefreshCredentialsJob() - } - } - - if (appStatus is AppStatus.UpdateRecommended) { - showRecommendedUpdateDialog() - return - } - - if (appStatus !is AppStatus.NoActionRequired) { - navController.navigate(RootNavDirections.actionAppStatus(appStatus)) - } else { - closeAppStatusIfOpen(navController) - } - } - - private fun closeAppStatusIfOpen( - navController: NavController - ) { - val isAppStatusFragment = - navController.currentBackStackEntry?.destination?.id == R.id.nav_app_locked - if (isAppStatusFragment) { - navController.navigate(RootNavDirections.actionMain()) - } - } - - private fun showRecommendedUpdateDialog() { - dialogUtil.presentDialog( - context = this, - title = R.string.app_status_update_recommended_title, - message = getString(R.string.app_status_update_recommended_message), - positiveButtonText = R.string.app_status_update_recommended_action, - positiveButtonCallback = { intentUtil.openPlayStore(this) }, - negativeButtonText = R.string.app_status_update_recommended_dismiss_action - ) - } - - override fun onStart() { - super.onStart() - // get app and providers config on every app foreground when introduction is finished - if (!introductionViewModel.getIntroductionRequired()) { - appConfigViewModel.refresh(mobileCoreWrapper, isFreshStart) { - lifecycle.coroutineScope.launchWhenStarted { - coronaCheckRepository.configProviders(useCache = false) - } - } - isFreshStart = false - } - } - - override fun onResume() { - super.onResume() - // Add connectivity change listener. If a network is detected try to refresh the config - androidUtil.getConnectivityManager().registerNetworkCallback( - networkChangeFilter, connectivityChangeCallback - ) - } - - override fun onPause() { - super.onPause() - androidUtil.getConnectivityManager().unregisterNetworkCallback(connectivityChangeCallback) - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - - // Handle if an external app sets the launch mode of this activity to single top of single task. - // In this case we need to set the graph again and handle the deeplink ourselves so that the entire - // graph is traversed to find the deeplink - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment - val navController = navHostFragment.navController - navController.setGraph(R.navigation.holder_nav_graph_root) - if (featureFlagUseCase.getAddEventsButtonEnabled()) { - navController.handleDeepLink(intent) - } } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivityViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivityViewModel.kt deleted file mode 100644 index 0ab4d48ab..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainActivityViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -package nl.rijksoverheid.ctr.holder - -import android.os.Bundle -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.navigation.NavDirections -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.shared.livedata.Event - -abstract class HolderMainActivityViewModel : ViewModel() { - val navigateLiveData: LiveData> = MutableLiveData() - val navigateWithBundleLiveData: LiveData>> = MutableLiveData() - abstract fun navigate(navDirections: NavDirections, delayMillis: Long = 0) - - abstract fun navigateWithBundle(actionId: Int, bundle: Bundle) -} - -class HolderMainActivityViewModelImpl : HolderMainActivityViewModel() { - - override fun navigate(navDirections: NavDirections, delayMillis: Long) { - viewModelScope.launch { - delay(delayMillis) - (navigateLiveData as MutableLiveData).postValue(Event(navDirections)) - } - } - - override fun navigateWithBundle(actionId: Int, bundle: Bundle) { - viewModelScope.launch { - (navigateWithBundleLiveData as MutableLiveData).postValue(Event(Pair(actionId, bundle))) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainFragment.kt deleted file mode 100644 index 4c23f97d9..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/HolderMainFragment.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder - -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.view.View -import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.Fragment -import androidx.navigation.NavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.ui.NavigationUI -import androidx.navigation.ui.setupWithNavController -import nl.rijksoverheid.ctr.holder.databinding.FragmentMainBinding -import nl.rijksoverheid.ctr.shared.ext.getNavigationIconView -import nl.rijksoverheid.ctr.shared.utils.Accessibility.makeIndeterminateAccessible -import nl.rijksoverheid.ctr.shared.utils.Accessibility.setAccessibilityFocus - -class HolderMainFragment : Fragment(R.layout.fragment_main) { - - private var _binding: FragmentMainBinding? = null - private val binding get() = _binding!! - private var _navController: NavController? = null - private val navController get() = _navController!! - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - _binding = FragmentMainBinding.bind(view) - - val navHostFragment = - childFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - _navController = navHostFragment.navController - - val defaultToolbarElevation = resources.getDimension(R.dimen.toolbar_elevation) - navController.addOnDestinationChangedListener { _, destination, _ -> - // loading a new screen should automatically update the accessibility tree so Talkback knows what is on the screen. - binding.toolbar.getNavigationIconView()?.setAccessibilityFocus() - - binding.toolbar.elevation = if (destination.id == R.id.nav_dashboard) { - 0f - } else { - defaultToolbarElevation - } - } - binding.toolbar.setupWithNavController(navController) - - binding.toolbar.setNavigationOnClickListener { - when (navController.currentDestination?.id) { - R.id.nav_your_events -> { - // Trigger custom dispatcher in destination - requireActivity().onBackPressedDispatcher.onBackPressed() - return@setNavigationOnClickListener - } - R.id.nav_fuzzy_matching_onboarding -> { - // Trigger custom dispatcher in destination - requireActivity().onBackPressedDispatcher.onBackPressed() - return@setNavigationOnClickListener - } - } - - NavigationUI.navigateUp(navController, null) - } - } - - fun presentLoading(loading: Boolean) { - binding.loading.makeIndeterminateAccessible( - context = requireContext(), - isLoading = loading - ) - binding.loading.visibility = if (loading) View.VISIBLE else View.GONE - } - - fun getToolbar(): Toolbar { - return binding.toolbar - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - fun resetMenuItemListener() { - binding.toolbar.setOnMenuItemClickListener { - NavigationUI.onNavDestinationSelected(it, navController) - } - } -} - -fun Fragment.hideNavigationIcon() { - (parentFragment?.parentFragment as? HolderMainFragment)?.getToolbar()?.navigationIcon = null -} - -fun Fragment.showNavigationIcon(icon: Drawable) { - (parentFragment?.parentFragment as? HolderMainFragment)?.getToolbar()?.navigationIcon = icon -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/HolderApiClient.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/HolderApiClient.kt deleted file mode 100644 index c68ae5706..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/HolderApiClient.kt +++ /dev/null @@ -1,35 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api - -import nl.rijksoverheid.ctr.holder.api.post.GetCouplingData -import nl.rijksoverheid.ctr.holder.api.post.GetCredentialsPostData -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteAccessTokens -import nl.rijksoverheid.ctr.holder.paper_proof.models.RemoteCouplingResponse -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.holder.your_events.models.RemotePrepareIssue -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.POST - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface HolderApiClient { - @GET("holder/prepare_issue") - suspend fun getPrepareIssue(): RemotePrepareIssue - - @POST("holder/get_credentials") - suspend fun getCredentials( - @Body data: GetCredentialsPostData - ): RemoteGreenCards - - @POST("holder/access_tokens") - suspend fun getAccessTokens(@Header("Authorization") authorization: String): RemoteAccessTokens - - @POST("holder/coupling") - suspend fun getCoupling(@Body data: GetCouplingData): RemoteCouplingResponse -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/HolderApiClientUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/HolderApiClientUtil.kt deleted file mode 100644 index db34a5f5b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/HolderApiClientUtil.kt +++ /dev/null @@ -1,78 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api - -import android.util.Base64 -import com.appmattus.certificatetransparency.CTLogger -import com.appmattus.certificatetransparency.VerificationResult -import com.appmattus.certificatetransparency.certificateTransparencyTrustManager -import com.appmattus.certificatetransparency.loglist.LogListDataSourceFactory -import java.io.ByteArrayInputStream -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import javax.net.ssl.X509TrustManager -import nl.rijksoverheid.ctr.holder.BuildConfig -import okhttp3.OkHttpClient -import okhttp3.tls.HandshakeCertificates -import retrofit2.Retrofit -import timber.log.Timber - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface HolderApiClientUtil { - fun client(certificateBytes: List): HolderApiClient -} - -class HolderApiClientUtilImpl( - private val okHttpClient: OkHttpClient, - private val retrofit: Retrofit -) : HolderApiClientUtil { - - private fun transparentTrustManager(trustManager: X509TrustManager) = - certificateTransparencyTrustManager(trustManager) { - if (BuildConfig.DEBUG) { - setLogger(object : CTLogger { - override fun log(host: String, result: VerificationResult) { - Timber.tag("certificate transparency") - .d("host: $host, verification result: $result") - } - }) - } - - setLogListService(LogListDataSourceFactory.createLogListService( - baseUrl = "https://www.gstatic.com/ct/log_list/v3/" - )) - } - - override fun client(certificateBytes: List): HolderApiClient { - val okHttpClient = okHttpClient - .newBuilder() - .apply { - if (BuildConfig.FEATURE_TEST_PROVIDER_API_CHECKS) { - val handshakeCertificates = HandshakeCertificates.Builder() - .apply { - certificateBytes.forEach { - val base64Decoded = Base64.decode(it, Base64.DEFAULT) - val certificateFactory = CertificateFactory.getInstance("X.509") - val x509Certificate = certificateFactory.generateCertificate( - ByteArrayInputStream(base64Decoded) - ) as X509Certificate - addTrustedCertificate(x509Certificate) - } - } - .build() - - sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - transparentTrustManager(handshakeCertificates.trustManager) - ) - } - }.build() - - return retrofit.newBuilder().client(okHttpClient) - .build().create(HolderApiClient::class.java) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/MijnCnApiClient.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/MijnCnApiClient.kt deleted file mode 100644 index 078df863e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/MijnCnApiClient.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.api - -import nl.rijksoverheid.ctr.holder.get_events.models.MijnCNTokenResponse -import retrofit2.http.Field -import retrofit2.http.FormUrlEncoded -import retrofit2.http.Headers -import retrofit2.http.POST -import retrofit2.http.Url - -interface MijnCnApiClient { - - @POST - @Headers("Content-Type: application/x-www-form-urlencoded", "Accept: application/json") - @FormUrlEncoded - suspend fun getAccessToken( - @Url url: String, - @Field("code") code: String, - @Field("grant_type") grantType: String, - @Field("redirect_uri") redirectUri: String, - @Field("code_verifier") codeVerifier: String, - @Field("client_id") clientId: String - ): MijnCNTokenResponse -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/OriginTypeJsonAdapter.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/OriginTypeJsonAdapter.kt deleted file mode 100644 index b4141ddf6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/OriginTypeJsonAdapter.kt +++ /dev/null @@ -1,13 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api - -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -class OriginTypeJsonAdapter { - @FromJson - fun fromJson(value: String?): OriginType = OriginType.fromTypeString(value ?: error("OriginType not known")) - - @ToJson - fun toJson(value: OriginType): String = value.getTypeString() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteConfigApiClient.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteConfigApiClient.kt deleted file mode 100644 index 019dd8178..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteConfigApiClient.kt +++ /dev/null @@ -1,11 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api - -import nl.rijksoverheid.ctr.api.signing.http.SignedRequest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import retrofit2.http.GET - -interface RemoteConfigApiClient { - @GET("holder/config_providers") - @SignedRequest - suspend fun getConfigCtp(): RemoteConfigProviders -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteCouplingStatusJsonAdapter.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteCouplingStatusJsonAdapter.kt deleted file mode 100644 index b50ab5bb3..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteCouplingStatusJsonAdapter.kt +++ /dev/null @@ -1,19 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api - -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson -import nl.rijksoverheid.ctr.holder.paper_proof.models.RemoteCouplingStatus - -class RemoteCouplingStatusJsonAdapter { - @FromJson - fun fromJson(value: String?): RemoteCouplingStatus = when (value) { - RemoteCouplingStatus.TYPE_EXPIRED -> RemoteCouplingStatus.Expired - RemoteCouplingStatus.TYPE_BLOCKED -> RemoteCouplingStatus.Blocked - RemoteCouplingStatus.TYPE_REJECTED -> RemoteCouplingStatus.Rejected - RemoteCouplingStatus.TYPE_ACCEPTED -> RemoteCouplingStatus.Accepted - else -> RemoteCouplingStatus.Rejected - } - - @ToJson - fun toJson(value: RemoteCouplingStatus): String = value.typeString -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteTestStatusJsonAdapter.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteTestStatusJsonAdapter.kt deleted file mode 100644 index cb00c7eab..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/RemoteTestStatusJsonAdapter.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (c) 2020 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ -package nl.rijksoverheid.ctr.holder.api - -import com.squareup.moshi.FromJson -import com.squareup.moshi.ToJson -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol - -class RemoteTestStatusJsonAdapter { - @FromJson - fun fromJson(value: String?): RemoteProtocol.Status = RemoteProtocol.Status.fromValue(value) - - @ToJson - fun toJson(value: RemoteProtocol.Status): String = value.apiStatus -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/TestProviderApiClient.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/TestProviderApiClient.kt deleted file mode 100644 index da190d0a8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/TestProviderApiClient.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.api - -import nl.rijksoverheid.ctr.api.interceptors.SigningCertificate -import nl.rijksoverheid.ctr.api.signing.http.SignedRequest -import nl.rijksoverheid.ctr.holder.api.models.SignedResponseWithModel -import nl.rijksoverheid.ctr.holder.api.post.GetTestResultPostData -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteUnomi -import retrofit2.http.Body -import retrofit2.http.Header -import retrofit2.http.POST -import retrofit2.http.Tag -import retrofit2.http.Url - -interface TestProviderApiClient { - @POST - @SignedRequest - suspend fun getTestResult( - @Url url: String, - @Header("Authorization") authorization: String, - @Header("CoronaCheck-Protocol-Version") protocolVersion: String = "3.0", - @Body data: GetTestResultPostData?, - @Tag certificate: SigningCertificate - ): SignedResponseWithModel - - @POST - @SignedRequest - suspend fun getUnomi( - @Url url: String, - @Header("Authorization") authorization: String, - @Header("CoronaCheck-Protocol-Version") protocolVersion: String = "3.0", - @Body params: Map, - @Tag certificate: SigningCertificate - ): SignedResponseWithModel - - @POST - @SignedRequest - suspend fun getEvents( - @Url url: String, - @Header("Authorization") authorization: String, - @Header("CoronaCheck-Protocol-Version") protocolVersion: String = "3.0", - @Body params: Map, - @Tag certificate: SigningCertificate - ): SignedResponseWithModel -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/TestProviderApiClientUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/TestProviderApiClientUtil.kt deleted file mode 100644 index 73d1c1353..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/TestProviderApiClientUtil.kt +++ /dev/null @@ -1,87 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api - -import com.appmattus.certificatetransparency.CTLogger -import com.appmattus.certificatetransparency.VerificationResult -import com.appmattus.certificatetransparency.certificateTransparencyTrustManager -import com.appmattus.certificatetransparency.loglist.LogListDataSourceFactory -import com.squareup.moshi.Moshi -import java.io.ByteArrayInputStream -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate -import javax.net.ssl.X509TrustManager -import nl.rijksoverheid.ctr.holder.BuildConfig -import okhttp3.OkHttpClient -import okhttp3.tls.HandshakeCertificates -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import timber.log.Timber - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface TestProviderApiClientUtil { - fun client( - tlsCertificateBytes: List, - cmsCertificateBytes: List - ): TestProviderApiClient -} - -class TestProviderApiClientUtilImpl( - private val moshi: Moshi, - private val okHttpClient: OkHttpClient, - private val retrofit: Retrofit -) : TestProviderApiClientUtil { - - private fun transparentTrustManager(trustManager: X509TrustManager) = - certificateTransparencyTrustManager(trustManager) { - if (BuildConfig.DEBUG) { - setLogger(object : CTLogger { - override fun log(host: String, result: VerificationResult) { - Timber.tag("certificate transparency") - .d("host: $host, verification result: $result") - } - }) - } - - setLogListService(LogListDataSourceFactory.createLogListService( - baseUrl = "https://www.gstatic.com/ct/log_list/v3/" - )) - } - - override fun client( - tlsCertificateBytes: List, - cmsCertificateBytes: List - ): TestProviderApiClient { - val okHttpClient = okHttpClient - .newBuilder() - .apply { - if (BuildConfig.FEATURE_TEST_PROVIDER_API_CHECKS) { - val handshakeCertificates = HandshakeCertificates.Builder() - .apply { - val certificateBytes = tlsCertificateBytes + cmsCertificateBytes - certificateBytes.forEach { - val certificateFactory = CertificateFactory.getInstance("X.509") - val x509Certificate = certificateFactory.generateCertificate( - ByteArrayInputStream(it) - ) as X509Certificate - addTrustedCertificate(x509Certificate) - } - } - .build() - - sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - transparentTrustManager(handshakeCertificates.trustManager) - ) - } - }.build() - - return retrofit.newBuilder().client(okHttpClient) - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .build().create(TestProviderApiClient::class.java) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/models/SignedResponseWithModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/models/SignedResponseWithModel.kt deleted file mode 100644 index 123a97bc7..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/models/SignedResponseWithModel.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.api.models - -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -class SignedResponseWithModel(val rawResponse: ByteArray, val model: T) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetCouplingData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetCouplingData.kt deleted file mode 100644 index 343fa79ba..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetCouplingData.kt +++ /dev/null @@ -1,13 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.post - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class GetCouplingData( - val credential: String, - val couplingCode: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetCredentialsPostData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetCredentialsPostData.kt deleted file mode 100644 index 9f8195fcd..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetCredentialsPostData.kt +++ /dev/null @@ -1,15 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.post - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class GetCredentialsPostData( - val stoken: String, - val events: List, - val issueCommitmentMessage: String, - val flows: List -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetTestIsmPostData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetTestIsmPostData.kt deleted file mode 100644 index d26d58426..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetTestIsmPostData.kt +++ /dev/null @@ -1,18 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.post - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class GetTestIsmPostData( - @Json(name = "stoken") val sToken: String, - val test: String, - val icm: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetTestResultPostData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetTestResultPostData.kt deleted file mode 100644 index b19523f49..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/post/GetTestResultPostData.kt +++ /dev/null @@ -1,15 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.post - -import com.squareup.moshi.JsonClass - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class GetTestResultPostData( - val verificationCode: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/CoronaCheckRepository.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/CoronaCheckRepository.kt deleted file mode 100644 index bfbf02c22..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/CoronaCheckRepository.kt +++ /dev/null @@ -1,177 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.repositories - -import android.util.Base64 -import nl.rijksoverheid.ctr.api.factory.NetworkRequestResultFactory -import nl.rijksoverheid.ctr.holder.api.HolderApiClient -import nl.rijksoverheid.ctr.holder.api.HolderApiClientUtil -import nl.rijksoverheid.ctr.holder.api.RemoteConfigApiClient -import nl.rijksoverheid.ctr.holder.api.post.GetCouplingData -import nl.rijksoverheid.ctr.holder.api.post.GetCredentialsPostData -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteAccessTokens -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.paper_proof.models.RemoteCouplingResponse -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.holder.your_events.models.RemotePrepareIssue -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.shared.models.CoronaCheckErrorResponse -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import okhttp3.ResponseBody -import okhttp3.ResponseBody.Companion.asResponseBody -import retrofit2.Converter - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -interface CoronaCheckRepository { - suspend fun configProviders(useCache: Boolean = true): NetworkRequestResult - suspend fun accessTokens(jwt: String): NetworkRequestResult - suspend fun getGreenCards( - stoken: String, - events: List, - issueCommitmentMessage: String, - flow: Flow - ): NetworkRequestResult - - suspend fun getPrepareIssue(): NetworkRequestResult - suspend fun getCoupling( - credential: String, - couplingCode: String - ): NetworkRequestResult -} - -private const val FUZZY_MATCHING_ERROR = 99790 -private const val VACCINATION_BACKEND_FLOW = "vaccination" -private const val POSITIVE_TEST_BACKEND_FLOW = "positivetest" -private const val NEGATIVE_TEST_BACKEND_FLOW = "negativetest" -private const val REFRESH_BACKEND_FLOW = "refresh" - -open class CoronaCheckRepositoryImpl( - private val cachedAppConfigUseCase: HolderCachedAppConfigUseCase, - private val holderApiClientUtil: HolderApiClientUtil, - private val remoteConfigApiClient: RemoteConfigApiClient, - private val errorResponseBodyConverter: Converter, - private val responseBodyConverter: Converter, - private val networkRequestResultFactory: NetworkRequestResultFactory -) : CoronaCheckRepository { - - private var cachedConfigProvidersResult: NetworkRequestResult? = null - - private fun getHolderApiClient(): HolderApiClient { - val backendTlsCertificates = - cachedAppConfigUseCase.getCachedAppConfig().backendTLSCertificates - val certificateBytes = backendTlsCertificates.map { it.toByteArray() } - return holderApiClientUtil.client(certificateBytes) - } - - override suspend fun configProviders(useCache: Boolean): NetworkRequestResult { - if (useCache) { - cachedConfigProvidersResult?.takeIf { - it is NetworkRequestResult.Success - }?.let { return it } - } - - val result = networkRequestResultFactory.createResult(HolderStep.ConfigProvidersNetworkRequest) { - remoteConfigApiClient.getConfigCtp() - } - cachedConfigProvidersResult = result - return result - } - - override suspend fun accessTokens(jwt: String): NetworkRequestResult { - return networkRequestResultFactory.createResult(HolderStep.AccessTokensNetworkRequest) { - getHolderApiClient().getAccessTokens(authorization = "Bearer $jwt") - } - } - - override suspend fun getGreenCards( - stoken: String, - events: List, - issueCommitmentMessage: String, - flow: Flow - ): NetworkRequestResult { - return networkRequestResultFactory.createResult( - step = HolderStep.GetCredentialsNetworkRequest, - networkCall = { - getHolderApiClient().getCredentials( - data = GetCredentialsPostData( - stoken = stoken, - events = events, - issueCommitmentMessage = Base64.encodeToString( - issueCommitmentMessage.toByteArray(), - Base64.NO_WRAP - ), - flows = when (flow) { - is HolderFlow.Vaccination -> listOf(VACCINATION_BACKEND_FLOW) - is HolderFlow.Recovery -> listOf(POSITIVE_TEST_BACKEND_FLOW) - is HolderFlow.CommercialTest, is HolderFlow.DigidTest -> listOf( - NEGATIVE_TEST_BACKEND_FLOW - ) - is HolderFlow.VaccinationAndPositiveTest -> listOf( - VACCINATION_BACKEND_FLOW, - POSITIVE_TEST_BACKEND_FLOW - ) - is HolderFlow.Refresh -> listOf(REFRESH_BACKEND_FLOW) - is HolderFlow.HkviScanned -> { - // Hkvi is a flow where you scanned a paper qr which holds one event. That event determines the backend flow. - when (flow.remoteProtocol.events?.first()) { - is RemoteEventVaccination -> listOf(VACCINATION_BACKEND_FLOW) - is RemoteEventNegativeTest -> listOf(NEGATIVE_TEST_BACKEND_FLOW) - is RemoteEventPositiveTest -> listOf(POSITIVE_TEST_BACKEND_FLOW) - else -> listOf(REFRESH_BACKEND_FLOW) - } - } - else -> listOf() - } - ) - ) - }, - interceptHttpError = { - it.response()?.errorBody()?.let { errorBody -> - val errorBodyBuffer = errorBody.source().buffer.clone() - errorResponseBodyConverter.convert(errorBody)?.let { coronaErrorResponse -> - if (coronaErrorResponse.code == FUZZY_MATCHING_ERROR) { - val errorBodyClone = errorBodyBuffer.asResponseBody( - errorBody.contentType(), - errorBody.contentLength() - ) - responseBodyConverter.convert(errorBodyClone) - } else { - null - } - } - } - } - ) - } - - override suspend fun getPrepareIssue(): NetworkRequestResult { - return networkRequestResultFactory.createResult(HolderStep.PrepareIssueNetworkRequest) { - getHolderApiClient().getPrepareIssue() - } - } - - override suspend fun getCoupling( - credential: String, - couplingCode: String - ): NetworkRequestResult { - return networkRequestResultFactory.createResult(HolderStep.CouplingNetworkRequest) { - getHolderApiClient().getCoupling( - data = GetCouplingData( - credential = credential, - couplingCode = couplingCode - ) - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/EventProviderRepository.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/EventProviderRepository.kt deleted file mode 100644 index 3305305dc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/EventProviderRepository.kt +++ /dev/null @@ -1,126 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.repositories - -import nl.rijksoverheid.ctr.api.factory.NetworkRequestResultFactory -import nl.rijksoverheid.ctr.api.interceptors.SigningCertificate -import nl.rijksoverheid.ctr.holder.api.TestProviderApiClient -import nl.rijksoverheid.ctr.holder.api.TestProviderApiClientUtil -import nl.rijksoverheid.ctr.holder.api.models.SignedResponseWithModel -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteUnomi -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface EventProviderRepository { - companion object { - /** - * Get filter for backend endpoints - */ - fun getFilter(originType: RemoteOriginType): String { - return when (originType) { - is RemoteOriginType.Vaccination -> { - "vaccination" - } - is RemoteOriginType.Recovery -> { - "positivetest" - } - is RemoteOriginType.Test -> { - "negativetest" - } - } - } - } - - suspend fun getUnomi( - url: String, - token: String, - filter: String, - scope: String?, - signingCertificateBytes: List, - provider: String, - tlsCertificateBytes: List - ): NetworkRequestResult - - suspend fun getEvents( - url: String, - token: String, - signingCertificateBytes: List, - filter: String, - scope: String?, - provider: String, - tlsCertificateBytes: List - ): NetworkRequestResult> -} - -class EventProviderRepositoryImpl( - private val testProviderApiClientUtil: TestProviderApiClientUtil, - private val networkRequestResultFactory: NetworkRequestResultFactory -) : EventProviderRepository { - - private fun getTestProviderApiClient(tlsCertificateBytes: List, cmsCertificateBytes: List): TestProviderApiClient { - return testProviderApiClientUtil.client(tlsCertificateBytes, cmsCertificateBytes) - } - - override suspend fun getUnomi( - url: String, - token: String, - filter: String, - scope: String?, - signingCertificateBytes: List, - provider: String, - tlsCertificateBytes: List - ): NetworkRequestResult { - val params = mutableMapOf() - params["filter"] = filter - scope?.let { - params["scope"] = scope - } - - return networkRequestResultFactory.createResult( - step = HolderStep.UnomiNetworkRequest, - provider = provider - ) { - getTestProviderApiClient(tlsCertificateBytes, signingCertificateBytes).getUnomi( - url = url, - authorization = "Bearer $token", - params = params, - certificate = SigningCertificate(signingCertificateBytes) - ).model - } - } - - override suspend fun getEvents( - url: String, - token: String, - signingCertificateBytes: List, - filter: String, - scope: String?, - provider: String, - tlsCertificateBytes: List - ): NetworkRequestResult> { - val params = mutableMapOf() - params["filter"] = filter - scope?.let { - params["scope"] = scope - } - - return networkRequestResultFactory.createResult( - step = HolderStep.EventNetworkRequest, - provider = provider - ) { - getTestProviderApiClient(tlsCertificateBytes, signingCertificateBytes).getEvents( - url = url, - authorization = "Bearer $token", - params = params, - certificate = SigningCertificate(signingCertificateBytes) - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/MijnCNAuthenticationRepository.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/MijnCNAuthenticationRepository.kt deleted file mode 100644 index dd49fe67a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/MijnCNAuthenticationRepository.kt +++ /dev/null @@ -1,96 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.repositories - -import android.content.Intent -import android.net.Uri -import androidx.activity.result.ActivityResultLauncher -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import net.openid.appauth.AuthorizationRequest -import net.openid.appauth.AuthorizationResponse -import net.openid.appauth.AuthorizationService -import net.openid.appauth.AuthorizationServiceConfiguration -import net.openid.appauth.ResponseTypeValues -import net.openid.appauth.TokenRequest -import nl.rijksoverheid.ctr.api.factory.NetworkRequestResultFactory -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.api.MijnCnApiClient -import nl.rijksoverheid.ctr.holder.get_events.models.MijnCNTokenResponse -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import nl.rijksoverheid.rdo.modules.openidconnect.OpenIDConnectRepository -import nl.rijksoverheid.rdo.modules.openidconnect.TokenResponse - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class MijnCNAuthenticationRepository( - private val mijnCnApiClient: MijnCnApiClient, - private val networkRequestResultFactory: NetworkRequestResultFactory -) : OpenIDConnectRepository { - - override suspend fun requestAuthorization( - issuerUrl: String, - activityResultLauncher: ActivityResultLauncher, - authService: AuthorizationService - ) { - val authServiceConfiguration = authorizationServiceConfiguration() - val authRequest = authRequest(serviceConfiguration = authServiceConfiguration) - val authIntent = authService.getAuthorizationRequestIntent(authRequest) - activityResultLauncher.launch(authIntent) - } - - private suspend fun authorizationServiceConfiguration(): AuthorizationServiceConfiguration { - return suspendCoroutine { continuation -> - AuthorizationServiceConfiguration.fetchFromIssuer(Uri.parse(BuildConfig.MIJNCN_BASEURL)) { serviceConfiguration, error -> - when { - error != null -> continuation.resumeWithException(error) - serviceConfiguration != null -> continuation.resume(serviceConfiguration) - else -> throw Exception("Could not get service configuration") - } - } - } - } - - private fun authRequest(serviceConfiguration: AuthorizationServiceConfiguration): AuthorizationRequest { - return AuthorizationRequest.Builder( - serviceConfiguration, - BuildConfig.OPEN_ID_CLIENT_ID, - ResponseTypeValues.CODE, - Uri.parse(BuildConfig.OPEN_ID_REDIRECT_URL) - ).setScope("openid email profile").build() - } - - override suspend fun tokenResponse( - authService: AuthorizationService, - authResponse: AuthorizationResponse - ): TokenResponse { - val request = authResponse.createTokenExchangeRequest() - val res = retrieveAccessToken(request) - return suspendCoroutine { continuation -> - when (res) { - is NetworkRequestResult.Success -> continuation.resume(TokenResponse(res.response.idToken, res.response.accessToken)) - is NetworkRequestResult.Failed -> continuation.resumeWithException(Exception("Failed to get JWT")) - } - } - } - - private suspend fun retrieveAccessToken(tokenRequest: TokenRequest): NetworkRequestResult { - val result = - networkRequestResultFactory.createResult(HolderStep.AccessTokensNetworkRequest) { - mijnCnApiClient.getAccessToken( - url = tokenRequest.configuration.tokenEndpoint.toString(), - code = tokenRequest.authorizationCode ?: "", - grantType = tokenRequest.grantType, - redirectUri = BuildConfig.OPEN_ID_REDIRECT_URL, - codeVerifier = tokenRequest.codeVerifier ?: "", - clientId = tokenRequest.clientId - ) - } - return result - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/TestProviderRepository.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/TestProviderRepository.kt deleted file mode 100644 index 053219251..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/api/repositories/TestProviderRepository.kt +++ /dev/null @@ -1,81 +0,0 @@ -package nl.rijksoverheid.ctr.holder.api.repositories - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.api.factory.NetworkRequestResultFactory -import nl.rijksoverheid.ctr.api.interceptors.SigningCertificate -import nl.rijksoverheid.ctr.holder.api.TestProviderApiClient -import nl.rijksoverheid.ctr.holder.api.TestProviderApiClientUtil -import nl.rijksoverheid.ctr.holder.api.models.SignedResponseWithModel -import nl.rijksoverheid.ctr.holder.api.post.GetTestResultPostData -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import okhttp3.ResponseBody -import retrofit2.Converter - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface TestProviderRepository { - suspend fun remoteTestResult( - url: String, - token: String, - provider: String, - verifierCode: String?, - signingCertificateBytes: List, - tlsCertificateBytes: List - ): NetworkRequestResult> -} - -class TestProviderRepositoryImpl( - private val testProviderApiClientUtil: TestProviderApiClientUtil, - private val networkRequestResultFactory: NetworkRequestResultFactory, - private val responseConverter: Converter> -) : TestProviderRepository { - - private fun getTestProviderApiClient(tlsCertificateBytes: List, cmsCertificateBytes: List): TestProviderApiClient { - return testProviderApiClientUtil.client(tlsCertificateBytes, cmsCertificateBytes) - } - - @Suppress("BlockingMethodInNonBlockingContext") - override suspend fun remoteTestResult( - url: String, - token: String, - provider: String, - verifierCode: String?, - signingCertificateBytes: List, - tlsCertificateBytes: List - ): NetworkRequestResult> { - return networkRequestResultFactory.createResult( - step = HolderStep.TestResultNetworkRequest, - provider = provider, - networkCall = { - getTestProviderApiClient(tlsCertificateBytes, signingCertificateBytes).getTestResult( - url = url, - authorization = "Bearer $token", - data = verifierCode?.let { - GetTestResultPostData( - it - ) - }, - certificate = SigningCertificate(signingCertificateBytes) - ) - }, - interceptHttpError = { - val errorBody = it.response()?.errorBody() - if ((it.code() == 401 || it.code() == 403) && errorBody != null) { - withContext(Dispatchers.IO) { - responseConverter.convert(errorBody) - } - } else { - null - } - } - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/certificate_created/CertificateCreatedFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/certificate_created/CertificateCreatedFragment.kt deleted file mode 100644 index cc4346e1c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/certificate_created/CertificateCreatedFragment.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.certificate_created - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentCertificateCreatedBinding -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety - -class CertificateCreatedFragment : - Fragment(R.layout.fragment_certificate_created) { - - private val args: CertificateCreatedFragmentArgs by navArgs() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentCertificateCreatedBinding.bind(view) - binding.bottom.setButtonClick { backToOverview() } - with(args) { - binding.title.text = title - binding.description.setHtmlText( - htmlText = description, - htmlLinksEnabled = true - ) - } - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : - OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - backToOverview() - } - }) - } - - private fun backToOverview() { - findNavControllerSafety()?.popBackStack() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/choose_proof_type/ChooseProofTypeFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/choose_proof_type/ChooseProofTypeFragment.kt deleted file mode 100644 index a64763567..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/choose_proof_type/ChooseProofTypeFragment.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.choose_proof_type - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentChooseProofTypeBinding -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.ui.create_qr.bind -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import org.koin.android.ext.android.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class ChooseProofTypeFragment : Fragment(R.layout.fragment_choose_proof_type) { - - private val featureFlagUseCase: HolderFeatureFlagUseCase by inject() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentChooseProofTypeBinding.bind(view) - - binding.negativeTestButton.bind( - R.string.qr_code_type_negative_test_title, - getString(R.string.qr_code_type_negative_test_description) - ) { - if (featureFlagUseCase.getGgdEnabled()) { - navigateSafety(ChooseProofTypeFragmentDirections.actionChooseProvider()) - } else { - navigateSafety(ChooseProofTypeFragmentDirections.actionInputToken()) - } - } - - binding.recoveryButton.bind( - R.string.qr_code_type_recovery_title, - getString(R.string.qr_code_type_recovery_description) - ) { - navigateSafety( - ChooseProofTypeFragmentDirections.actionGetEvents( - originType = RemoteOriginType.Recovery, - toolbarTitle = resources.getString(R.string.choose_provider_toolbar) - ) - ) - } - - binding.vaccinationButton.bind( - R.string.qr_code_type_vaccination_title, - getString(R.string.qr_code_type_vaccination_description) - ) { - navigateSafety( - ChooseProofTypeFragmentDirections.actionGetEvents( - originType = RemoteOriginType.Vaccination, - toolbarTitle = resources.getString(R.string.choose_provider_toolbar) - ) - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/choose_provider/ChooseProviderFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/choose_provider/ChooseProviderFragment.kt deleted file mode 100644 index 48c454fb6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/choose_provider/ChooseProviderFragment.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.choose_provider - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentChooseProviderBinding -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.ui.create_qr.bind -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.utils.Accessibility.setAsAccessibilityButton -import org.koin.android.ext.android.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class ChooseProviderFragment : Fragment(R.layout.fragment_choose_provider) { - - private val infoFragmentUtil: InfoFragmentUtil by inject() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentChooseProviderBinding.bind(view) - - binding.providerCommercial.bind( - R.string.choose_provider_commercial_title, - null - ) { - findNavController().navigate(ChooseProviderFragmentDirections.actionInputToken()) - } - - binding.providerGgd.bind( - R.string.choose_provider_ggd_title, - null - ) { - navigateSafety( - ChooseProviderFragmentDirections.actionGetEvents( - originType = RemoteOriginType.Test, - toolbarTitle = getString(R.string.choose_provider_toolbar) - ) - ) - } - - binding.notYetTested.setOnClickListener { - infoFragmentUtil.presentAsBottomSheet( - childFragmentManager, InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.not_yet_tested_title), - descriptionData = DescriptionData(R.string.not_yet_tested_description), - secondaryButtonData = ButtonData.LinkButton( - getString(R.string.not_yet_tested_button), - getString(R.string.url_make_appointment) - ) - ) - ) - } - - binding.providerCommercial.root.setAsAccessibilityButton() - } - - override fun onDestroyView() { - super.onDestroyView() - (parentFragment?.parentFragment as? HolderMainFragment)?.presentLoading(false) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/could_not_create_qr/CouldNotCreateQrFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/could_not_create_qr/CouldNotCreateQrFragment.kt deleted file mode 100644 index e6a2aff94..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/could_not_create_qr/CouldNotCreateQrFragment.kt +++ /dev/null @@ -1,41 +0,0 @@ -package nl.rijksoverheid.ctr.holder.could_not_create_qr - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.holder.databinding.FragmentCouldNotCreateQrBinding - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class CouldNotCreateQrFragment : Fragment() { - - private val args: CouldNotCreateQrFragmentArgs by navArgs() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return FragmentCouldNotCreateQrBinding.inflate(inflater, container, false).root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentCouldNotCreateQrBinding.bind(view) - binding.title.text = args.title - binding.description.setHtmlText(args.description, htmlLinksEnabled = true) - binding.bottom.setButtonClick { - findNavController().navigate(CouldNotCreateQrFragmentDirections.actionMyOverview()) - } - binding.bottom.setButtonText(args.buttonTitle) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardFragment.kt deleted file mode 100644 index bd7cae9c8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardFragment.kt +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard - -import android.graphics.Typeface -import android.os.Bundle -import android.view.View -import android.widget.TextView -import androidx.core.view.children -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.tabs.TabLayout -import com.google.android.material.tabs.TabLayoutMediator -import nl.rijksoverheid.ctr.appconfig.AppConfigViewModel -import nl.rijksoverheid.ctr.appconfig.usecases.ClockDeviationUseCase -import nl.rijksoverheid.ctr.design.R.dimen -import nl.rijksoverheid.ctr.design.menu.MenuViewModel -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.NavGraphOverviewDirections -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardSync -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardTabItem -import nl.rijksoverheid.ctr.holder.databinding.FragmentDashboardBinding -import nl.rijksoverheid.ctr.holder.fuzzy_matching.MatchingBlobIds -import nl.rijksoverheid.ctr.persistence.PersistenceManager -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.sharedViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class DashboardFragment : Fragment(R.layout.fragment_dashboard) { - - private var _binding: FragmentDashboardBinding? = null - private val binding get() = _binding!! - private val dashboardViewModel: DashboardViewModel by viewModel() - private val args: DashboardFragmentArgs by navArgs() - private val dialogUtil: DialogUtil by inject() - private val persistenceManager: PersistenceManager by inject() - private val clockDeviationUseCase: ClockDeviationUseCase by inject() - private val appConfigViewModel: AppConfigViewModel by sharedViewModel() - private val menuViewModel: MenuViewModel by viewModel() - - /** count of amount of tabs visible. When tab amount changes on policy change the adapter items need to be reset */ - private var tabItemsCount = 0 - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - _binding = FragmentDashboardBinding.bind(view) - val adapter = DashboardPagerAdapter( - childFragmentManager, - viewLifecycleOwner.lifecycle, - args.returnUri - ) - - setupViewPager(adapter) - observeServerTimeSynced() - observeItems(adapter) - observeSyncErrors() - observeAppConfig() - observeBottomElevation() - observeMenuItem() - observeDialogs() - pendingDialogs() - } - - private fun observeDialogs() { - dashboardViewModel.showMigrationDialogLiveData.observe( - viewLifecycleOwner, - EventObserver { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.holder_migrationFlow_deleteDetails_dialog_title, - message = getString(R.string.holder_migrationFlow_deleteDetails_dialog_message), - positiveButtonText = R.string.holder_migrationFlow_deleteDetails_dialog_deleteButton, - negativeButtonText = R.string.holder_migrationFlow_deleteDetails_dialog_retainButton, - positiveButtonCallback = { - dashboardViewModel.deleteMigrationData() - } - ) - }) - } - - private fun pendingDialogs() { - dashboardViewModel.showMigrationDialog() - } - - private fun observeBottomElevation() { - dashboardViewModel.bottomButtonElevationLiveData.observe(viewLifecycleOwner) { elevate -> - binding.bottom.elevation = if (elevate) { - resources.getDimensionPixelSize(dimen.scroll_view_button_elevation).toFloat() - } else { - 0f - } - } - } - - private fun observeMenuItem() { - menuViewModel.menuSectionLiveData.observe(viewLifecycleOwner, EventObserver { - navigateSafety( - DashboardFragmentDirections.actionMenu( - toolbarTitle = getString(R.string.general_menu), - menuSections = it - ) - ) - }) - } - - private fun setupViewPager(adapter: DashboardPagerAdapter) { - binding.viewPager.adapter = adapter - } - - /** - * Whenever the server time is synced we want to refresh our dashboard to check - * if we want to inform the user that the clock is not correct - */ - private fun observeServerTimeSynced() { - clockDeviationUseCase.serverTimeSyncedLiveData.observe(viewLifecycleOwner, EventObserver { - dashboardViewModel.refresh( - dashboardSync = DashboardSync.DisableSync - ) - }) - } - - private fun observeItems(adapter: DashboardPagerAdapter) { - dashboardViewModel.dashboardTabItemsLiveData.observe(viewLifecycleOwner) { dashboardTabItems -> - - val init = adapter.itemCount == 0 - - setupTabs( - binding = binding, - items = dashboardTabItems, - init = init - ) - - // Setup adapter only once - if (init || adapter.itemCount != tabItemsCount) { - tabItemsCount = dashboardTabItems.count() - - adapter.setItems(dashboardTabItems) - - // Default select the item that we had selected last - binding.viewPager.setCurrentItem( - persistenceManager.getSelectedDashboardTab(), - false - ) - - // Register listener so that last selected item is saved - binding.viewPager.registerOnPageChangeCallback(object : - ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - persistenceManager.setSelectedDashboardTab(position) - } - }) - } - - binding.addQrButton.isVisible = dashboardTabItems.any { dashboardTabItem -> - dashboardTabItem.items.any { it is DashboardItem.AddQrButtonItem } - } - binding.addQrButton.setOnClickListener { - navigateSafety( - DashboardFragmentDirections.actionChooseProofType() - ) - } - } - } - - private fun observeSyncErrors() { - dashboardViewModel.databaseSyncerResultLiveData.observe(viewLifecycleOwner, - EventObserver { - when (it) { - is DatabaseSyncerResult.Failed -> { - if (it is DatabaseSyncerResult.Failed.NetworkError && it.hasGreenCardsWithoutCredentials) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.dialog_title_no_internet, - message = getString(R.string.dialog_credentials_expired_no_internet), - positiveButtonText = R.string.app_status_internet_required_action, - positiveButtonCallback = { - refresh( - dashboardSync = DashboardSync.ForceSync - ) - }, - negativeButtonText = R.string.dialog_close - ) - } else if (it !is DatabaseSyncerResult.Failed.ServerError) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.dialog_title_no_internet, - message = getString(R.string.dialog_update_credentials_no_internet), - positiveButtonText = R.string.app_status_internet_required_action, - positiveButtonCallback = { - refresh( - dashboardSync = DashboardSync.ForceSync - ) - }, - negativeButtonText = R.string.dialog_close - ) - } - } - is DatabaseSyncerResult.FuzzyMatchingError -> { - navigateSafety( - NavGraphOverviewDirections.actionFuzzyMatching( - MatchingBlobIds(it.matchingBlobIds) - ) - ) - } - is DatabaseSyncerResult.Success -> { - // no extra action needed - } - } - } - ) - } - - private fun observeAppConfig() { - appConfigViewModel.appStatusLiveData.observe(viewLifecycleOwner) { - dashboardViewModel.refresh() - } - } - - private fun refresh(dashboardSync: DashboardSync = DashboardSync.CheckSync) { - dashboardViewModel.refresh(dashboardSync) - } - - private fun setupTabs( - binding: FragmentDashboardBinding, - items: List, - init: Boolean - ) { - if (items.size == 1) { - binding.tabs.visibility = View.GONE - binding.tabsSeparator.visibility = View.GONE - } else { - binding.tabs.visibility = View.VISIBLE - binding.tabsSeparator.visibility = View.VISIBLE - - if (init) { - TabLayoutMediator(binding.tabs, binding.viewPager) { tab, position -> - tab.view.setOnLongClickListener { - true - } - tab.text = getString(items[position].title) - }.attach() - - binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - val textView = tab.view.children.find { it is TextView } as? TextView - textView?.setTypeface(null, Typeface.BOLD) - } - - override fun onTabUnselected(tab: TabLayout.Tab) { - val textView = tab.view.children.find { it is TextView } as? TextView - textView?.setTypeface(null, Typeface.NORMAL) - } - - override fun onTabReselected(tab: TabLayout.Tab) { - val textView = tab.view.children.find { it is TextView } as? TextView - textView?.setTypeface(null, Typeface.BOLD) - } - }) - - // Call selectTab so that styling get's picked up on launch - binding.tabs.selectTab(binding.tabs.getTabAt(0)) - } - } - } - - override fun onPause() { - super.onPause() - - // Do this check because our screenshot fragment tests run in it's own test activity - if (parentFragment != null && requireParentFragment().parentFragment != null) { - (requireParentFragment().requireParentFragment() as HolderMainFragment).getToolbar().menu.clear() - } - } - - override fun onResume() { - super.onResume() - refresh() - - getToolbar().let { toolbar -> - if (toolbar?.menu?.size() == 0) { - toolbar.apply { - inflateMenu(R.menu.menu_toolbar) - menu.findItem(R.id.action_menu).actionView?.setOnClickListener { - menuViewModel.click(requireContext()) - } - } - } - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun getToolbar() = (parentFragment?.parentFragment as HolderMainFragment?)?.getToolbar() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardModule.kt deleted file mode 100644 index b16215138..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard - -import nl.rijksoverheid.ctr.holder.dashboard.datamappers.DashboardTabsItemDataMapper -import nl.rijksoverheid.ctr.holder.dashboard.datamappers.DashboardTabsItemDataMapperImpl -import org.koin.dsl.module - -val dashboardModule = module { - factory { DashboardTabsItemDataMapperImpl(get()) } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardPageFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardPageFragment.kt deleted file mode 100644 index 1460f2c0b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardPageFragment.kt +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView -import com.xwray.groupie.GroupAdapter -import com.xwray.groupie.GroupieViewHolder -import com.xwray.groupie.Section -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardAddQrCardAdapterItem -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardGreenCardAdapterItem -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardGreenCardPlaceHolderAdapterItem -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardHeaderAdapterItem -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardInfoCardAdapterItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardSync -import nl.rijksoverheid.ctr.holder.dashboard.usecases.ShowBlockedEventsDialogResult -import nl.rijksoverheid.ctr.holder.dashboard.util.CardItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardPageInfoItemHandlerUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.RemovedEventsBottomSheetUtil -import nl.rijksoverheid.ctr.holder.databinding.FragmentDashboardPageBinding -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeFragmentData -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety -import nl.rijksoverheid.ctr.shared.ext.getParcelableCompat -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.sharedViewModel - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class DashboardPageFragment : Fragment(R.layout.fragment_dashboard_page) { - - companion object { - const val EXTRA_GREEN_CARD_TYPE = "GREEN_CARD_TYPE" - const val EXTRA_RETURN_URI = "RETURN_URI" - - fun getInstance( - greenCardType: GreenCardType, - returnUri: String? - ): DashboardPageFragment { - val fragment = DashboardPageFragment() - val arguments = Bundle() - arguments.putParcelable(EXTRA_GREEN_CARD_TYPE, greenCardType) - arguments.putString(EXTRA_RETURN_URI, returnUri) - fragment.arguments = arguments - return fragment - } - } - - private val dashboardPageInfoItemHandlerUtil: DashboardPageInfoItemHandlerUtil by inject() - private val removedEventsBottomSheetUtil: RemovedEventsBottomSheetUtil by inject() - private val cardItemUtil: CardItemUtil by inject() - private val dialogUtil: DialogUtil by inject() - val dashboardViewModel: DashboardViewModel by sharedViewModel(owner = { - requireParentFragment() - }) - val section = Section() - private val greenCardType: GreenCardType by lazy { - arguments?.getParcelableCompat( - EXTRA_GREEN_CARD_TYPE - ) ?: error("EXTRA_GREEN_CARD_TYPE should not be null") - } - - override fun onResume() { - super.onResume() - view?.findViewById(R.id.recyclerView)?.let { - dashboardViewModel.scrollUpdate(it.canScrollVertically(RecyclerView.SCROLL_AXIS_VERTICAL), greenCardType) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentDashboardPageBinding.bind(view) - initRecyclerView(binding) - observeItem() - observeShowBlockedEventsDialog() - } - - private fun observeItem() { - dashboardViewModel.dashboardTabItemsLiveData.observe(viewLifecycleOwner) { - setItems( - myDashboardItems = it.firstOrNull { items -> items.greenCardType == greenCardType }?.items - ?: listOf() - ) - } - } - - private fun observeShowBlockedEventsDialog() { - dashboardViewModel.showBlockedEventsDialogLiveData.observe( - viewLifecycleOwner, - EventObserver { result -> - when (result) { - is ShowBlockedEventsDialogResult.Show -> { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.holder_invaliddetailsremoved_alert_title, - message = getString(R.string.holder_invaliddetailsremoved_alert_body), - positiveButtonText = R.string.holder_invaliddetailsremoved_alert_button_close, - positiveButtonCallback = { }, - negativeButtonText = R.string.holder_invaliddetailsremoved_alert_button_moreinfo, - negativeButtonCallback = { removedEventsBottomSheetUtil.presentBlockedEvents(this, result.blockedEvents) } - ) - } - ShowBlockedEventsDialogResult.None -> { - /* nothing */ - } - } - } - ) - } - - private fun initRecyclerView(binding: FragmentDashboardPageBinding) { - val adapter = GroupAdapter().also { - it.add(section) - } - binding.recyclerView.adapter = adapter - binding.recyclerView.itemAnimator = null - binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - dashboardViewModel.scrollUpdate( - canScrollVertically = recyclerView.canScrollVertically(RecyclerView.SCROLL_AXIS_VERTICAL), - greenCardType = greenCardType - ) - } - }) - } - - private fun setItems( - myDashboardItems: List - ) { - val adapterItems = mutableListOf>() - myDashboardItems.forEach { dashboardItem -> - when (dashboardItem) { - is DashboardItem.HeaderItem -> addHeader(adapterItems, dashboardItem) - is DashboardItem.PlaceholderCardItem -> addPlaceHolder(adapterItems, dashboardItem) - is DashboardItem.CardsItem -> addCards(adapterItems, dashboardItem) - is DashboardItem.AddQrButtonItem -> { - // Handled by MyOverviewTabsFragment - } - is DashboardItem.InfoItem -> addInfoCard(adapterItems, dashboardItem) - is DashboardItem.AddQrCardItem -> addAddQrCardItem(adapterItems) - } - } - - section.update(adapterItems) - } - - private fun addAddQrCardItem(adapterItems: MutableList>) { - adapterItems.add( - DashboardAddQrCardAdapterItem( - onButtonClick = { - findNavControllerSafety()?.navigate(DashboardPageFragmentDirections.actionChooseProofType()) - }) - ) - } - - private fun addInfoCard( - adapterItems: MutableList>, - dashboardItem: DashboardItem.InfoItem - ) { - adapterItems.add(DashboardInfoCardAdapterItem( - infoItem = dashboardItem, - onButtonClick = { - dashboardPageInfoItemHandlerUtil.handleButtonClick(this, it) - }, - onDismiss = { infoCardItem, infoItem -> - dashboardPageInfoItemHandlerUtil.handleDismiss( - this, - infoCardItem, - infoItem - ) - } - )) - } - - private fun addCards( - adapterItems: MutableList>, - dashboardItem: DashboardItem.CardsItem - ) { - adapterItems.add( - DashboardGreenCardAdapterItem( - cards = dashboardItem.cards, - onButtonClick = { cardItem, credentialsWithExpirationTime -> - navigateSafety( - DashboardPageFragmentDirections.actionQrCode( - toolbarTitle = when (cardItem.greenCard.greenCardEntity.type) { - is GreenCardType.Eu -> { - getString(R.string.my_overview_test_result_international_title) - } - }, data = QrCodeFragmentData( - shouldDisclose = cardItemUtil.shouldDisclose(cardItem), - credentialsWithExpirationTime = credentialsWithExpirationTime, - type = cardItem.greenCard.greenCardEntity.type, - originType = cardItem.greenCard.origins.first().type - ), returnUri = arguments?.getString(EXTRA_RETURN_URI) - ) - ) - }, - onRetryClick = { - dashboardViewModel.refresh( - dashboardSync = DashboardSync.ForceSync - ) - }, - onCountDownFinished = { - dashboardViewModel.refresh() - } - ) - ) - } - - private fun addPlaceHolder( - adapterItems: MutableList>, - dashboardItem: DashboardItem.PlaceholderCardItem - ) { - adapterItems.add( - DashboardGreenCardPlaceHolderAdapterItem( - greenCardType = dashboardItem.greenCardType - ) - ) - } - - private fun addHeader( - adapterItems: MutableList>, - dashboardItem: DashboardItem.HeaderItem - ) { - adapterItems.add( - DashboardHeaderAdapterItem( - text = dashboardItem.text, - buttonInfo = dashboardItem.buttonInfo - ) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardPagerAdapter.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardPagerAdapter.kt deleted file mode 100644 index 0c27a4a8e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardPagerAdapter.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Lifecycle -import androidx.viewpager2.adapter.FragmentStateAdapter -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardTabItem - -/** - * viewpager adapter to house green card overviews for domestic and European. - * - * @param[fragment] Tabs fragment with viewpager where the overviews are nested within. - * @param[returnToExternalAppUri] Uri used to return to external app from which it was deep linked from. - */ -class DashboardPagerAdapter( - fragmentManager: FragmentManager, - lifecycle: Lifecycle, - private val returnToExternalAppUri: String? -) : - FragmentStateAdapter(fragmentManager, lifecycle) { - - private val items: List = mutableListOf() - - fun setItems(items: List) { - (this.items as MutableList).clear() - this.items.addAll(items) - notifyDataSetChanged() - } - - override fun getItemCount(): Int = items.size - - override fun createFragment(position: Int): Fragment { - return DashboardPageFragment.getInstance( - greenCardType = items[position].greenCardType, - returnUri = returnToExternalAppUri - ) - } - - override fun getItemId(position: Int): Long { - return items[position].title.toLong() - } - - override fun containsItem(itemId: Long): Boolean = items.any { it.title.toLong() == itemId } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardViewModel.kt deleted file mode 100644 index 38a399bde..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/DashboardViewModel.kt +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import java.time.OffsetDateTime -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.dashboard.usecases.RemoveExpiredGreenCardsUseCase -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.dashboard.datamappers.DashboardTabsItemDataMapper -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardSync -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardTabItem -import nl.rijksoverheid.ctr.holder.dashboard.usecases.GetDashboardItemsUseCase -import nl.rijksoverheid.ctr.holder.dashboard.usecases.ShowBlockedEventsDialogResult -import nl.rijksoverheid.ctr.holder.dashboard.usecases.ShowBlockedEventsDialogUseCase -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardRefreshUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.PersistenceManager -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncer -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard -import nl.rijksoverheid.ctr.persistence.database.usecases.DraftEventUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveExpiredEventsUseCase -import nl.rijksoverheid.ctr.shared.livedata.Event - -abstract class DashboardViewModel : ViewModel() { - val dashboardTabItemsLiveData: LiveData> = MutableLiveData() - val databaseSyncerResultLiveData: LiveData> = MutableLiveData() - val showBlockedEventsDialogLiveData: LiveData> = - MutableLiveData() - val bottomButtonElevationLiveData: LiveData = MutableLiveData() - val showMigrationDialogLiveData: LiveData> = MutableLiveData() - - abstract fun refresh(dashboardSync: DashboardSync = DashboardSync.CheckSync) - abstract fun removeOrigin(originEntity: OriginEntity) - abstract fun dismissBlockedEventsInfo() - abstract fun dismissFuzzyMatchedEventsInfo() - - /** - * Post scroll updates from recyclerview scrolls and - * when a recyclerview becomes visible again in case of multiple active tabs - * When the current recyclerview can scroll more, we add an elevation to the bottom - * component to indicate the user he can scroll more - * @param canScrollVertically if reached end of scrolling - * @param greenCardType which greencard's type recyclerview interacting with - */ - abstract fun scrollUpdate(canScrollVertically: Boolean, greenCardType: GreenCardType) - abstract fun showMigrationDialog() - abstract fun deleteMigrationData() - - companion object { - val RETRY_FAILED_REQUEST_AFTER_SECONDS = - if (BuildConfig.FLAVOR == "acc") TimeUnit.SECONDS.toSeconds(10) else TimeUnit.MINUTES.toSeconds( - 10 - ) - } -} - -class DashboardViewModelImpl( - private val holderDatabase: HolderDatabase, - private val greenCardUtil: GreenCardUtil, - private val getDashboardItemsUseCase: GetDashboardItemsUseCase, - private val greenCardRefreshUtil: GreenCardRefreshUtil, - private val holderDatabaseSyncer: HolderDatabaseSyncer, - private val persistenceManager: PersistenceManager, - private val removeExpiredGreenCardsUseCase: RemoveExpiredGreenCardsUseCase, - private val dashboardTabsItemDataMapper: DashboardTabsItemDataMapper, - private val removeExpiredEventsUseCase: RemoveExpiredEventsUseCase, - private val draftEventUseCase: DraftEventUseCase, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val showBlockedEventsDialogUseCase: ShowBlockedEventsDialogUseCase -) : DashboardViewModel() { - - /** - * Refreshing of database happens every 60 seconds - */ - override fun refresh(dashboardSync: DashboardSync) { - if (loading()) { - return - } - viewModelScope.launch { - refreshCredentials(dashboardSync) - } - } - - private fun loading(): Boolean { - val cardItems = dashboardTabItemsLiveData.value?.flatMap { it.items } - ?.filterIsInstance() ?: return false - return cardItems.flatMap { it.cards } - .any { it.credentialState is DashboardItem.CardsItem.CredentialState.LoadingCredential } - } - - private suspend fun refreshCredentials(dashboardSync: DashboardSync) { - val previousSyncResult = databaseSyncerResultLiveData.value?.peekContent() - - // Check if we need to load new credentials - val shouldLoadNewCredentials = when (dashboardSync) { - is DashboardSync.ForceSync -> { - // Load new credentials if we force it. For example on a retry button click - true - } - is DashboardSync.DisableSync -> { - // Never load new credentials when we don't want to. For example if we are checking to show the clock skew banner - false - } - is DashboardSync.CheckSync -> { - // Load new credentials if no previous refresh has been executed and we should refresh because a credentials for a green card expired - val shouldRefreshCredentials = greenCardRefreshUtil.shouldRefresh() - - // Load new credentials if we the previous request failed more than once and more than x minutes ago - val shouldRetryFailedRequest = - previousSyncResult is DatabaseSyncerResult.Failed.ServerError.MultipleTimes && OffsetDateTime.now() - .isAfter( - previousSyncResult.failedAt.plusSeconds( - RETRY_FAILED_REQUEST_AFTER_SECONDS - ) - ) - - // Do the actual checks - (shouldRefreshCredentials || shouldRetryFailedRequest) && !featureFlagUseCase.isInArchiveMode() - } - } - - val allGreenCards = greenCardUtil.getAllGreenCards() - val allEventGroupEntities = holderDatabase.eventGroupDao().getAll() - - removeExpiredGreenCardsUseCase.execute( - allGreenCards = allGreenCards - ) - - refreshDashboardTabItems( - allGreenCards = allGreenCards, - databaseSyncerResult = databaseSyncerResultLiveData.value?.peekContent() - ?: DatabaseSyncerResult.Success(listOf()), - isLoadingNewCredentials = shouldLoadNewCredentials, - allEventGroupEntities = allEventGroupEntities - ) - - val databaseSyncerResult = holderDatabaseSyncer.sync( - syncWithRemote = shouldLoadNewCredentials, - previousSyncResult = previousSyncResult, - flow = HolderFlow.Refresh - ) - - if (databaseSyncerResult is DatabaseSyncerResult.Success) { - val result = showBlockedEventsDialogUseCase.execute( - blockedRemoteEvents = databaseSyncerResult.blockedEvents - ) - (showBlockedEventsDialogLiveData as MutableLiveData).postValue(Event(result)) - } - - (databaseSyncerResultLiveData as MutableLiveData).value = Event(databaseSyncerResult) - - // If we loaded new credentials, we want to update our items again - if (shouldLoadNewCredentials) { - refreshDashboardTabItems( - allGreenCards = greenCardUtil.getAllGreenCards(), - allEventGroupEntities = holderDatabase.eventGroupDao().getAll(), - databaseSyncerResult = databaseSyncerResult, - isLoadingNewCredentials = false - ) - } - - draftEventUseCase.remove() - - removeExpiredEventsUseCase.execute( - events = allEventGroupEntities - ) - } - - /** - * Remove the origin from a green card. - */ - override fun removeOrigin(originEntity: OriginEntity) { - viewModelScope.launch { - holderDatabase.originDao().delete(originEntity) - } - } - - private suspend fun refreshDashboardTabItems( - allEventGroupEntities: List, - allGreenCards: List, - databaseSyncerResult: DatabaseSyncerResult, - isLoadingNewCredentials: Boolean - ) { - val items = getDashboardItemsUseCase.getItems( - allGreenCards = allGreenCards, - databaseSyncerResult = databaseSyncerResult, - isLoadingNewCredentials = isLoadingNewCredentials, - allEventGroupEntities = allEventGroupEntities - ) - - val tabItems = dashboardTabsItemDataMapper.transform( - dashboardItems = items - ) - - (dashboardTabItemsLiveData as MutableLiveData>).postValue( - tabItems - ) - } - - override fun dismissBlockedEventsInfo() { - viewModelScope.launch { - holderDatabase.removedEventDao().deleteAll(reason = RemovedEventReason.Blocked) - } - } - - override fun dismissFuzzyMatchedEventsInfo() { - viewModelScope.launch { - holderDatabase.removedEventDao().deleteAll(reason = RemovedEventReason.FuzzyMatched) - } - } - - override fun scrollUpdate(canScrollVertically: Boolean, greenCardType: GreenCardType) { - val currentTab = persistenceManager.getSelectedDashboardTab() - val tabItems = dashboardTabItemsLiveData.value - val currentTabItem = tabItems?.getOrNull(currentTab) ?: return - - if (currentTabItem.greenCardType == greenCardType) { - (bottomButtonElevationLiveData as MutableLiveData).value = canScrollVertically - } - } - - override fun showMigrationDialog() { - if (persistenceManager.getShowMigrationDialog()) { - persistenceManager.setShowMigrationDialog(false) - (showMigrationDialogLiveData as MutableLiveData).postValue(Event(Unit)) - } - } - - override fun deleteMigrationData() { - viewModelScope.launch { - holderDatabase.eventGroupDao().deleteAll() - holderDatabase.greenCardDao().deleteAll() - holderDatabase.removedEventDao().deleteAll() - refresh(DashboardSync.ForceSync) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/datamappers/DashboardTabsItemDataMapper.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/datamappers/DashboardTabsItemDataMapper.kt deleted file mode 100644 index b27d54d00..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/datamappers/DashboardTabsItemDataMapper.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.datamappers - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItems -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardTabItem -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -interface DashboardTabsItemDataMapper { - suspend fun transform(dashboardItems: DashboardItems): List -} - -class DashboardTabsItemDataMapperImpl( - private val cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : DashboardTabsItemDataMapper { - - override suspend fun transform(dashboardItems: DashboardItems): List { - return withContext(Dispatchers.IO) { - val tabItems = mutableListOf() - - val internationalItem = DashboardTabItem( - title = R.string.travel_button_europe, - greenCardType = GreenCardType.Eu, - items = dashboardItems.internationalItems - ) - tabItems.add(internationalItem) - - tabItems - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardAddQrCardAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardAddQrCardAdapterItem.kt deleted file mode 100644 index 032d04339..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardAddQrCardAdapterItem.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemDashboardAddQrBinding -import org.koin.core.component.KoinComponent - -class DashboardAddQrCardAdapterItem( - private val onButtonClick: () -> Unit -) : BindableItem(R.layout.adapter_item_dashboard_add_qr.toLong()), - KoinComponent { - - override fun bind(viewBinding: AdapterItemDashboardAddQrBinding, position: Int) { - viewBinding.text.setOnClickListener { onButtonClick.invoke() } - } - - override fun getLayout(): Int = R.layout.adapter_item_dashboard_add_qr - - override fun initializeViewBinding(view: View): AdapterItemDashboardAddQrBinding { - return AdapterItemDashboardAddQrBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItem.kt deleted file mode 100644 index 99f2426f0..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItem.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.view.View -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.core.view.ViewCompat -import com.xwray.groupie.viewbinding.BindableItem -import java.time.Clock -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem.CardsItem.CredentialState.HasCredential -import nl.rijksoverheid.ctr.holder.dashboard.models.GreenCardEnabledState -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginState -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemDashboardGreenCardBinding -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -data class AdapterCard( - val greenCard: GreenCard, - val originStates: List -) - -class DashboardGreenCardAdapterItem( - private val cards: List, - private val onButtonClick: (cardItem: DashboardItem.CardsItem.CardItem, credentials: List>) -> Unit, - private val onRetryClick: () -> Unit = {}, - private val onCountDownFinished: () -> Unit = {} -) : - BindableItem(R.layout.adapter_item_dashboard_green_card.toLong()), - KoinComponent { - - private val dashboardGreenCardAdapterItemUtil: DashboardGreenCardAdapterItemUtil by inject() - private val dashboardGreenCardAdapterItemExpiryUtil: DashboardGreenCardAdapterItemExpiryUtil by inject() - private val cachedAppConfigUseCase: CachedAppConfigUseCase by inject() - private val clock: Clock by inject() - private val featureFlagUseCase: HolderFeatureFlagUseCase by inject() - - private val runnable = Runnable { - notifyChanged() - } - - private fun countdown(viewBinding: AdapterItemDashboardGreenCardBinding) { - val (expireDate, type) = cards.flatMap { it.originStates } - .map { Pair(it.origin.expirationTime, it.origin.type) } - .maxByOrNull { it.first } ?: return - - val expireCountDown = - dashboardGreenCardAdapterItemExpiryUtil.getExpireCountdown(expireDate, type) - - if (expireCountDown is DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.Show) { - if (expireCountDown.expired()) { - onCountDownFinished() - } else { - viewBinding.expiresIn.postDelayed(runnable, 1000) - } - } - } - - override fun bind(viewBinding: AdapterItemDashboardGreenCardBinding, position: Int) { - applyStyling(viewBinding = viewBinding) - setContent(viewBinding = viewBinding) - initButton( - viewBinding = viewBinding, - card = cards.first() - ) - accessibility( - viewBinding = viewBinding, - greenCardType = cards.first().greenCard.greenCardEntity.type - ) - countdown(viewBinding) - } - - private fun accessibility( - viewBinding: AdapterItemDashboardGreenCardBinding, - greenCardType: GreenCardType - ) { - viewBinding.buttonWithProgressWidgetContainer.accessibility( - viewBinding.title.text.toString() - ) - val imageContentDescription = viewBinding.root.context.getString( - when (greenCardType) { - GreenCardType.Eu -> R.string.validity_type_european_title - } - ) - viewBinding.headerContainer.contentDescription = - "${viewBinding.title.text} $imageContentDescription" - // Mark title of the cards as heading for accessibility - ViewCompat.setAccessibilityHeading(viewBinding.title, true) - } - - private fun initButton( - viewBinding: AdapterItemDashboardGreenCardBinding, - card: DashboardItem.CardsItem.CardItem - ) { - when (card.greenCardEnabledState) { - is GreenCardEnabledState.Enabled -> { - viewBinding.buttonWithProgressWidgetContainer.visibility = View.VISIBLE - viewBinding.disabledState.visibility = View.GONE - viewBinding.buttonWithProgressWidgetContainer.setButtonOnClickListener { - val mainCredentialState = cards.first().credentialState - if (mainCredentialState is HasCredential || featureFlagUseCase.isInArchiveMode()) { - val credentialEntities = cards.mapNotNull { - (it.credentialState as? HasCredential)?.credential - } - onButtonClick.invoke( - cards.first(), - credentialEntities.map { Pair(it.data, it.expirationTime) } - ) - } - } - } - is GreenCardEnabledState.Disabled -> { - viewBinding.buttonWithProgressWidgetContainer.visibility = View.GONE - viewBinding.disabledState.visibility = View.VISIBLE - viewBinding.disabledState.setText(card.greenCardEnabledState.text) - } - } - } - - private fun applyStyling(viewBinding: AdapterItemDashboardGreenCardBinding) { - viewBinding.buttonWithProgressWidgetContainer.setButtonText( - viewBinding.root.context.getString( - if (cards.size > 1) R.string.my_overview_results_button else R.string.my_overview_test_result_button - ) - ) - - val card = cards.first() - - when (card.greenCard.greenCardEntity.type) { - is GreenCardType.Eu -> { - viewBinding.internationalImageContainer.visibility = View.VISIBLE - viewBinding.buttonWithProgressWidgetContainer.setEnabledButtonColor(R.color.link) - } - } - - if (cards.first().credentialState is DashboardItem.CardsItem.CredentialState.LoadingCredential) { - viewBinding.buttonWithProgressWidgetContainer.loading() - } else { - viewBinding.buttonWithProgressWidgetContainer.idle( - isEnabled = cards.first().credentialState is HasCredential || featureFlagUseCase.isInArchiveMode() - ) - } - } - - private fun setContent(viewBinding: AdapterItemDashboardGreenCardBinding) { - // reset layout - viewBinding.run { - (proof2.layoutParams as ViewGroup.MarginLayoutParams).height = 0 - (proof3.layoutParams as ViewGroup.MarginLayoutParams).height = 0 - description.removeAllViews() - errorContainer.visibility = View.GONE - } - - dashboardGreenCardAdapterItemUtil.setContent( - DashboardGreenCardAdapterItemBindingWrapperImpl(viewBinding), - cards.map { AdapterCard(it.greenCard, it.originStates) } - .sortedByDescending { it.originStates.first().origin.eventTime } - ) - - stackAdditionalCards(viewBinding) - - showError(viewBinding) - } - - /** - * Show a border of extra cards when item has additional items of the same type - * - * @param[viewBinding] view binding containing binding of parent view group of green cards - */ - private fun stackAdditionalCards(viewBinding: AdapterItemDashboardGreenCardBinding) { - viewBinding.apply { - if (cards.size >= 2) { - (proof2.layoutParams as ViewGroup.MarginLayoutParams).height = - viewBinding.root.context.resources.getDimensionPixelSize(R.dimen.dashboard_card_additional_card_height) - } - - if (cards.size >= 3) { - (proof3.layoutParams as ViewGroup.MarginLayoutParams).height = - viewBinding.root.context.resources.getDimensionPixelSize(R.dimen.dashboard_card_additional_card_height) - } - } - } - - private fun showError(viewBinding: AdapterItemDashboardGreenCardBinding) { - val context = viewBinding.root.context - val credentialState = cards.first().credentialState - val noCredential = credentialState is DashboardItem.CardsItem.CredentialState.NoCredential - val credentialExpired = - credentialState is DashboardItem.CardsItem.CredentialState.HasCredential && credentialState.credential.expirationTime.isBefore( - OffsetDateTime.now(clock) - ) - if (noCredential || credentialExpired) { - when (cards.first().databaseSyncerResult) { - is DatabaseSyncerResult.Failed.NetworkError -> { - viewBinding.errorText.setHtmlText( - htmlText = context.getString(R.string.my_overview_green_card_internet_error), - htmlTextColor = ContextCompat.getColor(context, R.color.error), - htmlTextColorLink = ContextCompat.getColor(context, R.color.error) - ) - viewBinding.errorText.enableCustomLinks(onRetryClick) - viewBinding.errorContainer.visibility = View.VISIBLE - } - is DatabaseSyncerResult.Failed.ServerError.FirstTime -> { - viewBinding.errorText.setHtmlText( - htmlText = context.getString(R.string.my_overview_green_card_server_error), - htmlTextColor = ContextCompat.getColor(context, R.color.error), - htmlTextColorLink = ContextCompat.getColor(context, R.color.error) - ) - viewBinding.errorText.enableCustomLinks(onRetryClick) - viewBinding.errorContainer.visibility = View.VISIBLE - } - is DatabaseSyncerResult.Failed.ServerError.MultipleTimes -> { - viewBinding.errorText.setHtmlText( - htmlText = context.getString( - R.string.my_overview_green_card_server_error_after_retry, - cachedAppConfigUseCase.getCachedAppConfig().contactInfo.phoneNumber - ), - htmlTextColor = ContextCompat.getColor(context, R.color.error), - htmlTextColorLink = ContextCompat.getColor(context, R.color.error) - ) - viewBinding.errorContainer.visibility = View.VISIBLE - } - else -> { - } - } - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_dashboard_green_card - } - - override fun initializeViewBinding(view: View): AdapterItemDashboardGreenCardBinding { - return AdapterItemDashboardGreenCardBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemBindingWrapper.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemBindingWrapper.kt deleted file mode 100644 index afb5d9aa8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemBindingWrapper.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.widget.LinearLayout -import android.widget.TextView -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemDashboardGreenCardBinding - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface DashboardGreenCardAdapterItemBindingWrapper { - val title: TextView - val description: LinearLayout - val expiresIn: TextView -} - -class DashboardGreenCardAdapterItemBindingWrapperImpl(private val viewBinding: AdapterItemDashboardGreenCardBinding) : - DashboardGreenCardAdapterItemBindingWrapper { - - override val title: TextView - get() = viewBinding.title - - override val description: LinearLayout - get() = viewBinding.description - - override val expiresIn: TextView - get() = viewBinding.expiresIn -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemExpiryUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemExpiryUtil.kt deleted file mode 100644 index 56d11285e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemExpiryUtil.kt +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.content.Context -import java.time.Clock -import java.time.Instant -import java.time.OffsetDateTime -import java.time.temporal.ChronoUnit -import java.util.concurrent.TimeUnit -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -interface DashboardGreenCardAdapterItemExpiryUtil { - sealed class ExpireCountDown { - data class Show( - val daysLeft: Long, - val hoursLeft: Long, - val minutesLeft: Long, - val secondsLeft: Long - ) : ExpireCountDown() { - fun expired(): Boolean = arrayOf( - daysLeft, - hoursLeft, - minutesLeft, - secondsLeft).all { it == 0L } - } - object Hide : ExpireCountDown() - data class ShowExpired(val date: String) : ExpireCountDown() - } - - fun getExpireCountdown( - expireDate: OffsetDateTime, - type: OriginType - ): ExpireCountDown - - fun getExpiryText(result: ExpireCountDown.Show): String - - /** - * Get the last origin that's valid if it's the only valid one. - * - * @param[origins] origins list to get the last one valid from - * @return last origin that's valid or null when none or more are valid - */ - fun getLastValidOrigin(origins: List): OriginEntity? -} - -class DashboardGreenCardAdapterItemExpiryUtilImpl( - private val clock: Clock, - private val context: Context -) : DashboardGreenCardAdapterItemExpiryUtil { - - override fun getExpireCountdown( - expireDate: OffsetDateTime, - type: OriginType - ): DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown { - if (expireDate.isBefore(OffsetDateTime.now(clock))) { - val date = expireDate.toLocalDate().formatDayMonthYear() - return DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.ShowExpired(date) - } - val hoursBetweenExpiration = - ChronoUnit.HOURS.between(OffsetDateTime.now(clock), expireDate) - return if (hoursBetweenExpiration >= getExpiryHoursForType(type)) { - DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.Hide - } else { - var diff = expireDate.toInstant().toEpochMilli() - Instant.now(clock).toEpochMilli() - val daysUntilFinish = diff / TimeUnit.DAYS.toMillis(1) - diff %= TimeUnit.DAYS.toMillis(1) - val hoursUntilFinish = diff / TimeUnit.HOURS.toMillis(1) - diff %= TimeUnit.HOURS.toMillis(1) - val minutesUntilFinish = diff / TimeUnit.MINUTES.toMillis(1) - diff %= TimeUnit.MINUTES.toMillis(1) - val secondsUntilFinish = diff / TimeUnit.SECONDS.toMillis(1) - diff %= TimeUnit.SECONDS.toMillis(1) - - DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.Show( - daysLeft = daysUntilFinish, - hoursLeft = hoursUntilFinish, - minutesLeft = minutesUntilFinish, - secondsLeft = secondsUntilFinish - ) - } - } - - private fun getExpiryHoursForType(type: OriginType): Int { - return if (type == OriginType.Test) { - TimeUnit.HOURS.toHours(6).toInt() - } else { - TimeUnit.DAYS.toHours(21).toInt() - } - } - - override fun getExpiryText( - result: DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.Show - ): String { - val daysLeft = result.daysLeft.toInt() - val hoursLeft = result.hoursLeft.toInt() - val minutesLeft = result.minutesLeft.toInt() - val secondsLeft = result.secondsLeft.toInt() - return when { - daysLeft >= 2 -> { - context.getString( - R.string.my_overview_test_result_expires_in, - "$daysLeft ${ - context.resources.getQuantityString( - R.plurals.general_days, - daysLeft - ) - }" - ) - } - daysLeft >= 1 -> { - context.getString( - R.string.my_overview_test_result_expires_in_hours_minutes, - "$daysLeft ${ - context.resources.getQuantityString( - R.plurals.general_days, - daysLeft - ) - }", - "$hoursLeft ${ - context.resources.getQuantityString( - R.plurals.my_overview_test_result_expires_hours, - hoursLeft - ) - }" - ) - } - hoursLeft >= 1 -> { - context.getString( - R.string.my_overview_test_result_expires_in_hours_minutes, - "$hoursLeft ${ - context.resources.getQuantityString( - R.plurals.my_overview_test_result_expires_hours, - hoursLeft - ) - }", - "$minutesLeft ${ - context.resources.getQuantityString( - R.plurals.my_overview_test_result_expires_minutes, - minutesLeft - ) - }" - ) - } - minutesLeft >= 5 -> { - context.getString( - R.string.my_overview_test_result_expires_in, - "$minutesLeft ${ - context.resources.getQuantityString( - R.plurals.my_overview_test_result_expires_minutes, - minutesLeft - ) - }" - ) - } - minutesLeft <= 5 && minutesLeft != 0 -> { - context.getString( - R.string.my_overview_test_result_expires_in_hours_minutes, - "$minutesLeft ${ - context.resources.getQuantityString( - R.plurals.my_overview_test_result_expires_minutes, - minutesLeft - ) - }", - "$secondsLeft ${context.resources.getString(R.string.general_seconds)}" - ) - } - else -> { - context.getString( - R.string.my_overview_test_result_expires_in, - "$secondsLeft ${context.resources.getString(R.string.general_seconds)}" - ) - } - } - } - - override fun getLastValidOrigin(origins: List): OriginEntity? { - return origins.filter { it.expirationTime > OffsetDateTime.now(clock) } - .takeIf { it.size == 1 } - ?.firstOrNull() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemUtil.kt deleted file mode 100644 index c9f5f89f4..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardAdapterItemUtil.kt +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.content.Context -import android.view.View -import android.widget.LinearLayout -import android.widget.Space -import android.widget.TextView -import androidx.core.content.ContextCompat -import java.time.Clock -import java.time.Instant -import java.util.concurrent.TimeUnit -import nl.rijksoverheid.ctr.design.ext.formatDateTime -import nl.rijksoverheid.ctr.design.ext.formatDayMonthTime -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.util.CredentialUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginState -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard -import nl.rijksoverheid.ctr.shared.ext.capitalize -import nl.rijksoverheid.ctr.shared.ext.locale -import org.koin.core.component.KoinComponent - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface DashboardGreenCardAdapterItemUtil { - fun setContent( - dashboardGreenCardAdapterItemBinding: DashboardGreenCardAdapterItemBindingWrapper, - cards: List - ) -} - -class DashboardGreenCardAdapterItemUtilImpl( - private val utcClock: Clock, - private val context: Context, - private val credentialUtil: CredentialUtil, - private val dashboardGreenCardAdapterItemExpiryUtil: DashboardGreenCardAdapterItemExpiryUtil -) : DashboardGreenCardAdapterItemUtil, KoinComponent { - - override fun setContent( - dashboardGreenCardAdapterItemBinding: DashboardGreenCardAdapterItemBindingWrapper, - cards: List - ) { - cards.forEach { card -> - val it = card.greenCard - when (it.greenCardEntity.type) { - is GreenCardType.Eu -> { - // European card only has one origin - val originState = card.originStates.first() - val origin = originState.origin - when (origin.type) { - is OriginType.Test -> { - dashboardGreenCardAdapterItemBinding.title.text = - context.getString(R.string.general_testcertificate_0G).capitalize() - setEuTestOrigin( - dashboardGreenCardAdapterItemBinding, it, originState, origin - ) - setExpiryText(origin, dashboardGreenCardAdapterItemBinding) - } - - is OriginType.Vaccination -> { - dashboardGreenCardAdapterItemBinding.title.text = - context.getString(R.string.general_vaccinationcertificate_0G) - .capitalize() - setEuVaccinationOrigin( - dashboardGreenCardAdapterItemBinding, it, origin - ) - setExpiryText(origin, dashboardGreenCardAdapterItemBinding) - } - - is OriginType.Recovery -> { - dashboardGreenCardAdapterItemBinding.title.text = - context.getString(R.string.general_recoverycertificate_0G) - .capitalize() - setEuRecoveryOrigin( - dashboardGreenCardAdapterItemBinding, - originState, - origin - ) - setExpiryText(origin, dashboardGreenCardAdapterItemBinding) - } - } - } - } - } - - val originStates = cards.first().originStates - val becomesValidAutomatically = - originStates.size == 1 && originStates.first() is OriginState.Future - if (becomesValidAutomatically) { - dashboardGreenCardAdapterItemBinding.expiresIn.visibility = View.VISIBLE - dashboardGreenCardAdapterItemBinding.expiresIn.text = - context.getString(R.string.qr_card_validity_future) - } - } - - /** - * Returns if the origin will expire in more than three years from now - * @param origin The origin to check - */ - private fun originExpirationTimeThreeYearsFromNow(origin: OriginEntity): Boolean { - val expirationSecondsFromNow = - origin.expirationTime.toInstant().epochSecond - Instant.now(utcClock).epochSecond - val expirationYearsFromNow = TimeUnit.SECONDS.toDays(expirationSecondsFromNow) / 365 - return expirationYearsFromNow >= 3 - } - - private fun setEuRecoveryOrigin( - dashboardGreenCardAdapterItemBinding: DashboardGreenCardAdapterItemBindingWrapper, - originState: OriginState, - origin: OriginEntity - ) { - // EU recovery description has no title so we put only the space in between for correct alignment - if (originState !is OriginState.Expired) { - dashboardGreenCardAdapterItemBinding.description.addView( - Space(context), LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - context.resources.getDimensionPixelSize(R.dimen.green_card_item_proof_spacing) - ) - ) - } - setOriginSubtitle( - descriptionLayout = dashboardGreenCardAdapterItemBinding.description, - originState = originState, - showTime = true, - subtitle = context.getString( - R.string.qr_card_validity_valid, - origin.expirationTime.toLocalDate().formatDayMonthYear() - ) - ) - } - - private fun setEuVaccinationOrigin( - dashboardGreenCardAdapterItemBinding: DashboardGreenCardAdapterItemBindingWrapper, - greenCard: GreenCard, - origin: OriginEntity - ) { - val getCurrentDosesString: (String, String, String) -> String = - { currentDose: String, sumDoses: String, country: String -> - val dosisString = context.getString( - R.string.qr_card_vaccination_doses, - currentDose, sumDoses - ) - if (country.isNotEmpty()) { - "$dosisString$country" - } else { - dosisString - } - } - val doses = credentialUtil.getVaccinationDosesCountryLineForEuropeanCredentials( - greenCard.credentialEntities, - context.locale().language, - getCurrentDosesString - ) - setOriginTitle( - descriptionLayout = dashboardGreenCardAdapterItemBinding.description, - title = doses - ) - - setOriginSubtitle( - descriptionLayout = dashboardGreenCardAdapterItemBinding.description, - // force a valid origin, as we need to allow the user to view the QR - // and when is valid from, it depends from the country going to - originState = OriginState.Valid(greenCard.origins.first()), - showTime = false, - subtitle = "${context.getString(R.string.qr_card_vaccination_title_eu)} ${ - origin.eventTime.toLocalDate().formatDayMonthYear() - }" - ) - } - - private fun setEuTestOrigin( - dashboardGreenCardAdapterItemBinding: DashboardGreenCardAdapterItemBindingWrapper, - greenCard: GreenCard, - originState: OriginState, - origin: OriginEntity - ) { - setOriginTitle( - descriptionLayout = dashboardGreenCardAdapterItemBinding.description, - title = "${context.getString(R.string.qr_card_test_eu)} ${ - credentialUtil.getTestTypeForEuropeanCredentials( - greenCard.credentialEntities - ) - }" - ) - - setOriginSubtitle( - descriptionLayout = dashboardGreenCardAdapterItemBinding.description, - originState = originState, - showTime = false, - subtitle = "${context.getString(R.string.qr_card_test_title_eu)} ${ - origin.eventTime.formatDateTime( - context - ) - }" - ) - } - - private fun setOriginTitle( - descriptionLayout: LinearLayout, - title: String - ) { - descriptionLayout.addView( - TextView(descriptionLayout.context).apply { - setTextAppearance(R.style.App_TextAppearance_MaterialComponents_Body1) - text = title - setTextIsSelectable(true) - }, - LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ).apply { - val topMargin = - context.resources.getDimensionPixelSize(R.dimen.green_card_item_proof_spacing) - setMargins(0, topMargin, 0, 0) - } - ) - } - - private fun setOriginSubtitle( - descriptionLayout: LinearLayout, - originState: OriginState, - showTime: Boolean, - subtitle: String - ) { - val textView = TextView(descriptionLayout.context).apply { - setTextAppearance(R.style.App_TextAppearance_MaterialComponents_Body1) - setTextIsSelectable(true) - } - - when (originState) { - is OriginState.Future -> { - val showUntil = - originState.origin.type == OriginType.Vaccination && - !originExpirationTimeThreeYearsFromNow(originState.origin) || - originState.origin.type == OriginType.Recovery - - val validFromDateTime = originState.origin.validFrom - val validFrom = if (showTime) { - validFromDateTime.formatDayMonthTime(context) - } else { - validFromDateTime.toLocalDate().formatDayMonthYear() - } - textView.text = context.getString( - R.string.qr_card_validity_future_from, validFrom, if (showUntil) { - val until = - originState.origin.expirationTime.toLocalDate().formatDayMonthYear() - context.getString(R.string.qr_card_validity_future_until, until) - } else { - "" - } - ) - textView.visibility = View.VISIBLE - } - - is OriginState.Valid -> { - textView.text = subtitle - textView.visibility = View.VISIBLE - } - - is OriginState.Expired -> { - // Should be filtered out and never reach here - } - } - - descriptionLayout.addView(textView) - } - - private fun setExpiryText( - origin: OriginEntity, - dashboardGreenCardAdapterItemBinding: DashboardGreenCardAdapterItemBindingWrapper - ) { - val expireCountDownResult = dashboardGreenCardAdapterItemExpiryUtil.getExpireCountdown( - origin.expirationTime, origin.type - ) - when (expireCountDownResult) { - is DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.Show -> { - dashboardGreenCardAdapterItemBinding.expiresIn.visibility = View.VISIBLE - dashboardGreenCardAdapterItemBinding.expiresIn.text = - dashboardGreenCardAdapterItemExpiryUtil.getExpiryText( - expireCountDownResult - ) - } - - is DashboardGreenCardAdapterItemExpiryUtil.ExpireCountDown.ShowExpired -> { - dashboardGreenCardAdapterItemBinding.expiresIn.visibility = View.VISIBLE - val context = dashboardGreenCardAdapterItemBinding.expiresIn.context - val expiredInText = context.getString(R.string.holder_dashboard_qrValidityDate_expired) - dashboardGreenCardAdapterItemBinding.expiresIn.setTextColor(ContextCompat.getColor(context, R.color.error)) - - dashboardGreenCardAdapterItemBinding.expiresIn.text = "$expiredInText ${expireCountDownResult.date}" - } - - else -> { - dashboardGreenCardAdapterItemBinding.expiresIn.visibility = View.GONE - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardPlaceHolderAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardPlaceHolderAdapterItem.kt deleted file mode 100644 index fc69e4a6c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardGreenCardPlaceHolderAdapterItem.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemDashboardGreenCardPlaceholderBinding -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class DashboardGreenCardPlaceHolderAdapterItem(private val greenCardType: GreenCardType) : - BindableItem(R.layout.adapter_item_dashboard_green_card_placeholder.toLong()) { - - override fun getLayout(): Int { - return R.layout.adapter_item_dashboard_green_card_placeholder - } - - override fun initializeViewBinding(view: View): AdapterItemDashboardGreenCardPlaceholderBinding { - return AdapterItemDashboardGreenCardPlaceholderBinding.bind(view) - } - - override fun bind(viewBinding: AdapterItemDashboardGreenCardPlaceholderBinding, position: Int) { - val isEu = greenCardType == GreenCardType.Eu - - viewBinding.icon.setBackgroundResource(if (isEu) { - R.drawable.ic_illustration_hand_qr_placeholder_eu - } else { - R.drawable.ic_illustration_hand_qr_placeholder - }) - viewBinding.title.text = viewBinding.title.context.getString(if (isEu) { - R.string.my_overview_qr_placeholder_header_eu - } else { - R.string.my_overview_qr_placeholder_header - }) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardHeaderAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardHeaderAdapterItem.kt deleted file mode 100644 index eea7e6806..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardHeaderAdapterItem.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.view.View -import androidx.annotation.StringRes -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.design.utils.IntentUtil -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemDashboardHeaderBinding -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class ButtonInfo(@StringRes val text: Int, @StringRes val link: Int) - -class DashboardHeaderAdapterItem(@StringRes private val text: Int, private val buttonInfo: ButtonInfo?) : - BindableItem(R.layout.adapter_item_dashboard_header.toLong()), KoinComponent { - - private val intentUtil: IntentUtil by inject() - - override fun bind(viewBinding: AdapterItemDashboardHeaderBinding, position: Int) { - viewBinding.text.setHtmlText(text, htmlLinksEnabled = true) - viewBinding.button.run { - if (buttonInfo != null) { - visibility = View.VISIBLE - setText(buttonInfo.text) - setOnClickListener { - intentUtil.openUrl( - context = context, - url = context.getString(buttonInfo.link) - ) - } - } else { - visibility = View.GONE - } - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_dashboard_header - } - - override fun initializeViewBinding(view: View): AdapterItemDashboardHeaderBinding { - return AdapterItemDashboardHeaderBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardHeaderAdapterItemUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardHeaderAdapterItemUtil.kt deleted file mode 100644 index f9f70cecc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardHeaderAdapterItemUtil.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -interface DashboardHeaderAdapterItemUtil { - fun getHeaderItem( - greenCardType: GreenCardType, - emptyState: Boolean - ): DashboardItem.HeaderItem -} - -class DashboardHeaderAdapterItemUtilImpl : DashboardHeaderAdapterItemUtil { - - /** - * Get the header item text to display in the domestic tab on the dashboard screen - * @param greenCardType The type of the tab that is currently selected - * @param emptyState If we should treat the dashboard as being empty state - * @param hasVisitorPassIncompleteItem If there is a incomplete visitor pass item currently showing on the dashboard - */ - override fun getHeaderItem( - greenCardType: GreenCardType, - emptyState: Boolean - ): DashboardItem.HeaderItem { - val text = getHeaderText(greenCardType, emptyState) - val buttonInfo = getButtonInfo(greenCardType, emptyState) - return DashboardItem.HeaderItem(text, buttonInfo) - } - - private fun getHeaderText( - tabType: GreenCardType, - emptyState: Boolean - ) = when (tabType) { - is GreenCardType.Eu -> { - if (emptyState) { - R.string.holder_dashboard_emptyState_international_0G_message - } else { - R.string.holder_dashboard_filledState_international_0G_message - } - } - } - - private fun getButtonInfo(tabType: GreenCardType, empty: Boolean) = - if (tabType == GreenCardType.Eu) { - if (empty) { - ButtonInfo( - R.string.holder_dashboard_international_0G_action_certificateNeeded, - R.string.my_overview_description_eu_button_link - ) - } else { - ButtonInfo( - R.string.my_overview_description_eu_button_text, - R.string.my_overview_description_eu_button_link - ) - } - } else { - null - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardInfoCardAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardInfoCardAdapterItem.kt deleted file mode 100644 index 635d5780e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardInfoCardAdapterItem.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.view.View -import androidx.core.content.ContextCompat -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemDashboardInfoCardBinding -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class DashboardInfoCardAdapterItem( - private val infoItem: DashboardItem.InfoItem, - private val onButtonClick: (infoItem: DashboardItem.InfoItem) -> Unit, - private val onDismiss: (infoCardAdapterItem: DashboardInfoCardAdapterItem, infoItem: DashboardItem.InfoItem) -> Unit = { _, _ -> } -) : BindableItem(R.layout.adapter_item_dashboard_info_card.toLong()), - KoinComponent { - - private val utilAdapter: DashboardInfoCardAdapterItemUtil by inject() - - override fun bind(viewBinding: AdapterItemDashboardInfoCardBinding, position: Int) { - if (infoItem.isDismissible) { - // dismissible item has a close button with callback - viewBinding.close.visibility = View.VISIBLE - viewBinding.close.setOnClickListener { - onDismiss.invoke(this, infoItem) - } - } else { - // Non dismissible item does not have a close button - viewBinding.close.visibility = View.GONE - } - - viewBinding.button.run { - val buttonTextId = infoItem.buttonText ?: R.string.general_readmore - visibility = if (infoItem.hasButton) View.VISIBLE else View.GONE - setText(buttonTextId) - contentDescription = context.getString(buttonTextId) - } - - when (infoItem) { - is DashboardItem.InfoItem.ExportPdf -> { - viewBinding.text.setText(R.string.holder_pdfExport_card_description) - val context = viewBinding.dashboardItemInfoRoot.context - viewBinding.dashboardItemInfoRoot.setCardBackgroundColor(ContextCompat.getColor(context, R.color.export_pdf_info_item_background)) - } - is DashboardItem.InfoItem.ConfigFreshnessWarning -> { - viewBinding.text.setText(R.string.config_warning_card_message) - } - is DashboardItem.InfoItem.ClockDeviationItem -> { - viewBinding.text.setText(R.string.my_overview_clock_deviation_description) - } - is DashboardItem.InfoItem.GreenCardExpiredItem -> { - val expiredItemText = utilAdapter.getExpiredItemText( - greenCardType = infoItem.greenCardType, - originType = infoItem.originEntity.type - ) - viewBinding.text.text = viewBinding.root.context.getString(expiredItemText) - } - is DashboardItem.InfoItem.OriginInfoItem -> { - viewBinding.text.text = - utilAdapter.getOriginInfoText(infoItem, viewBinding.dashboardItemInfoRoot.context) - } - is DashboardItem.InfoItem.AppUpdate -> { - viewBinding.text.setText(R.string.recommended_update_card_description) - } - is DashboardItem.InfoItem.BlockedEvents -> { - viewBinding.text.setText( - R.string.holder_invaliddetailsremoved_banner_title - ) - } - is DashboardItem.InfoItem.FuzzyMatchedEvents -> { - viewBinding.text.setText( - R.string.holder_identityRemoved_banner_title - ) - } - } - - viewBinding.button.setOnClickListener { - onButtonClick.invoke(infoItem) - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_dashboard_info_card - } - - override fun initializeViewBinding(view: View): AdapterItemDashboardInfoCardBinding { - return AdapterItemDashboardInfoCardBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardInfoCardAdapterItemUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardInfoCardAdapterItemUtil.kt deleted file mode 100644 index 9c4d82bbd..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/items/DashboardInfoCardAdapterItemUtil.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.items - -import android.content.Context -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -interface DashboardInfoCardAdapterItemUtil { - fun getOriginInfoText( - infoItem: DashboardItem.InfoItem.OriginInfoItem, - context: Context - ): String - - @StringRes - fun getExpiredItemText( - greenCardType: GreenCardType, - originType: OriginType - ): Int -} - -class DashboardInfoCardAdapterItemUtilImpl : DashboardInfoCardAdapterItemUtil { - override fun getOriginInfoText( - infoItem: DashboardItem.InfoItem.OriginInfoItem, - context: Context - ): String { - val originString = when (infoItem.originType) { - is OriginType.Vaccination -> context.getString(R.string.type_vaccination) - is OriginType.Recovery -> context.getString(R.string.type_recovery) - is OriginType.Test -> context.getString(R.string.type_test) - } - - return when (infoItem.greenCardType) { - is GreenCardType.Eu -> { - when (infoItem.originType) { - else -> { - context.getString( - R.string.my_overview_not_valid_eu_but_is_in_domestic, originString - ) - } - } - } - } - } - - override fun getExpiredItemText( - greenCardType: GreenCardType, - originType: OriginType - ): Int { - return when { - greenCardType == GreenCardType.Eu && originType == OriginType.Vaccination -> R.string.holder_dashboard_originExpiredBanner_internationalVaccine_title - greenCardType == GreenCardType.Eu && originType == OriginType.Recovery -> R.string.holder_dashboard_originExpiredBanner_internationalRecovery_title - greenCardType == GreenCardType.Eu && originType == OriginType.Test -> R.string.holder_dashboard_originExpiredBanner_internationalTest_title - else -> R.string.qr_card_expired - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardItem.kt deleted file mode 100644 index 3700e9e65..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardItem.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.models - -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.items.ButtonInfo -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginState -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard - -sealed class DashboardItem { - - data class HeaderItem( - @StringRes val text: Int, - val buttonInfo: ButtonInfo? - ) : DashboardItem() - - data class PlaceholderCardItem(val greenCardType: GreenCardType) : DashboardItem() - - sealed class InfoItem( - val isDismissible: Boolean, - val hasButton: Boolean, - @StringRes open val buttonText: Int? = null - ) : DashboardItem() { - - data class ConfigFreshnessWarning(val maxValidityDate: Long) : - InfoItem(isDismissible = false, hasButton = true) - - data class OriginInfoItem( - val greenCardType: GreenCardType, - val originType: OriginType - ) : InfoItem(isDismissible = false, hasButton = true) - - object ClockDeviationItem : InfoItem(isDismissible = false, hasButton = true) - - data class GreenCardExpiredItem( - val greenCardType: GreenCardType, - val originEntity: OriginEntity - ) : InfoItem( - isDismissible = true, - hasButton = false - ) - - object AppUpdate : InfoItem( - isDismissible = false, - hasButton = true, - buttonText = R.string.recommended_update_card_action - ) - - data class BlockedEvents( - val blockedEvents: List, - @StringRes override val buttonText: Int = R.string.general_readmore - ) : InfoItem(isDismissible = true, hasButton = true) - - data class FuzzyMatchedEvents( - val storedEvent: EventGroupEntity, - val events: List, - @StringRes override val buttonText: Int = R.string.general_readmore - ) : InfoItem(isDismissible = true, hasButton = true) - - data class ExportPdf( - @StringRes override val buttonText: Int = R.string.holder_pdfExport_card_action - ) : InfoItem(isDismissible = false, hasButton = true) - } - - data class CardsItem(val cards: List) : DashboardItem() { - - sealed class CredentialState { - data class HasCredential(val credential: CredentialEntity) : CredentialState() - object LoadingCredential : CredentialState() - object NoCredential : CredentialState() - } - - data class CardItem( - val greenCard: GreenCard, - val originStates: List, - val credentialState: CredentialState, - val databaseSyncerResult: DatabaseSyncerResult, - val greenCardEnabledState: GreenCardEnabledState - ) - } - - object AddQrButtonItem : DashboardItem() - - object AddQrCardItem : DashboardItem() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardItems.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardItems.kt deleted file mode 100644 index cd85f93a0..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardItems.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.models - -data class DashboardItems( - val domesticItems: List, - val internationalItems: List -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardSyncState.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardSyncState.kt deleted file mode 100644 index 4bffba9cf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardSyncState.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.models - -sealed class DashboardSync { - object ForceSync : DashboardSync() - object DisableSync : DashboardSync() - object CheckSync : DashboardSync() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardTabItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardTabItem.kt deleted file mode 100644 index 20d0f1571..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/DashboardTabItem.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.models - -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -data class DashboardTabItem( - @StringRes val title: Int, - val greenCardType: GreenCardType, - val items: List -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/GreenCardEnabledState.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/GreenCardEnabledState.kt deleted file mode 100644 index 3aa2a3e35..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/models/GreenCardEnabledState.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.models - -import nl.rijksoverheid.ctr.holder.R - -sealed class GreenCardEnabledState { - object Enabled : GreenCardEnabledState() - data class Disabled(val text: Int = R.string.holder_dashboard_domesticQRCard_3G_inactive_label) : GreenCardEnabledState() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/GetDashboardItemsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/GetDashboardItemsUseCase.kt deleted file mode 100644 index 7d01ab2ff..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/GetDashboardItemsUseCase.kt +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.usecases - -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardHeaderAdapterItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItems -import nl.rijksoverheid.ctr.holder.dashboard.util.CardItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.CredentialUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardItemEmptyStateUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginState -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginUtil -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard - -interface GetDashboardItemsUseCase { - suspend fun getItems( - allEventGroupEntities: List, - allGreenCards: List, - databaseSyncerResult: DatabaseSyncerResult = DatabaseSyncerResult.Success(), - isLoadingNewCredentials: Boolean - ): DashboardItems -} - -class GetDashboardItemsUseCaseImpl( - private val greenCardUtil: GreenCardUtil, - private val credentialUtil: CredentialUtil, - private val originUtil: OriginUtil, - private val dashboardItemUtil: DashboardItemUtil, - private val dashboardItemEmptyStateUtil: DashboardItemEmptyStateUtil, - private val dashboardHeaderAdapterItemUtil: DashboardHeaderAdapterItemUtil, - private val cardItemUtil: CardItemUtil, - private val sortGreenCardItemsUseCase: SortGreenCardItemsUseCase, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabase: HolderDatabase -) : GetDashboardItemsUseCase { - override suspend fun getItems( - allEventGroupEntities: List, - allGreenCards: List, - databaseSyncerResult: DatabaseSyncerResult, - isLoadingNewCredentials: Boolean - ): DashboardItems { - return DashboardItems( - domesticItems = emptyList(), - internationalItems = getInternationalItems( - allGreenCards = allGreenCards, - databaseSyncerResult = databaseSyncerResult, - isLoadingNewCredentials = isLoadingNewCredentials, - allEventGroupEntities = allEventGroupEntities - ) - ) - } - - private suspend fun getInternationalItems( - allEventGroupEntities: List, - allGreenCards: List, - databaseSyncerResult: DatabaseSyncerResult, - isLoadingNewCredentials: Boolean - ): List { - val dashboardItems = mutableListOf() - val internationalGreenCards = - allGreenCards.filter { it.greenCardEntity.type == GreenCardType.Eu } - - val hasEmptyState = dashboardItemEmptyStateUtil.hasEmptyState( - allGreenCards = allGreenCards, - greenCardsForTab = internationalGreenCards - ) - - val headerItem = dashboardHeaderAdapterItemUtil.getHeaderItem( - emptyState = hasEmptyState, - greenCardType = GreenCardType.Eu - ) - - dashboardItems.add(headerItem) - - if (dashboardItemUtil.isAppUpdateAvailable()) { - dashboardItems.add(DashboardItem.InfoItem.AppUpdate) - } - - if (dashboardItemUtil.shouldShowBlockedEventsItem()) { - dashboardItems.add( - DashboardItem.InfoItem.BlockedEvents( - blockedEvents = holderDatabase.removedEventDao() - .getAll(reason = RemovedEventReason.Blocked) - ) - ) - } - - if (dashboardItemUtil.shouldShowFuzzyMatchedEventsItem()) { - dashboardItems.add( - DashboardItem.InfoItem.FuzzyMatchedEvents( - storedEvent = holderDatabase.eventGroupDao().getAll().first(), - events = holderDatabase.removedEventDao() - .getAll(reason = RemovedEventReason.FuzzyMatched) - ) - ) - } - - if (dashboardItemUtil.shouldShowClockDeviationItem(hasEmptyState, allGreenCards)) { - dashboardItems.add(DashboardItem.InfoItem.ClockDeviationItem) - } - - if (dashboardItemUtil.shouldShowConfigFreshnessWarning()) { - dashboardItems.add( - DashboardItem.InfoItem.ConfigFreshnessWarning( - maxValidityDate = dashboardItemUtil.getConfigFreshnessMaxValidity() - ) - ) - } - - if (dashboardItemUtil.shouldShowExportPdf()) { - dashboardItems.add( - DashboardItem.InfoItem.ExportPdf() - ) - } - - dashboardItems.addAll( - getGreenCardItems( - greenCards = allGreenCards, - greenCardType = GreenCardType.Eu, - greenCardsForSelectedType = internationalGreenCards, - greenCardsForUnselectedType = emptyList(), - databaseSyncerResult = databaseSyncerResult, - isLoadingNewCredentials = isLoadingNewCredentials, - combineVaccinations = true - ) - ) - - if (dashboardItemUtil.shouldShowPlaceholderItem(hasEmptyState)) { - dashboardItems.add( - DashboardItem.PlaceholderCardItem(greenCardType = GreenCardType.Eu) - ) - } - - val addEventsButtonEnabled = featureFlagUseCase.getAddEventsButtonEnabled() - - if (addEventsButtonEnabled) { - if (dashboardItemUtil.shouldShowAddQrCardItem(false, hasEmptyState)) { - dashboardItems.add(DashboardItem.AddQrCardItem) - } - - if (dashboardItemUtil.shouldAddQrButtonItem(hasEmptyState)) { - dashboardItems.add(DashboardItem.AddQrButtonItem) - } - } - - return sortGreenCardItemsUseCase.sort(dashboardItems) - } - - private fun getGreenCardItems( - greenCards: List, - greenCardType: GreenCardType, - greenCardsForSelectedType: List, - greenCardsForUnselectedType: List, - databaseSyncerResult: DatabaseSyncerResult, - isLoadingNewCredentials: Boolean, - combineVaccinations: Boolean - ): List { - - // Loop through all green cards that exists in the database and map them to UI models - val items = greenCardsForSelectedType - .mapIndexed { index, greenCard -> - if (!featureFlagUseCase.isInArchiveMode() && greenCardUtil.isExpired(greenCard) && greenCard.origins.isNotEmpty()) { - getExpiredBannerItem( - greenCard = greenCard - ) - } else { - mapGreenCardsItem( - greenCard = greenCard, - greenCardIndex = index, - isLoadingNewCredentials = isLoadingNewCredentials, - databaseSyncerResult = databaseSyncerResult - ) - } - } - .let { if (combineVaccinations) dashboardItemUtil.combineEuVaccinationItems(it) else it } - .toMutableList() - - // If we have valid origins that exists in the other selected type but not in the current one, we show a banner - val allOriginsForSelectedType = greenCardsForSelectedType.map { it.origins }.flatten() - val allOriginsForUnselectedType = greenCardsForUnselectedType.map { it.origins }.flatten() - val allValidOriginsForSelectedType = originUtil.getOriginState(allOriginsForSelectedType) - .filter { it is OriginState.Valid || it is OriginState.Future }.map { it.origin } - val allValidOriginsForUnselectedType = - originUtil.getOriginState(allOriginsForUnselectedType) - .filter { it is OriginState.Valid || it is OriginState.Future }.map { it.origin } - - allValidOriginsForUnselectedType.forEach { originForUnselectedType -> - if (!allValidOriginsForSelectedType.map { it.type } - .contains(originForUnselectedType.type)) { - - if (dashboardItemUtil.shouldShowOriginInfoItem( - greenCards = greenCards, - greenCardType = greenCardType, - originType = originForUnselectedType.type - ) - ) { - items.add( - DashboardItem.InfoItem.OriginInfoItem( - greenCardType = greenCardType, - originType = originForUnselectedType.type - ) - ) - } - } - } - - return items - } - - private fun getExpiredBannerItem( - greenCard: GreenCard - ): DashboardItem { - val origin = greenCard.origins.last() - return DashboardItem.InfoItem.GreenCardExpiredItem( - greenCardType = greenCard.greenCardEntity.type, - originEntity = origin - ) - } - - private fun mapGreenCardsItem( - greenCard: GreenCard, - greenCardIndex: Int, - isLoadingNewCredentials: Boolean, - databaseSyncerResult: DatabaseSyncerResult - ): DashboardItem.CardsItem { - // Check if we have a credential - val activeCredential = credentialUtil.getActiveCredential( - greenCardType = greenCard.greenCardEntity.type, - entities = greenCard.credentialEntities - ) - - // Check the states of our origins - val originStates = originUtil.getOriginState( - origins = greenCard.origins - ).sortedBy { it.origin.type.order } - - // Check if we have any valid origins - val hasValidOriginStates = originStates.any { it is OriginState.Valid } - val nonExpiredOriginStates = originStates.filterNot { it is OriginState.Expired } - - // More our credential to a more readable state - val credentialState = when { - isLoadingNewCredentials -> DashboardItem.CardsItem.CredentialState.LoadingCredential - activeCredential == null -> DashboardItem.CardsItem.CredentialState.NoCredential - !hasValidOriginStates && !featureFlagUseCase.isInArchiveMode() -> DashboardItem.CardsItem.CredentialState.NoCredential - else -> DashboardItem.CardsItem.CredentialState.HasCredential(activeCredential) - } - - val greenCardItem = DashboardItem.CardsItem.CardItem( - greenCard = greenCard, - originStates = if (featureFlagUseCase.isInArchiveMode()) { - originStates - } else { - nonExpiredOriginStates - }, - credentialState = credentialState, - databaseSyncerResult = databaseSyncerResult, - greenCardEnabledState = cardItemUtil.getEnabledState( - greenCard = greenCard - ) - ) - - return DashboardItem.CardsItem(listOf(greenCardItem)) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/RemoveExpiredGreenCardsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/RemoveExpiredGreenCardsUseCase.kt deleted file mode 100644 index 84ef5efa3..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/RemoveExpiredGreenCardsUseCase.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.dashboard.usecases - -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard - -/** - * Remove expired green cards (= no more origins). We do this every time before fetching green cards. - * The reason for this is that we transform the database model in [SplitDomesticGreenCardsUseCase]. - * If there is one green card with three origins (vaccination, recovery, test), there are cases we split it - * into two green card (green card 1: vaccination, recovery and green card 2: test) in [SplitDomesticGreenCardsUseCase] - * In case the origin of the second "fake" green card expires, we don't want to remove the green card but only the origin. - */ -interface RemoveExpiredGreenCardsUseCase { - suspend fun execute(allGreenCards: List) -} - -class RemoveExpiredGreenCardsUseCaseImpl( - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabase: HolderDatabase -) : RemoveExpiredGreenCardsUseCase { - - override suspend fun execute(allGreenCards: List) { - if (featureFlagUseCase.isInArchiveMode()) { - return - } - - val greenCardsToRemove = allGreenCards - .filter { - it.origins.isEmpty() - } - - greenCardsToRemove.forEach { - holderDatabase.greenCardDao().delete(it.greenCardEntity) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/ShowBlockedEventsDialogUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/ShowBlockedEventsDialogUseCase.kt deleted file mode 100644 index c535417a9..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/ShowBlockedEventsDialogUseCase.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.usecases - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason - -interface ShowBlockedEventsDialogUseCase { - suspend fun execute(blockedRemoteEvents: List): ShowBlockedEventsDialogResult -} - -class ShowBlockedEventsDialogUseCaseImpl( - private val holderDatabase: HolderDatabase -) : ShowBlockedEventsDialogUseCase { - - override suspend fun execute(blockedRemoteEvents: List): ShowBlockedEventsDialogResult { - return if (blockedRemoteEvents.isEmpty()) { - ShowBlockedEventsDialogResult.None - } else { - ShowBlockedEventsDialogResult.Show( - blockedEvents = holderDatabase.removedEventDao().getAll(reason = RemovedEventReason.Blocked) - ) - } - } -} - -sealed class ShowBlockedEventsDialogResult { - data class Show(val blockedEvents: List) : ShowBlockedEventsDialogResult() - object None : ShowBlockedEventsDialogResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/SortGreenCardItemsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/SortGreenCardItemsUseCase.kt deleted file mode 100644 index 7a5740bfe..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/usecases/SortGreenCardItemsUseCase.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.usecases - -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase - -interface SortGreenCardItemsUseCase { - fun sort(items: List): List -} - -class SortGreenCardItemsUseCaseImpl( - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val greenCardUtil: GreenCardUtil -) : SortGreenCardItemsUseCase { - - override fun sort(items: List): List { - if (items.size < 2) { - return items - } - return items.sortedBy { - when (it) { - is DashboardItem.HeaderItem -> 10 - is DashboardItem.InfoItem.ExportPdf -> 15 - DashboardItem.InfoItem.ClockDeviationItem -> 20 - is DashboardItem.InfoItem.ConfigFreshnessWarning -> 30 - is DashboardItem.InfoItem.BlockedEvents -> 40 - is DashboardItem.InfoItem.FuzzyMatchedEvents -> 45 - DashboardItem.InfoItem.AppUpdate -> 50 - is DashboardItem.InfoItem.GreenCardExpiredItem -> 70 - is DashboardItem.InfoItem.OriginInfoItem -> 120 - is DashboardItem.PlaceholderCardItem -> 130 - is DashboardItem.CardsItem -> { - val cardsItemOrder = 140 - cardsItemOrder + (it.cards.firstOrNull()?.originStates?.firstOrNull()?.origin?.type?.order ?: 1) - } - DashboardItem.AddQrButtonItem -> 150 - DashboardItem.AddQrCardItem -> 160 - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/CardItemUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/CardItemUtil.kt deleted file mode 100644 index 599e7cb24..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/CardItemUtil.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.dashboard.models.GreenCardEnabledState -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeFragmentData -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard - -interface CardItemUtil { - - fun getEnabledState( - greenCard: GreenCard - ): GreenCardEnabledState - - fun shouldDisclose( - cardItem: DashboardItem.CardsItem.CardItem - ): QrCodeFragmentData.ShouldDisclose - - @StringRes - fun getQrCodesFragmentToolbarTitle(cardItem: DashboardItem.CardsItem.CardItem): Int -} - -class CardItemUtilImpl : CardItemUtil { - - override fun getEnabledState( - greenCard: GreenCard - ): GreenCardEnabledState { - return when (greenCard.greenCardEntity.type) { - is GreenCardType.Eu -> { - GreenCardEnabledState.Enabled - } - } - } - - override fun shouldDisclose(cardItem: DashboardItem.CardsItem.CardItem): QrCodeFragmentData.ShouldDisclose { - return when (cardItem.greenCard.greenCardEntity.type) { - is GreenCardType.Eu -> { - QrCodeFragmentData.ShouldDisclose.DoNotDisclose - } - } - } - - override fun getQrCodesFragmentToolbarTitle(cardItem: DashboardItem.CardsItem.CardItem): Int { - return R.string.domestic_qr_code_title - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/CredentialUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/CredentialUtil.kt deleted file mode 100644 index dccc28b1e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/CredentialUtil.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import java.time.Clock -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset -import nl.rijksoverheid.ctr.holder.utils.CountryUtil -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.ext.getStringOrNull -import org.json.JSONArray -import org.json.JSONObject - -interface CredentialUtil { - fun getActiveCredential(greenCardType: GreenCardType, entities: List, ignoreExpiredEuCredentials: Boolean = true): CredentialEntity? - fun isExpiring(credentialRenewalDays: Long, credential: CredentialEntity): Boolean - fun getTestTypeForEuropeanCredentials(entities: List): String - fun getVaccinationDosesCountryLineForEuropeanCredentials( - entities: List, - deviceLanguage: String, - getString: (String, String, String) -> String - ): String - - fun vaccinationShouldBeHidden( - readEuropeanCredentials: List, - indexOfVaccination: Int - ): Boolean - - fun europeanCredentialHasExpired(credentialExpirationTimeSeconds: Long): Boolean -} - -class CredentialUtilImpl( - private val clock: Clock, - private val mobileCoreWrapper: MobileCoreWrapper, - private val appConfigUseCase: HolderCachedAppConfigUseCase, - private val countryUtil: CountryUtil, - cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : CredentialUtil { - - private val holderConfig = cachedAppConfigUseCase.getCachedAppConfig() - - override fun getActiveCredential(greenCardType: GreenCardType, entities: List, ignoreExpiredEuCredentials: Boolean): CredentialEntity? { - - val credentialsInWindow = entities.filter { - when (greenCardType) { - is GreenCardType.Eu -> { - if (ignoreExpiredEuCredentials) { - // accept expired credentials for dcc - true - } else { - // don't accept expired credentials for dcc - it.validFrom.isBefore( - OffsetDateTime.now(clock) - ) && it.expirationTime.isAfter( - OffsetDateTime.now( - clock - ) - ) - } - } - } - } - - // Return the credential with the longest expiration time if it exists - return credentialsInWindow.maxByOrNull { - it.expirationTime.toEpochSecond() - OffsetDateTime.now(clock) - .toEpochSecond() - } - } - - override fun isExpiring(credentialRenewalDays: Long, credential: CredentialEntity): Boolean { - val now = OffsetDateTime.now(clock) - return credential.expirationTime.minusDays(credentialRenewalDays).isBefore(now) - } - - override fun getTestTypeForEuropeanCredentials(entities: List): String { - val data = mobileCoreWrapper.readEuropeanCredential(entities.first().data) - - return try { - val type = ((((data["dcc"] as JSONObject)["t"] as JSONArray)[0]) as JSONObject)["tt"] as String - holderConfig.euTestTypes.firstOrNull { - it.code == type - }?.name ?: type - } catch (exception: Exception) { - exception.printStackTrace() - "" - } - } - - override fun getVaccinationDosesCountryLineForEuropeanCredentials( - entities: List, - deviceLanguage: String, - getString: (String, String, String) -> String - ): String { - return try { - val data = mobileCoreWrapper.readEuropeanCredential(entities.first().data) - val vaccinationData = (((data["dcc"] as JSONObject)["v"] as JSONArray)[0]) as JSONObject - val dn = vaccinationData["dn"] as Int - val sd = vaccinationData["sd"] as Int - val countryCode = vaccinationData["co"] as String - val countryString = if (countryCode != "NL") { - " (${countryUtil.getCountryForInfoScreen(deviceLanguage, countryCode)})" - } else { - "" - } - getString("$dn", "$sd", countryString) - } catch (exception: Exception) { - exception.printStackTrace() - "" - } - } - - override fun vaccinationShouldBeHidden( - readEuropeanCredentials: List, - indexOfVaccination: Int - ): Boolean { - if (readEuropeanCredentials.size == 1) { - return false - } - val vaccinations = readEuropeanCredentials.map { - it.optJSONObject("dcc")?.getJSONArray("v")?.optJSONObject(0) - } - val (dose, totalDoses) = getDoses(vaccinations[indexOfVaccination]) - return getDateWhenRelevant(vaccinations[indexOfVaccination])?.let { - it < OffsetDateTime.now(clock) && - dose < totalDoses && - !hasCompletedButNotRelevantVaccination(vaccinations, dose) - } ?: false - } - - override fun europeanCredentialHasExpired(credentialExpirationTimeSeconds: Long): Boolean { - return credentialExpirationTimeSeconds < OffsetDateTime.now(clock).toEpochSecond() - } - - private fun hasCompletedButNotRelevantVaccination( - vaccinations: List, - doseOfHiddenVaccination: Int - ): Boolean { - return vaccinations.any { vaccination -> - val (dose, totalDoses) = getDoses(vaccination) - getDateWhenRelevant(vaccination)?.let { - it > OffsetDateTime.now(clock) && - dose == totalDoses && - dose == doseOfHiddenVaccination + 1 - } ?: false - } - } - - private fun getDoses(vaccination: JSONObject?): Pair { - val dose = vaccination?.getStringOrNull("dn") ?: "" - val totalDoses = vaccination?.getStringOrNull("sd") ?: "" - return Pair(dose.toInt(), totalDoses.toInt()) - } - - private fun getDateWhenRelevant(vaccination: JSONObject?): OffsetDateTime? { - val date = LocalDate.parse(vaccination?.getStringOrNull("dt")) - ?.atStartOfDay() - ?.atOffset(ZoneOffset.UTC) - val relevancyDays = - appConfigUseCase.getCachedAppConfig().internationalQRRelevancyDays.toLong() - return date?.plusDays(relevancyDays) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardItemEmptyStateUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardItemEmptyStateUtil.kt deleted file mode 100644 index 4d929a22f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardItemEmptyStateUtil.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard - -interface DashboardItemEmptyStateUtil { - fun hasEmptyState( - allGreenCards: List, - greenCardsForTab: List - ): Boolean -} - -class DashboardItemEmptyStateUtilImpl : - DashboardItemEmptyStateUtil { - - override fun hasEmptyState( - allGreenCards: List, - greenCardsForTab: List - ): Boolean { - - return greenCardsForTab.isEmpty() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardItemUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardItemUtil.kt deleted file mode 100644 index 54e177d3f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardItemUtil.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigFreshnessUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ClockDeviationUseCase -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard -import nl.rijksoverheid.ctr.shared.BuildConfigUseCase - -interface DashboardItemUtil { - fun shouldShowClockDeviationItem(emptyState: Boolean, allGreenCards: List): Boolean - fun shouldShowPlaceholderItem(emptyState: Boolean): Boolean - fun shouldAddQrButtonItem(emptyState: Boolean): Boolean - fun isAppUpdateAvailable(): Boolean - suspend fun shouldShowBlockedEventsItem(): Boolean - suspend fun shouldShowFuzzyMatchedEventsItem(): Boolean - - /** - * Multiple EU vaccination green card items will be combined into 1. - * - * @param[items] Items list containing possible multiple vaccination items to combine. - * @return Items list with vaccination green card items combined into 1. - */ - fun combineEuVaccinationItems(items: List): List - - fun shouldShowConfigFreshnessWarning(): Boolean - fun getConfigFreshnessMaxValidity(): Long - - fun shouldShowOriginInfoItem( - greenCards: List, - greenCardType: GreenCardType, - originType: OriginType - ): Boolean - - fun shouldShowAddQrCardItem( - hasVisitorPassIncompleteItem: Boolean, - emptyState: Boolean - ): Boolean - - fun shouldShowExportPdf(): Boolean -} - -class DashboardItemUtilImpl( - private val clockDeviationUseCase: ClockDeviationUseCase, - private val appConfigFreshnessUseCase: AppConfigFreshnessUseCase, - private val appConfigUseCase: HolderCachedAppConfigUseCase, - private val buildConfigUseCase: BuildConfigUseCase, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabase: HolderDatabase -) : DashboardItemUtil { - - override fun shouldShowClockDeviationItem(emptyState: Boolean, allGreenCards: List) = - clockDeviationUseCase.hasDeviation() && (!emptyState) - - override fun shouldShowPlaceholderItem( - emptyState: Boolean - ) = emptyState - - override fun shouldAddQrButtonItem(emptyState: Boolean): Boolean = emptyState - - override fun isAppUpdateAvailable(): Boolean { - return buildConfigUseCase.getVersionCode() < appConfigUseCase.getCachedAppConfig().recommendedVersion - } - - override suspend fun shouldShowBlockedEventsItem(): Boolean { - return holderDatabase.removedEventDao().getAll(reason = RemovedEventReason.Blocked) - .isNotEmpty() - } - - override suspend fun shouldShowFuzzyMatchedEventsItem(): Boolean { - val storedEvents = holderDatabase.eventGroupDao().getAll() - // if user has removed his events from the menu, there is no point on showing the banner - if (storedEvents.isEmpty()) { - holderDatabase.removedEventDao().deleteAll(RemovedEventReason.FuzzyMatched) - return false - } - return holderDatabase.removedEventDao().getAll(reason = RemovedEventReason.FuzzyMatched) - .isNotEmpty() - } - - override fun combineEuVaccinationItems(items: List): List { - return items - .groupBy { it::class } - .map { itemTypeToItem -> - if (itemTypeToItem.value.first() !is DashboardItem.CardsItem) { - itemTypeToItem.value - } else { - itemTypeToItem.value - .groupBy { (it as DashboardItem.CardsItem).cards.first().greenCard.origins.first().type } - .map { - if (it.key == OriginType.Vaccination) { - listOf( - DashboardItem.CardsItem(it.value.map { greenCardsItem -> - (greenCardsItem as DashboardItem.CardsItem).cards - }.flatten()) - ) - } else it.value - }.flatten() - } - }.flatten() - } - - override fun shouldShowConfigFreshnessWarning(): Boolean { - // return true if config is older than 10 days && less than 28 days - return appConfigFreshnessUseCase.shouldShowConfigFreshnessWarning() - } - - override fun getConfigFreshnessMaxValidity(): Long { - return appConfigFreshnessUseCase.getAppConfigMaxValidityTimestamp() - } - - override fun shouldShowOriginInfoItem( - greenCards: List, - greenCardType: GreenCardType, - originType: OriginType - ): Boolean { - return false - } - - override fun shouldShowAddQrCardItem( - hasVisitorPassIncompleteItem: Boolean, - emptyState: Boolean - ) = !emptyState && !hasVisitorPassIncompleteItem - - override fun shouldShowExportPdf(): Boolean { - return featureFlagUseCase.isInArchiveMode() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardPageInfoItemHandlerUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardPageInfoItemHandlerUtil.kt deleted file mode 100644 index 0b03dcee4..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/DashboardPageInfoItemHandlerUtil.kt +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import android.content.Intent -import android.provider.Settings -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset -import nl.rijksoverheid.ctr.design.ext.formatDayMonthTime -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.design.utils.IntentUtil -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.DashboardFragmentDirections -import nl.rijksoverheid.ctr.holder.dashboard.DashboardPageFragment -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardInfoCardAdapterItem -import nl.rijksoverheid.ctr.holder.dashboard.models.DashboardItem -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.shared.ext.navigateSafety - -/** - * Handles [DashboardInfoCardAdapterItem] actions - */ -interface DashboardPageInfoItemHandlerUtil { - fun handleButtonClick( - dashboardPageFragment: DashboardPageFragment, - infoItem: DashboardItem.InfoItem - ) - - fun handleDismiss( - dashboardPageFragment: DashboardPageFragment, - infoCardAdapterItem: DashboardInfoCardAdapterItem, - infoItem: DashboardItem.InfoItem - ) -} - -class DashboardPageInfoItemHandlerUtilImpl( - private val infoFragmentUtil: InfoFragmentUtil, - private val intentUtil: IntentUtil, - private val removedEventsBottomSheetUtil: RemovedEventsBottomSheetUtil -) : DashboardPageInfoItemHandlerUtil { - - /** - * Handles the button click in the info card - */ - override fun handleButtonClick( - dashboardPageFragment: DashboardPageFragment, - infoItem: DashboardItem.InfoItem - ) { - when (infoItem) { - is DashboardItem.InfoItem.ConfigFreshnessWarning -> - onConfigRefreshClicked(dashboardPageFragment, infoItem) - is DashboardItem.InfoItem.ClockDeviationItem -> - onClockDeviationClicked(dashboardPageFragment) - is DashboardItem.InfoItem.OriginInfoItem -> - onOriginInfoClicked(dashboardPageFragment, infoItem) - is DashboardItem.InfoItem.AppUpdate -> openPlayStore(dashboardPageFragment) - is DashboardItem.InfoItem.BlockedEvents -> onBlockedEventsClick(dashboardPageFragment, infoItem.blockedEvents) - is DashboardItem.InfoItem.FuzzyMatchedEvents -> onFuzzyMatchedEventsClick(dashboardPageFragment, infoItem.storedEvent, infoItem.events) - is DashboardItem.InfoItem.GreenCardExpiredItem -> { - /* nothing, DashboardPageFragment.setItems never creates a card for this */ - } - is DashboardItem.InfoItem.ExportPdf -> { - dashboardPageFragment.navigateSafety(DashboardFragmentDirections.actionExportIntroduction()) - } - } - } - - private fun openPlayStore(dashboardPageFragment: DashboardPageFragment) { - intentUtil.openPlayStore(dashboardPageFragment.requireContext()) - } - - private fun onConfigRefreshClicked( - dashboardPageFragment: DashboardPageFragment, - infoItem: DashboardItem.InfoItem.ConfigFreshnessWarning - ) { - infoFragmentUtil.presentAsBottomSheet( - dashboardPageFragment.parentFragmentManager, - InfoFragmentData.TitleDescription( - title = dashboardPageFragment.getString(R.string.config_warning_page_title), - descriptionData = DescriptionData( - htmlTextString = dashboardPageFragment.getString( - R.string.config_warning_page_message, - OffsetDateTime.ofInstant( - Instant.ofEpochSecond(infoItem.maxValidityDate), - ZoneOffset.UTC - ).formatDayMonthTime(dashboardPageFragment.requireContext()) - ), - htmlLinksEnabled = true - ) - ) - ) - } - - private fun onClockDeviationClicked( - dashboardPageFragment: DashboardPageFragment - ) { - infoFragmentUtil.presentAsBottomSheet( - dashboardPageFragment.parentFragmentManager, InfoFragmentData.TitleDescription( - title = dashboardPageFragment.getString(R.string.clock_deviation_explanation_title), - descriptionData = DescriptionData( - R.string.clock_deviation_explanation_description, - customLinkIntent = Intent(Settings.ACTION_DATE_SETTINGS) - ) - ) - ) - } - - private fun onBlockedEventsClick(dashboardPageFragment: DashboardPageFragment, blockedEvents: List) { - removedEventsBottomSheetUtil.presentBlockedEvents(dashboardPageFragment, blockedEvents) - } - - private fun onFuzzyMatchedEventsClick(dashboardPageFragment: DashboardPageFragment, storedEvent: EventGroupEntity, events: List) { - removedEventsBottomSheetUtil.presentRemovedEvents(dashboardPageFragment, storedEvent, events) - } - - private fun onOriginInfoClicked( - dashboardPageFragment: DashboardPageFragment, - item: DashboardItem.InfoItem.OriginInfoItem - ) { - when (item.greenCardType) { - is GreenCardType.Eu -> presentOriginInfoForEuQr( - item.originType, dashboardPageFragment - ) - } - } - - private fun presentOriginInfoForEuQr( - originType: OriginType, - dashboardPageFragment: DashboardPageFragment - ) { - infoFragmentUtil.presentAsBottomSheet( - dashboardPageFragment.parentFragmentManager, - data = when (originType) { - is OriginType.Test -> { - InfoFragmentData.TitleDescription( - title = dashboardPageFragment.getString(R.string.my_overview_green_card_not_valid_title_test), - descriptionData = DescriptionData(R.string.my_overview_green_card_not_valid_eu_but_is_in_domestic_bottom_sheet_description_test) - ) - } - is OriginType.Vaccination -> { - InfoFragmentData.TitleDescription( - title = dashboardPageFragment.getString(R.string.my_overview_green_card_not_valid_title_vaccination), - descriptionData = DescriptionData(R.string.my_overview_green_card_not_valid_eu_but_is_in_domestic_bottom_sheet_description_vaccination) - ) - } - is OriginType.Recovery -> { - InfoFragmentData.TitleDescription( - title = dashboardPageFragment.getString(R.string.my_overview_green_card_not_valid_title_recovery), - descriptionData = DescriptionData( - htmlText = R.string.my_overview_green_card_not_valid_eu_but_is_in_domestic_bottom_sheet_description_recovery, - htmlLinksEnabled = true - ) - ) - } - } - ) - } - - /** - * Handles the dismiss button click in the info card - */ - override fun handleDismiss( - dashboardPageFragment: DashboardPageFragment, - infoCardAdapterItem: DashboardInfoCardAdapterItem, - infoItem: DashboardItem.InfoItem - ) { - // Remove section from adapter - dashboardPageFragment.section.remove(infoCardAdapterItem) - - // Clear preference so it doesn't show again - when (infoItem) { - is DashboardItem.InfoItem.GreenCardExpiredItem -> { - dashboardPageFragment.dashboardViewModel.removeOrigin(infoItem.originEntity) - } - is DashboardItem.InfoItem.BlockedEvents -> { - dashboardPageFragment.dashboardViewModel.dismissBlockedEventsInfo() - } - is DashboardItem.InfoItem.FuzzyMatchedEvents -> { - dashboardPageFragment.dashboardViewModel.dismissFuzzyMatchedEventsInfo() - } - else -> { - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/GreenCardRefreshUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/GreenCardRefreshUtil.kt deleted file mode 100644 index 5996e18e7..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/GreenCardRefreshUtil.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import java.time.Clock -import java.time.OffsetDateTime -import java.time.temporal.ChronoUnit.DAYS -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase - -sealed class RefreshState { - class Refreshable(val days: Long) : RefreshState() - object NoRefresh : RefreshState() -} - -interface GreenCardRefreshUtil { - suspend fun shouldRefresh(): Boolean - suspend fun refreshState(): RefreshState -} - -class GreenCardRefreshUtilImpl( - private val holderDatabase: HolderDatabase, - cachedAppConfigUseCase: HolderCachedAppConfigUseCase, - private val greenCardUtil: GreenCardUtil, - private val clock: Clock, - private val credentialUtil: CredentialUtil, - private val originUtil: OriginUtil -) : GreenCardRefreshUtil { - - private val holderConfig = cachedAppConfigUseCase.getCachedAppConfig() - - override suspend fun shouldRefresh(): Boolean { - val credentialRenewalDays = holderConfig.credentialRenewalDays.toLong() - - // Foreign dccs and dutch paper based dccs should not be refreshed - // so exclude them from the refresh logic - val eventFromDccHints = holderDatabase.originHintDao().get("event_from_dcc") - val greenCardsToRefresh = holderDatabase.greenCardDao().getAll() - .filter { !greenCardUtil.isEventFromDcc(it, eventFromDccHints) } - - val greenCardExpiring = greenCardsToRefresh.firstOrNull { greenCard -> - val hasNewCredentials = !greenCardUtil.getExpireDate(greenCard).isEqual( - greenCard.credentialEntities.lastOrNull()?.expirationTime - ?: OffsetDateTime.now(clock) - ) - val latestCredential = greenCard.credentialEntities.maxByOrNull { it.expirationTime } - val latestCredentialExpiring = latestCredential?.let { - credentialUtil.isExpiring(credentialRenewalDays, latestCredential) - } ?: false - hasNewCredentials && latestCredentialExpiring - } - - // It can be that a green card has no credentials but they will be available in the future. - // A refresh should be done in the case there are valid origins within the threshold. - val hasValidFutureOrigins = greenCardsToRefresh - .filter { it.credentialEntities.isEmpty() } - .any { greenCard -> - greenCard.origins.any { - originUtil.isValidWithinRenewalThreshold(credentialRenewalDays, it) - } - } - - return greenCardExpiring != null || hasValidFutureOrigins - } - - // returns the refresh state of the app, - // if it should schedule a refresh or not - // and if so, in how many days from now - override suspend fun refreshState(): RefreshState { - val credentialRenewalDays = holderConfig.credentialRenewalDays.toLong() - - // Foreign dccs and dutch paper based dccs should not be refreshed - // so exclude them from the refresh logic - val eventFromDccHints = holderDatabase.originHintDao().get("event_from_dcc") - val greenCardsToRefresh = holderDatabase.greenCardDao().getAll() - .filter { !greenCardUtil.isEventFromDcc(it, eventFromDccHints) } - - // find the furthest in the future credentials that - // can be renewed, if any - val latestCredentialExpirationTime: OffsetDateTime? = - greenCardsToRefresh.filter { greenCard -> - !greenCardUtil.getExpireDate(greenCard).isEqual( - greenCard.credentialEntities.lastOrNull()?.expirationTime - ?: OffsetDateTime.now(clock) - ) - }.mapNotNull { greenCard -> - greenCard.credentialEntities.maxByOrNull { it.expirationTime }?.expirationTime - }.maxOrNull() - - // for domestic, we don't get credentials for origins which are not valid yet - // so we take into account to fetch them when they become valid - val firstFutureValidFrom: OffsetDateTime? = greenCardsToRefresh - .filter { it.credentialEntities.isEmpty() } - .flatMap { originUtil.getOriginState(it.origins) } - .filterIsInstance() - .map { it.origin.validFrom } - .minOrNull() - - // either we have a future origin to refresh, or an expiring credentials or both - // in the latter case, use the one closest to now - return when { - firstFutureValidFrom != null && latestCredentialExpirationTime != null -> { - if (firstFutureValidFrom.isBefore(latestCredentialExpirationTime.minusDays(credentialRenewalDays))) { - RefreshState.Refreshable( - daysBetween(firstFutureValidFrom) - ) - } else { - RefreshState.Refreshable( - daysBetween(latestCredentialExpirationTime.minusDays(credentialRenewalDays)) - ) - } - } - firstFutureValidFrom != null -> RefreshState.Refreshable( - daysBetween(firstFutureValidFrom) - ) - latestCredentialExpirationTime != null -> RefreshState.Refreshable( - daysBetween(latestCredentialExpirationTime.minusDays(credentialRenewalDays)) - ) - else -> RefreshState.NoRefresh - } - } - - private fun daysBetween(toDate: OffsetDateTime): Long { - val days = DAYS.between(OffsetDateTime.now(clock), toDate) - return if (days < 1) { - 1 - } else { - days - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/GreenCardUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/GreenCardUtil.kt deleted file mode 100644 index c00bae748..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/GreenCardUtil.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import java.time.Clock -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginHintEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper - -interface GreenCardUtil { - suspend fun getAllGreenCards(): List - - fun hasOrigin(greenCards: List, originType: OriginType): Boolean - - fun isExpired(greenCard: GreenCard): Boolean - - /** - * Get the expiration date of the green card or of a specific origin of the green card - * - * @param[greenCard] green card to check expiration date - * @param[type] Origin to get expiration from or if null latest expiration of all origins - * @return Expiration time of origin type or latest expiration of all origins when not specified - */ - fun getExpireDate(greenCard: GreenCard, type: OriginType? = null): OffsetDateTime - - fun getErrorCorrectionLevel(greenCardType: GreenCardType): ErrorCorrectionLevel - - fun isExpiring(renewalDays: Long, greenCard: GreenCard): Boolean - - fun hasNoActiveCredentials(greenCard: GreenCard, ignoreExpiredEuCredentials: Boolean = true): Boolean - - fun isEventFromDcc(greenCard: GreenCard, hints: List): Boolean -} - -class GreenCardUtilImpl( - private val holderDatabase: HolderDatabase, - private val clock: Clock, - private val credentialUtil: CredentialUtil, - private val mobileCoreWrapper: MobileCoreWrapper -) : GreenCardUtil { - - override fun getExpireDate(greenCard: GreenCard, type: OriginType?): OffsetDateTime { - return greenCard.origins - .filter { if (type == null) true else type == it.type } - .maxByOrNull { it.expirationTime } - ?.expirationTime - ?: OffsetDateTime.now(clock) - } - - override fun getErrorCorrectionLevel(greenCardType: GreenCardType): ErrorCorrectionLevel { - return when (greenCardType) { - is GreenCardType.Eu -> ErrorCorrectionLevel.Q - } - } - - override suspend fun getAllGreenCards(): List { - return holderDatabase - .greenCardDao() - .getAll() - .filter { it.origins.isNotEmpty() } - } - - override fun hasOrigin(greenCards: List, originType: OriginType): Boolean { - return greenCards.map { it.origins }.flatten().map { it.type }.any { it == originType } - } - - override fun isExpired(greenCard: GreenCard): Boolean { - return OffsetDateTime.now(clock) >= getExpireDate(greenCard) - } - - override fun isExpiring(renewalDays: Long, greenCard: GreenCard): Boolean { - val now = OffsetDateTime.now(clock) - val expirationTime = getExpireDate(greenCard) - return expirationTime.minusDays(renewalDays).isBefore(now) - } - - override fun hasNoActiveCredentials(greenCard: GreenCard, ignoreExpiredEuCredentials: Boolean): Boolean { - return credentialUtil.getActiveCredential(greenCard.greenCardEntity.type, greenCard.credentialEntities, ignoreExpiredEuCredentials) == null - } - - override fun isEventFromDcc(greenCard: GreenCard, hints: List): Boolean { - return when (greenCard.greenCardEntity.type) { - is GreenCardType.Eu -> { - val eventFromDccHintOriginIds = hints.map { it.originId } - val greenCardOriginIds = greenCard.origins.map { it.id.toLong() } - greenCardOriginIds.intersect(eventFromDccHintOriginIds.toSet()).isNotEmpty() - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/OriginUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/OriginUtil.kt deleted file mode 100644 index 039580ef8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/OriginUtil.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import java.time.Clock -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity - -interface OriginUtil { - fun getOriginState(origins: List): List - - fun isValidWithinRenewalThreshold(credentialRenewalDays: Long, origin: OriginEntity): Boolean -} - -class OriginUtilImpl(private val clock: Clock) : OriginUtil { - - companion object { - private const val PRESENT_SUBTITLE_WHEN_LESS_THEN_YEARS = 3 - } - - override fun getOriginState(origins: List): List { - return origins.map { origin -> - when { - origin.expirationTime.isBefore(OffsetDateTime.now(clock)) -> { - OriginState.Expired(origin) - } - origin.validFrom.isAfter(OffsetDateTime.now(clock)) -> { - OriginState.Future(origin) - } - else -> { - OriginState.Valid(origin) - } - } - } - } - - override fun isValidWithinRenewalThreshold( - credentialRenewalDays: Long, - origin: OriginEntity - ): Boolean { - val now = OffsetDateTime.now(clock) - val thresholdEndDate = now.plusDays(credentialRenewalDays) - return origin.validFrom < thresholdEndDate && origin.expirationTime > now - } -} - -sealed class OriginState(open val origin: OriginEntity) { - data class Valid(override val origin: OriginEntity) : OriginState(origin) - data class Future(override val origin: OriginEntity) : OriginState(origin) - data class Expired(override val origin: OriginEntity) : OriginState(origin) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/RemovedEventsBottomSheetUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/RemovedEventsBottomSheetUtil.kt deleted file mode 100644 index 9f7850c9f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/dashboard/util/RemovedEventsBottomSheetUtil.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.dashboard.util - -import android.content.Context -import androidx.core.text.HtmlCompat -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.ext.formatDateTime -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.dashboard.DashboardPageFragment -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCase -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventStringUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.shared.factories.ErrorCodeStringFactory -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.BlockedEventException - -interface RemovedEventsBottomSheetUtil { - fun presentBlockedEvents(dashboardPageFragment: DashboardPageFragment, blockedEvents: List) - fun presentRemovedEvents(dashboardPageFragment: DashboardPageFragment, storedEvent: EventGroupEntity, removedEvents: List) -} - -class RemovedEventsBottomSheetUtilImpl( - private val errorCodeStringFactory: ErrorCodeStringFactory, - private val remoteEventStringUtil: RemoteEventStringUtil, - private val getRemoteProtocolFromEventGroupUseCase: GetRemoteProtocolFromEventGroupUseCase, - private val yourEventsFragmentUtil: YourEventsFragmentUtil, - private val infoFragmentUtil: InfoFragmentUtil, - private val cachedAppConfigUseCase: CachedAppConfigUseCase -) : RemovedEventsBottomSheetUtil { - - private fun formattedEvents(context: Context, events: List): String { - val removedEventsHtml = StringBuilder() - events.forEachIndexed { index, blockedEvent -> - val remoteEventClass = RemoteEvent.getRemoteEventClassFromType(blockedEvent.type) - - val title = remoteEventStringUtil.remoteEventTitle(remoteEventClass) - val date = when (remoteEventClass) { - RemoteEventVaccination::class.java -> "${context.getString(R.string.qr_card_vaccination_title_eu)} ${ - blockedEvent.eventTime?.toLocalDate()?.formatDayMonthYear() - }" - RemoteEventNegativeTest::class.java -> "${context.getString(R.string.qr_card_test_title_eu)} ${ - blockedEvent.eventTime?.formatDateTime( - context - ) - }" - RemoteEventPositiveTest::class.java -> "${context.getString(R.string.qr_card_test_title_eu)} ${ - blockedEvent.eventTime?.formatDateTime( - context - ) - }" - RemoteEventRecovery::class.java -> "${context.getString(R.string.qr_card_recovery_title_eu)} ${ - blockedEvent.eventTime?.toLocalDate()?.formatDayMonthYear() - }" - else -> "" - } - - removedEventsHtml.append("$title") - removedEventsHtml.append("
") - removedEventsHtml.append("$date") - - val finalEvent = index == events.size - 1 - if (!finalEvent) { - removedEventsHtml.append("

") - } - } - - return removedEventsHtml.toString() - } - - override fun presentBlockedEvents( - dashboardPageFragment: DashboardPageFragment, - blockedEvents: List - ) { - val context = dashboardPageFragment.requireContext() - val removedEventsHtml = formattedEvents(context, blockedEvents) - - val errorCode = errorCodeStringFactory.get( - HolderFlow.Refresh, listOf( - AppErrorResult( - HolderStep.GetCredentialsNetworkRequest, BlockedEventException() - ) - ) - ) - - val contactInformation = cachedAppConfigUseCase.getCachedAppConfig().contactInfo - infoFragmentUtil.presentAsBottomSheet( - dashboardPageFragment.parentFragmentManager, - InfoFragmentData.TitleDescription( - title = context.getString(R.string.holder_invaliddetailsremoved_moreinfo_title), - descriptionData = DescriptionData( - htmlTextString = context.getString( - R.string.holder_invaliddetailsremoved_moreinfo_body, - removedEventsHtml, - contactInformation.phoneNumber, - contactInformation.phoneNumber, - errorCode - ), - htmlLinksEnabled = true - ) - ) - ) - } - - override fun presentRemovedEvents( - dashboardPageFragment: DashboardPageFragment, - storedEvent: EventGroupEntity, - removedEvents: List - ) { - val context = dashboardPageFragment.requireContext() - val removedEventsHtml = formattedEvents(context, removedEvents) - - val name = yourEventsFragmentUtil.getFullName(getRemoteProtocolFromEventGroupUseCase.get(storedEvent)?.holder) - - infoFragmentUtil.presentAsBottomSheet( - dashboardPageFragment.parentFragmentManager, - InfoFragmentData.TitleDescription( - title = context.getString(R.string.holder_identityRemoved_moreinfo_title), - descriptionData = DescriptionData( - htmlTextString = context.getString( - R.string.holder_identityRemoved_moreinfo_body, - HtmlCompat.fromHtml(name, HtmlCompat.FROM_HTML_MODE_LEGACY), - removedEventsHtml - ), - htmlLinksEnabled = true - ) - ) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingBaseViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingBaseViewModel.kt deleted file mode 100644 index 5f6d78bb6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingBaseViewModel.kt +++ /dev/null @@ -1,40 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class FuzzyMatchingBaseViewModel( - private val holderDatabase: HolderDatabase, - private val greenCardUtil: GreenCardUtil -) : ViewModel() { - val toolbarButtonsStateLiveData: LiveData = MutableLiveData() - fun canSkip(fromGetEvents: Boolean) { - if (fromGetEvents) { - (toolbarButtonsStateLiveData as MutableLiveData).value = ToolbarButtonsState( - canGoBack = true, - canSkip = false - ) - } else { - viewModelScope.launch { - val activeCredentialExists = holderDatabase.greenCardDao().getAll() - .any { !greenCardUtil.hasNoActiveCredentials(it, false) } - (toolbarButtonsStateLiveData as MutableLiveData).value = ToolbarButtonsState( - canGoBack = activeCredentialExists, - canSkip = activeCredentialExists - ) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt deleted file mode 100644 index 5af9e79cb..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt +++ /dev/null @@ -1,52 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val fuzzyMatchingModule = module { - viewModel { - FuzzyMatchingOnboardingViewModel(get(), get()) - } - - viewModel { (matchingBlobIds: List>) -> - HolderNameSelectionViewModelImpl( - get(), - get(), - get(), - get(), - get(), - get(), - matchingBlobIds - ) - } - - factory { - fun getFormattedString(stringResId: Int, template: String): String { - return androidContext().getString(stringResId, template) - } - SelectionDataUtilImpl( - get(), - get(), - get(), - androidContext().resources::getQuantityString, - ::getFormattedString, - androidContext()::getString - ) - } - - factory { - SelectionDetailBottomSheetDescriptionUtilImpl() - } - - factory { - MatchedEventsUseCaseImpl(get(), get()) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingOnboardingFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingOnboardingFragment.kt deleted file mode 100644 index 6fe60d3e8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingOnboardingFragment.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.View -import android.widget.ScrollView -import androidx.activity.OnBackPressedCallback -import androidx.core.content.ContextCompat -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import androidx.viewpager2.widget.ViewPager2 -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.hideNavigationIcon -import nl.rijksoverheid.ctr.holder.showNavigationIcon -import nl.rijksoverheid.ctr.introduction.databinding.FragmentOnboardingBinding -import nl.rijksoverheid.ctr.introduction.onboarding.OnboardingPagerAdapter -import nl.rijksoverheid.ctr.introduction.onboarding.models.OnboardingItem -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import org.koin.androidx.viewmodel.ext.android.viewModel - -class FuzzyMatchingOnboardingFragment : Fragment(R.layout.fragment_onboarding) { - private var _binding: FragmentOnboardingBinding? = null - private val binding get() = _binding!! - - private val fuzzyMatchingOnboardingFragmentArgs: FuzzyMatchingOnboardingFragmentArgs by navArgs() - - private val viewModel: FuzzyMatchingOnboardingViewModel by viewModel() - - private val onboardingItems by lazy { - listOf( - OnboardingItem( - imageResource = R.drawable.ic_holder_fuzzymatching_onboarding_firstpage, - titleResource = R.string.holder_fuzzyMatching_onboarding_firstPage_title, - description = R.string.holder_fuzzyMatching_onboarding_firstPage_body - ), - OnboardingItem( - imageResource = R.drawable.ic_holder_fuzzymatching_onboarding_secondpage, - titleResource = R.string.holder_fuzzyMatching_onboarding_secondPage_title, - description = R.string.holder_fuzzyMatching_onboarding_secondPage_body - ), - OnboardingItem( - imageResource = R.drawable.ic_holder_fuzzymatching_onboarding_thirdpage, - titleResource = R.string.holder_fuzzyMatching_onboarding_thirdPage_title, - description = R.string.holder_fuzzyMatching_onboarding_thirdPage_body - ) - ) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - _binding = FragmentOnboardingBinding.bind(view) - - val adapter = - OnboardingPagerAdapter( - childFragmentManager, - lifecycle, - onboardingItems - ) - - if (onboardingItems.isNotEmpty()) { - binding.indicators.initIndicator(adapter.itemCount) - initViewPager(adapter, savedInstanceState?.getInt(indicatorPositionKey)) - } - - setBackPressListener() - setBindings(adapter) - - viewModel.toolbarButtonsStateLiveData.observe(viewLifecycleOwner) { (canGoBack, _) -> - if (!canGoBack) { - hideNavigationIcon() - } - } - viewModel.canSkip(fuzzyMatchingOnboardingFragmentArgs.getEventsFlow) - } - - private fun setBindings( - adapter: OnboardingPagerAdapter - ) { - binding.button.setOnClickListener { - val currentItem = binding.viewPager.currentItem - if (currentItem == adapter.itemCount - 1) { - navigateSafety( - FuzzyMatchingOnboardingFragmentDirections.actionHolderNameSelection( - matchingBlobIds = fuzzyMatchingOnboardingFragmentArgs.matchingBlobIds, - getEventsFlow = fuzzyMatchingOnboardingFragmentArgs.getEventsFlow - ) - ) - } else { - binding.viewPager.currentItem = currentItem + 1 - } - } - } - - private fun setBackPressListener() { - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : - OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - val currentItem = binding.viewPager.currentItem - if (currentItem == 0) { - findNavControllerSafety()?.popBackStack() - } else { - binding.viewPager.currentItem = binding.viewPager.currentItem - 1 - } - } - }) - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - _binding?.let { - outState.putInt(indicatorPositionKey, it.viewPager.currentItem) - } - } - - private fun initViewPager( - adapter: OnboardingPagerAdapter, - startingItem: Int? = null - ) { - binding.viewPager.offscreenPageLimit = onboardingItems.size - binding.viewPager.adapter = adapter - binding.viewPager.registerOnPageChangeCallback(object : - ViewPager2.OnPageChangeCallback() { - @SuppressLint("StringFormatInvalid") - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - binding.indicators.updateSelected(position) - - binding.indicators.contentDescription = getString( - nl.rijksoverheid.ctr.introduction.R.string.onboarding_page_indicator_label, - (position + 1).toString(), - adapter.itemCount.toString() - ) - val isFirstItem = position == 0 - val isLastItem = position == adapter.itemCount - 1 - binding.button.text = getString( - if (isLastItem) { - R.string.holder_fuzzyMatching_onboarding_thirdPage_action - } else { - R.string.onboarding_next - } - ) - - // Apply bottom elevation if the view inside the viewpager is scrollable - val scrollView = - childFragmentManager.fragments[position]?.view?.findViewById(nl.rijksoverheid.ctr.introduction.R.id.scroll) - if (scrollView?.canScrollVertically(1) == true) { - binding.bottom.cardElevation = - resources.getDimensionPixelSize(nl.rijksoverheid.ctr.introduction.R.dimen.scroll_view_button_elevation) - .toFloat() - } else { - binding.bottom.cardElevation = 0f - } - - if (viewModel.toolbarButtonsStateLiveData.value?.canGoBack == false) { - if (isFirstItem) { - hideNavigationIcon() - } else { - ContextCompat.getDrawable(requireContext(), R.drawable.ic_back)?.let { - it.setTint(ContextCompat.getColor(requireContext(), R.color.black)) - showNavigationIcon(it) - } - } - } - } - }) - startingItem?.let { binding.viewPager.currentItem = it } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - companion object { - private const val indicatorPositionKey = "indicator_position_key" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingOnboardingViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingOnboardingViewModel.kt deleted file mode 100644 index 6468b666c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingOnboardingViewModel.kt +++ /dev/null @@ -1,16 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class FuzzyMatchingOnboardingViewModel( - holderDatabase: HolderDatabase, - greenCardUtil: GreenCardUtil -) : FuzzyMatchingBaseViewModel(holderDatabase, greenCardUtil) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingSyncFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingSyncFragment.kt deleted file mode 100644 index 93737786f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingSyncFragment.kt +++ /dev/null @@ -1,99 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.os.Bundle -import android.view.View -import androidx.core.text.HtmlCompat -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentDirections -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.BaseFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.hideNavigationIcon -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.sync_greencards.SyncGreenCardsViewModel -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.models.Flow -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class FuzzyMatchingSyncFragment : BaseFragment(R.layout.fragment_saved_events_sync_green_cards) { - - private val syncGreenCardsViewModel: SyncGreenCardsViewModel by viewModel() - - private val infoFragmentUtil: InfoFragmentUtil by inject() - - private val fuzzyMatchingSyncFragmentArgs: FuzzyMatchingSyncFragmentArgs by navArgs() - - override fun onButtonClickClose() { - findNavControllerSafety()?.popBackStack() - } - - override fun onButtonClickWithRetryAction() { - syncGreenCardsViewModel.refresh() - } - - override fun getFlow(): Flow { - return HolderFlow.FuzzyMatching - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - hideNavigationIcon() - - syncGreenCardsViewModel.refresh() - - syncGreenCardsViewModel.databaseSyncerResultLiveData.observe( - viewLifecycleOwner, - EventObserver { - when (it) { - is DatabaseSyncerResult.Success -> { - val navDirections = InfoFragmentDirections.actionMyOverview() - infoFragmentUtil.presentFullScreen( - currentFragment = this, - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_identitySelection_success_title), - descriptionData = DescriptionData( - htmlTextString = getString( - R.string.holder_identitySelection_success_body, - HtmlCompat.fromHtml(fuzzyMatchingSyncFragmentArgs.selectedName, HtmlCompat.FROM_HTML_MODE_LEGACY) - ), - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.back_to_overview), - navigationActionId = navDirections.actionId, - navigationArguments = navDirections.arguments - ) - ), - toolbarTitle = getString(R.string.holder_identitySelection_success_toolbar_title), - hideNavigationIcon = true - ) - } - is DatabaseSyncerResult.Failed -> { - presentError( - errorResult = it.errorResult - ) - } - is DatabaseSyncerResult.FuzzyMatchingError -> { - findNavControllerSafety()?.popBackStack( - R.id.nav_holder_fuzzy_matching, - true - ) - } - } - }) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFooterAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFooterAdapterItem.kt deleted file mode 100644 index f1f1d84ff..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFooterAdapterItem.kt +++ /dev/null @@ -1,32 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.ItemHolderNameSelectionFooterBinding - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderNameSelectionFooterAdapterItem( - private val onButtonClicked: () -> Unit -) : BindableItem(R.layout.item_holder_name_selection_footer.toLong()) { - - override fun bind(viewBinding: ItemHolderNameSelectionFooterBinding, position: Int) { - viewBinding.holderNameSelectionExplainButton.setOnClickListener { - onButtonClicked() - } - } - - override fun getLayout(): Int { - return R.layout.item_holder_name_selection_footer - } - - override fun initializeViewBinding(view: View): ItemHolderNameSelectionFooterBinding { - return ItemHolderNameSelectionFooterBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt deleted file mode 100644 index 70a8d8564..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt +++ /dev/null @@ -1,201 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.os.Bundle -import android.view.View -import androidx.core.text.HtmlCompat -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.SimpleItemAnimator -import com.google.android.material.divider.MaterialDividerItemDecoration -import com.xwray.groupie.GroupAdapter -import com.xwray.groupie.GroupieViewHolder -import com.xwray.groupie.Section -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentHolderNameSelectionBinding -import nl.rijksoverheid.ctr.holder.fuzzy_matching.HolderNameSelectionFragmentDirections.Companion.actionSavedEventsSyncGreenCards -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.parameter.parametersOf - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderNameSelectionFragment : Fragment(R.layout.fragment_holder_name_selection) { - private val section = Section() - - private val infoFragmentUtil: InfoFragmentUtil by inject() - private val dialogUtil: DialogUtil by inject() - private val selectionDetailBottomSheetDescriptionUtil: SelectionDetailBottomSheetDescriptionUtil by inject() - private val holderNameSelectionFragmentArgs: HolderNameSelectionFragmentArgs by navArgs() - - private val viewModel: HolderNameSelectionViewModel by viewModel { - parametersOf(holderNameSelectionFragmentArgs.matchingBlobIds.ids) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentHolderNameSelectionBinding.bind(view) - initRecyclerView(binding) - - viewModel.toolbarButtonsStateLiveData.observe(viewLifecycleOwner) { (_, canSkip) -> - if (canSkip) { - addToolbarButton() - } - } - - viewModel.itemsLiveData.observe(viewLifecycleOwner) { - setItems(it, binding) - } - - viewModel.nameSelectionError.observe(viewLifecycleOwner) { noSelectionError -> - if (noSelectionError) { - viewModel.nothingSelectedError() - binding.bottom.showError() - } - } - - binding.bottom.setButtonClick { - viewModel.storeSelection { selectedName -> - navigateSafety( - actionSavedEventsSyncGreenCards( - selectedName = selectedName - ) - ) - } - } - } - - private fun addToolbarButton() { - (parentFragment?.parentFragment as? HolderMainFragment)?.getToolbar().let { toolbar -> - if (toolbar?.menu?.size() == 0) { - toolbar.apply { - inflateMenu(R.menu.fuzzy_matching_toolbar) - - setOnMenuItemClickListener { - if (it.itemId == R.id.skip) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.holder_identitySelection_skipAlert_title, - message = getString(R.string.holder_identitySelection_skipAlert_body), - positiveButtonText = R.string.holder_identitySelection_skipAlert_action, - positiveButtonCallback = { - // close fuzzy matching - findNavController().popBackStack( - R.id.nav_holder_fuzzy_matching, - true - ) - }, - negativeButtonText = R.string.general_cancel - ) - } - true - } - } - } - } - } - - private fun resetToolbar() { - (parentFragment?.parentFragment as? HolderMainFragment)?.let { - it.getToolbar().menu.clear() - // Reset menu item listener to default - it.resetMenuItemListener() - } - } - - override fun onResume() { - super.onResume() - viewModel.canSkip(holderNameSelectionFragmentArgs.getEventsFlow) - } - - override fun onPause() { - super.onPause() - resetToolbar() - } - - private fun initRecyclerView(binding: FragmentHolderNameSelectionBinding) { - val adapter = GroupAdapter().also { - it.add(section) - } - binding.recyclerView.adapter = adapter - binding.recyclerView.addItemDecoration( - MaterialDividerItemDecoration( - requireContext(), - DividerItemDecoration.VERTICAL - ).apply { - isLastItemDecorated = false - } - ) - // prevent item decorator flickering when updating recyclerview to its error state - (binding.recyclerView.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false - } - - private fun setItems( - items: List, - binding: FragmentHolderNameSelectionBinding - ) { - section.update( - items.map { item -> - when (item) { - HolderNameSelectionItem.FooterItem -> HolderNameSelectionFooterAdapterItem { - infoFragmentUtil.presentAsBottomSheet( - fragmentManager = parentFragmentManager, - data = InfoFragmentData.TitleDescription( - title = getString(R.string.holder_fuzzyMatching_why_title), - descriptionData = DescriptionData( - htmlText = R.string.holder_fuzzyMatching_why_body, - htmlLinksEnabled = true - ) - ) - ) - } - HolderNameSelectionItem.HeaderItem -> HolderNameSelectionHeaderAdapterItem() - is HolderNameSelectionItem.ListItem -> HolderNameSelectionViewAdapterItem( - item, - { - val nameText = - getString(R.string.holder_identitySelection_details_body, HtmlCompat.fromHtml(item.name, HtmlCompat.FROM_HTML_MODE_LEGACY)) - val eventsText = selectionDetailBottomSheetDescriptionUtil.get( - selectionDetailData = item.detailData, - separator = " ${getString(R.string.general_and)} " - ) { - if (it.contains("dcc")) { - getString(R.string.holder_identitySelection_details_scannedPaperProof) - } else { - getString( - R.string.holder_storedEvents_listHeader_fetchedFromProvider, - it - ) - } - } - infoFragmentUtil.presentAsBottomSheet( - fragmentManager = parentFragmentManager, - data = InfoFragmentData.TitleDescription( - title = getString(R.string.general_details), - descriptionData = DescriptionData( - htmlTextString = "$nameText $eventsText", - htmlLinksEnabled = true - ) - ) - ) - }) { - binding.bottom.hideError() - viewModel.onItemSelected(item.name) - } - } - } - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionHeaderAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionHeaderAdapterItem.kt deleted file mode 100644 index eaf4adabf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionHeaderAdapterItem.kt +++ /dev/null @@ -1,26 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.ItemHolderNameSelectionHeaderBinding - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderNameSelectionHeaderAdapterItem : BindableItem(R.layout.item_holder_name_selection_header.toLong()) { - override fun bind(viewBinding: ItemHolderNameSelectionHeaderBinding, position: Int) { - } - - override fun getLayout(): Int { - return R.layout.item_holder_name_selection_header - } - - override fun initializeViewBinding(view: View): ItemHolderNameSelectionHeaderBinding { - return ItemHolderNameSelectionHeaderBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionItem.kt deleted file mode 100644 index c9e9d5681..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionItem.kt +++ /dev/null @@ -1,23 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -sealed class HolderNameSelectionItem { - object HeaderItem : HolderNameSelectionItem() - - data class ListItem( - val name: String, - val events: String, - val isSelected: Boolean = false, - val willBeRemoved: Boolean = false, - val nothingSelectedError: Boolean = false, - val detailData: List = listOf() - ) : HolderNameSelectionItem() - - object FooterItem : HolderNameSelectionItem() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt deleted file mode 100644 index 42284448d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt +++ /dev/null @@ -1,56 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.content.res.ColorStateList -import android.view.View -import androidx.core.view.isVisible -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.ItemHolderNameSelectionViewBinding - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderNameSelectionViewAdapterItem( - private val item: HolderNameSelectionItem.ListItem, - private val onDetailsButtonClicked: () -> Unit, - private val onSelected: () -> Unit -) : BindableItem( - R.layout.item_holder_name_selection_view.toLong() -) { - - override fun bind(viewBinding: ItemHolderNameSelectionViewBinding, position: Int) { - viewBinding.radioButton.isChecked = item.isSelected - if (item.nothingSelectedError) { - viewBinding.radioButton.buttonTintList = - ColorStateList.valueOf(viewBinding.radioButton.context.getColor(R.color.error)) - } else { - viewBinding.radioButton.isUseMaterialThemeColors = true - if (!item.isSelected) { - viewBinding.radioButton.buttonTintList = - ColorStateList.valueOf(viewBinding.radioButton.context.getColor(R.color.menu_icon_color)) - } - } - viewBinding.nameTextView.text = item.name - viewBinding.eventsTextView.text = item.events - viewBinding.removedEventTextView.isVisible = item.willBeRemoved - viewBinding.root.setOnClickListener { - onSelected() - } - viewBinding.detailsButton.contentDescription = "${item.name} ${viewBinding.detailsButton.text}" - viewBinding.detailsButton.setOnClickListener { - onDetailsButtonClicked() - } - } - - override fun getLayout(): Int { - return R.layout.item_holder_name_selection_view - } - - override fun initializeViewBinding(view: View): ItemHolderNameSelectionViewBinding { - return ItemHolderNameSelectionViewBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt deleted file mode 100644 index 263c915d7..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt +++ /dev/null @@ -1,114 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCase -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class HolderNameSelectionViewModel( - holderDatabase: HolderDatabase, - greenCardUtil: GreenCardUtil -) : FuzzyMatchingBaseViewModel(holderDatabase, greenCardUtil) { - val itemsLiveData: LiveData> = MutableLiveData() - val nameSelectionError: LiveData = MutableLiveData() - - abstract fun onItemSelected(selectedName: String) - abstract fun storeSelection(onStored: (String) -> Unit) - abstract fun nothingSelectedError() -} - -class HolderNameSelectionViewModelImpl( - private val matchedEventsUseCase: MatchedEventsUseCase, - private val getRemoteProtocolFromEventGroupUseCase: GetRemoteProtocolFromEventGroupUseCase, - private val selectionDataUtil: SelectionDataUtil, - private val yourEventsFragmentUtil: YourEventsFragmentUtil, - private val holderDatabase: HolderDatabase, - greenCardUtil: GreenCardUtil, - private val matchingBlobIds: List> -) : HolderNameSelectionViewModel(holderDatabase, greenCardUtil) { - - init { - updateItems() - } - - override fun onItemSelected(selectedName: String) { - (nameSelectionError as MutableLiveData).value = false - updateItems(selectedName) - } - - private fun selectedName(): String? { - return itemsLiveData.value - ?.filterIsInstance() - ?.find { it.isSelected } - ?.name - } - - override fun storeSelection(onStored: (String) -> Unit) { - val selectedName = selectedName() - if (selectedName != null) { - val items = itemsLiveData.value?.filterIsInstance() ?: return - val itemSelected = items.find { it.isSelected } - if (itemSelected != null) { - viewModelScope.launch { - matchedEventsUseCase.selected(items.indexOf(itemSelected), matchingBlobIds) - onStored(selectedName) - } - } - } else { - (nameSelectionError as MutableLiveData).value = true - } - } - - override fun nothingSelectedError() { - updateItems(nothingSelectedError = true) - } - - private fun updateItems( - selectedName: String? = null, - nothingSelectedError: Boolean = false - ) { - viewModelScope.launch { - val allEvents = holderDatabase.eventGroupDao().getAll() - val fuzzyMatchedRemoteProtocols = matchingBlobIds.map { eventsCluster -> - eventsCluster.mapNotNull { fuzzyMatchedEventId -> - allEvents.find { it.id == fuzzyMatchedEventId } - }.mapNotNull(getRemoteProtocolFromEventGroupUseCase::get) - } - - val holderNames = fuzzyMatchedRemoteProtocols.map { it.map { yourEventsFragmentUtil.getFullName(it.holder) } } - val selectionItems = fuzzyMatchedRemoteProtocols.mapIndexed { index, remoteProtocols -> - val holders = remoteProtocols.map { it.holder }.map { yourEventsFragmentUtil.getFullName(it) } - val events = remoteProtocols.flatMap { it.events ?: emptyList() }.sortedByDescending { it.getDate() } - val providerIdentifier = remoteProtocols.first().providerIdentifier - // make sure we don't select a name present in other groups - val otherHolderNames = holderNames.filterIndexed { i, _ -> i != index }.flatten() - val name = holders.first { !otherHolderNames.contains(it) } - HolderNameSelectionItem.ListItem( - name = name, - events = selectionDataUtil.events(events), - detailData = selectionDataUtil.details(providerIdentifier, events), - isSelected = name == selectedName, - willBeRemoved = selectedName != null && name != selectedName, - nothingSelectedError = nothingSelectedError - ) - }.toTypedArray() - - (itemsLiveData as MutableLiveData).value = listOf( - HolderNameSelectionItem.HeaderItem, - *selectionItems, - HolderNameSelectionItem.FooterItem - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/MatchedEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/MatchedEventsUseCase.kt deleted file mode 100644 index 72d743757..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/MatchedEventsUseCase.kt +++ /dev/null @@ -1,47 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface MatchedEventsUseCase { - suspend fun selected(selectionIndex: Int, matchingBlobIds: List>) -} - -class MatchedEventsUseCaseImpl( - private val getRemoteProtocolFromEventGroupUseCase: GetRemoteProtocolFromEventGroupUseCase, - private val holderDatabase: HolderDatabase -) : MatchedEventsUseCase { - override suspend fun selected(selectionIndex: Int, matchingBlobIds: List>) { - val eventIdsToDelete = matchingBlobIds.filterIndexed { index, _ -> - index != selectionIndex - }.flatten().toSet().filter { !matchingBlobIds[selectionIndex].contains(it) } - - holderDatabase.eventGroupDao().getAllOfIds(eventIdsToDelete).forEach { eventGroupEntity -> - val remoteProtocol = getRemoteProtocolFromEventGroupUseCase.get(eventGroupEntity) - remoteProtocol?.events?.forEach { remoteEvent -> - holderDatabase.removedEventDao().insert( - RemovedEventEntity( - walletId = eventGroupEntity.walletId, - type = eventGroupEntity.type.getTypeString(), - eventTime = remoteEvent.getDate(), - reason = RemovedEventReason.FuzzyMatched - ) - ) - } - } - - val unDraftEventsIds = matchingBlobIds.flatten().toSet().minus(eventIdsToDelete.toSet()) - - holderDatabase.eventGroupDao().deleteAllOfIds(eventIdsToDelete) - holderDatabase.eventGroupDao().updateDraft(unDraftEventsIds.toList(), false) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/MatchingBlobIds.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/MatchingBlobIds.kt deleted file mode 100644 index 747ea147f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/MatchingBlobIds.kt +++ /dev/null @@ -1,14 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class MatchingBlobIds(val ids: List>) : Parcelable diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt deleted file mode 100644 index 3b80e5304..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt +++ /dev/null @@ -1,108 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_NEGATIVE_TEST -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_POSITIVE_TEST -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_RECOVERY -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_TEST -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_VACCINATION -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventStringUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface SelectionDataUtil { - fun events(remoteEvents: List): String - fun details( - providerIdentifier: String, - remoteEvents: List - ): List -} - -class SelectionDataUtilImpl( - private val cachedAppConfigUseCase: CachedAppConfigUseCase, - private val yourEventsFragmentUtil: YourEventsFragmentUtil, - private val remoteEventStringUtil: RemoteEventStringUtil, - private val getQuantityString: (Int, Int) -> String, - private val getFormattedString: (Int, String) -> String, - private val getString: (Int) -> String -) : SelectionDataUtil { - - override fun events(remoteEvents: List): String { - val testResultTypes = listOf(TYPE_NEGATIVE_TEST, TYPE_POSITIVE_TEST, TYPE_RECOVERY, TYPE_TEST) - val vaccinationCount = remoteEvents.filter { it.type == TYPE_VACCINATION }.size - val testCount = remoteEvents.filter { testResultTypes.contains(it.type) }.size - - val eventsString = StringBuilder() - - if (vaccinationCount > 0) { - eventsString.append( - "$vaccinationCount ${ - getQuantityString( - R.plurals.general_vaccinations, - vaccinationCount - ) - }" - ) - } - if (testCount > 0) { - if (eventsString.isNotEmpty()) { - eventsString.append(" ${getString(R.string.general_and)} ") - } - eventsString.append( - "$testCount ${ - getQuantityString( - R.plurals.general_testresults, - testCount - ) - }" - ) - } - - return eventsString.toString() - } - - override fun details( - providerIdentifier: String, - remoteEvents: List - ): List { - val configProviders = cachedAppConfigUseCase.getCachedAppConfig().providers - - val data = remoteEvents.map { event -> - val eventDate = event.getDate() - SelectionDetailData( - type = "${remoteEventStringUtil.remoteEventTitle(event.javaClass)}${ - if (providerIdentifier.startsWith("dcc") && event is RemoteEventVaccination) { - val dose = event.vaccination?.doseNumber - val totalDoses = event.vaccination?.totalDoses - if (dose != null && totalDoses != null) { - " ${getFormattedString(R.string.your_vaccination_explanation_dose, "$dose/$totalDoses")}" - } else { - "" - } - } else { - "" - } - }", - providerIdentifiers = listOf( - yourEventsFragmentUtil.getProviderName( - configProviders, - providerIdentifier - ) - ), - eventDate = eventDate?.toLocalDate()?.formatDayMonthYear() ?: "" - ) - } - - return data - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDetailBottomSheetDescriptionUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDetailBottomSheetDescriptionUtil.kt deleted file mode 100644 index 5dfc85ccf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDetailBottomSheetDescriptionUtil.kt +++ /dev/null @@ -1,34 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface SelectionDetailBottomSheetDescriptionUtil { - fun get( - selectionDetailData: List, - separator: String, - retrievedBy: (String) -> String - ): String -} - -class SelectionDetailBottomSheetDescriptionUtilImpl() : SelectionDetailBottomSheetDescriptionUtil { - override fun get( - selectionDetailData: List, - separator: String, - retrievedBy: (String) -> String - ): String { - val description = StringBuilder("

") - - selectionDetailData.forEach { - description.append("${it.type}
") - description.append("${retrievedBy(it.providerIdentifiers.joinToString(separator))}
") - description.append("${it.eventDate}

") - } - - return description.toString() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDetailData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDetailData.kt deleted file mode 100644 index 0efab56dc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDetailData.kt +++ /dev/null @@ -1,14 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class SelectionDetailData( - val type: String, - val providerIdentifiers: List, - val eventDate: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/ToolbarButtonsState.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/ToolbarButtonsState.kt deleted file mode 100644 index eb2505749..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/ToolbarButtonsState.kt +++ /dev/null @@ -1,10 +0,0 @@ -package nl.rijksoverheid.ctr.holder.fuzzy_matching - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class ToolbarButtonsState(val canGoBack: Boolean, val canSkip: Boolean) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/DigiDFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/DigiDFragment.kt deleted file mode 100644 index ddac367f0..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/DigiDFragment.kt +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events - -import android.os.Bundle -import android.view.View -import androidx.activity.result.contract.ActivityResultContracts -import net.openid.appauth.AppAuthConfiguration -import net.openid.appauth.AuthorizationService -import net.openid.appauth.browser.BrowserAllowList -import net.openid.appauth.browser.BrowserSelector -import net.openid.appauth.browser.VersionRange -import net.openid.appauth.browser.VersionedBrowserMatcher -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentDirections -import nl.rijksoverheid.ctr.design.utils.DialogButtonData -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.design.utils.SharedDialogFragmentDirections -import nl.rijksoverheid.ctr.holder.BaseFragment -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.EventsResult -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.get_events.utils.LoginTypeUtil -import nl.rijksoverheid.ctr.holder.modules.qualifier.LoginQualifier -import nl.rijksoverheid.ctr.holder.your_events.YourEventsFragmentType -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.ErrorResultFragmentData -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.sharedViewModel -import org.koin.androidx.viewmodel.ext.android.viewModel -import org.koin.core.qualifier.named - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class DigiDFragment(contentLayoutId: Int) : BaseFragment(contentLayoutId) { - - private val infoFragmentUtil: InfoFragmentUtil by inject() - private val loginTypeUtil: LoginTypeUtil by inject() - private val getEventsViewModel: GetEventsViewModel by viewModel() - protected val digidViewModel: LoginViewModel by sharedViewModel(named(LoginQualifier.DIGID)) - protected val mijnCnViewModel: LoginViewModel by viewModel(named(LoginQualifier.MIJN_CN)) - private val authService by lazy { - val appAuthConfig = AppAuthConfiguration.Builder() - .setBrowserMatcher(BrowserAllowList(*getSupportedBrowsers())) - .build() - AuthorizationService(requireActivity(), appAuthConfig) - } - - private val loginResult = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - digidViewModel.handleActivityResult(getLoginType(), it, authService) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val copy = getCopyForOriginType() - - getEventsViewModel.eventsResult.observe(viewLifecycleOwner, EventObserver { - when (it) { - is EventsResult.Success -> { - if (it.missingEvents) { - val yourEventsDestination = SharedDialogFragmentDirections.actionYourEvents( - toolbarTitle = getCopyForOriginType().toolbarTitle, - type = yourEventsFragmentType( - remoteProtocols = it.remoteEvents, - eventProviders = it.eventProviders - ), - flow = getFlow() - ) - openDialog( - data = DialogFragmentData( - title = getDialogTitleFromOriginType(getOriginTypes().first()), - message = R.string.error_get_events_missing_events_dialog_description, - positiveButtonData = DialogButtonData.NavigationButton( - textId = R.string.dialog_close, - navigationActionId = yourEventsDestination.actionId, - navigationArguments = yourEventsDestination.arguments - ) - ) - ) - dialogPresented() - } else { - onNavigateToYourEvents( - remoteProtocols = it.remoteEvents, - eventProviders = it.eventProviders - ) - } - } - is EventsResult.HasNoEvents -> { - if (it.missingEvents) { - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.error_get_events_no_events_title), - description = getString( - R.string.error_get_events_http_error_description, - getErrorCodes(it.errorResults) - ), - buttonTitle = getString(R.string.back_to_overview), - ErrorResultFragmentData.ButtonAction.Destination(R.id.action_my_overview), - urlData = ErrorResultFragmentData.UrlData( - urlButtonTitle = getString(R.string.error_something_went_wrong_outage_button), - urlButtonUrl = getString(R.string.error_something_went_wrong_outage_button_url) - ) - ) - ) - } else { - val navDirections = InfoFragmentDirections.actionMyOverview() - infoFragmentUtil.presentFullScreen( - currentFragment = this, - infoFragmentDirections = GetEventsFragmentDirections.actionInfoFragment( - toolbarTitle = copy.toolbarTitle, - data = InfoFragmentData.TitleDescriptionWithButton( - title = copy.hasNoEventsTitle, - descriptionData = DescriptionData( - htmlTextString = copy.hasNoEventsDescription, - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.back_to_overview), - navigationActionId = navDirections.actionId, - navigationArguments = navDirections.arguments - ) - ) - ) - ) - } - } - is EventsResult.Error -> { - when { - it.accessTokenSessionExpiredError() -> { - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.error_access_tokens_session_expired_title), - description = getString(R.string.error_access_tokens_Session_expired_description), - buttonTitle = getString(R.string.error_access_tokens_session_expired_button), - ErrorResultFragmentData.ButtonAction.PopBackStack - ) - ) - } - it.accessTokenNoBsn() -> { - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.error_access_tokens_no_bsn_title), - description = getString(R.string.error_access_tokens_no_bsn_description), - buttonTitle = getString(R.string.back_to_overview), - buttonAction = ErrorResultFragmentData.ButtonAction.Destination( - R.id.action_my_overview - ) - ) - ) - } - it.unomiOrEventErrors() -> { - presentError( - it.errorResults.first(), - getString( - R.string.error_get_events_http_error_description, - getErrorCodes(it.errorResults) - ) - ) - } - else -> { - presentError(it.errorResults.first()) - } - } - } - } - }) - - digidViewModel.loading.observe(viewLifecycleOwner, EventObserver { - onDigidLoading(it) - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(it) - }) - - digidViewModel.loginResultLiveData.observe(viewLifecycleOwner, EventObserver { - when (it) { - is LoginResult.Success -> { - getEventsViewModel.getDigidEvents( - getLoginType(), - it.jwt, - getOriginTypes() - ) - } - is LoginResult.Failed -> { - presentError(it.errorResult) - } - is LoginResult.Cancelled -> { - openDialog( - DialogFragmentData( - title = loginTypeUtil.getCanceledDialogTitle(getLoginType()), - message = loginTypeUtil.getCanceledDialogDescription( - getLoginType(), - getOriginTypes().first() - ), - positiveButtonData = DialogButtonData.Dismiss( - textId = R.string.dialog_close - ) - ) - ) - dialogPresented() - } - LoginResult.NoBrowserFound -> { - openDialog( - DialogFragmentData( - title = R.string.dialog_no_browser_title, - message = R.string.dialog_no_browser_message, - messageArguments = listOf( - getString( - loginTypeUtil.getNoBrowserDialogDescription( - getLoginType() - ) - ) - ), - positiveButtonData = DialogButtonData.Dismiss( - textId = R.string.ok - ) - ) - ) - dialogPresented() - } - } - onDigidLoading(false) - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(false) - }) - - getEventsViewModel.loading.observe(viewLifecycleOwner, EventObserver { - onGetEventsLoading(it) - }) - } - - fun loginWithDigiD() { - digidViewModel.login(getLoginType(), loginResult, authService) - } - - private fun getErrorCodes(errorResults: List): String { - return errorCodeStringFactory.get( - flow = getFlow(), - errorResults = errorResults - ) - } - - private fun getDialogTitleFromOriginType(originType: RemoteOriginType): Int { - return when (originType) { - RemoteOriginType.Recovery -> R.string.error_get_events_missing_events_dialog_title_recoveries - RemoteOriginType.Test -> R.string.error_get_events_missing_events_dialog_title_testresults - RemoteOriginType.Vaccination -> R.string.error_get_events_missing_events_dialog_title_vaccines - } - } - - /** - * Gets all supported browsers and filters out the custom tab browsers as those can cause - * issues with DigiD - * - * @return Array of browser matchers supported for the app auth config - */ - private fun getSupportedBrowsers(): Array = - BrowserSelector.getAllBrowsers(context) - .filter { it.useCustomTab == false } - .filter { it.packageName != "android" } - .map { - VersionedBrowserMatcher( - it.packageName, - it.signatureHashes, - false, - VersionRange.ANY_VERSION - ) - }.toTypedArray() - - protected fun getCopyForOriginType(): GetEventsFragmentCopy { - when (getOriginTypes().first()) { - is RemoteOriginType.Test -> { - return GetEventsFragmentCopy( - title = getString(R.string.holder_negativetest_ggd_title), - description = getString(R.string.holder_negativetest_ggd_message), - toolbarTitle = getString(R.string.your_negative_test_results_header), - hasNoEventsTitle = getString(R.string.no_test_results_title), - hasNoEventsDescription = getString(R.string.no_test_results_description) - ) - } - is RemoteOriginType.Vaccination -> { - return GetEventsFragmentCopy( - title = getString(R.string.holder_addVaccination_title), - description = getString(R.string.holder_addVaccination_message), - toolbarTitle = getString(R.string.your_vaccination_result_toolbar_title), - hasNoEventsTitle = getString(R.string.no_vaccinations_title), - hasNoEventsDescription = getString(R.string.no_vaccinations_description) - ) - } - is RemoteOriginType.Recovery -> { - return GetEventsFragmentCopy( - title = getString(R.string.get_recovery_title), - description = getString(R.string.get_recovery_description), - toolbarTitle = getString(R.string.your_positive_test_toolbar_title), - hasNoEventsTitle = getString(R.string.no_positive_test_result_title), - hasNoEventsDescription = getString(R.string.no_positive_test_result_description) - ) - } - } - } - - abstract fun getLoginType(): LoginType - abstract fun onDigidLoading(loading: Boolean) - abstract fun onGetEventsLoading(loading: Boolean) - abstract fun getOriginTypes(): List - abstract fun onNavigateToYourEvents( - remoteProtocols: Map, - eventProviders: List = emptyList() - ) - - abstract fun dialogPresented() - abstract fun yourEventsFragmentType( - remoteProtocols: Map, - eventProviders: List - ): YourEventsFragmentType - - abstract fun openDialog(data: DialogFragmentData) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/GetEventsFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/GetEventsFragment.kt deleted file mode 100644 index 271ef7e85..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/GetEventsFragment.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events - -import android.os.Bundle -import android.view.View -import androidx.core.view.isVisible -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentGetEventsBinding -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.no_digid.NoDigidFragmentData -import nl.rijksoverheid.ctr.holder.no_digid.NoDigidScreenDataUtil -import nl.rijksoverheid.ctr.holder.your_events.YourEventsFragmentType -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.utils.Accessibility.makeIndeterminateAccessible -import org.koin.android.ext.android.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class GetEventsFragment : DigiDFragment(R.layout.fragment_get_events) { - - private val args: GetEventsFragmentArgs by navArgs() - private var _binding: FragmentGetEventsBinding? = null - private val binding get() = _binding!! - private val noDigidScreenDataUtil: NoDigidScreenDataUtil by inject() - - override fun onButtonClickWithRetryAction() { - loginWithDigiD() - } - - override fun getFlow(): Flow { - return when (args.originType) { - RemoteOriginType.Recovery -> HolderFlow.Recovery - RemoteOriginType.Test -> HolderFlow.DigidTest - RemoteOriginType.Vaccination -> HolderFlow.Vaccination - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - _binding = FragmentGetEventsBinding.bind(view) - super.onViewCreated(view, savedInstanceState) - setBindings() - } - - override fun getLoginType(): LoginType { - return LoginType.Max - } - - private fun setBindings() { - val copy = getCopyForOriginType() - - binding.title.text = copy.title - binding.description.setHtmlText(copy.description, htmlLinksEnabled = true) - binding.button.setOnClickListener { - onButtonClickWithRetryAction() - } - binding.noDigidButton.setOnClickListener { - if (args.originType == RemoteOriginType.Vaccination) { - navigateSafety( - GetEventsFragmentDirections.actionNoDigid( - NoDigidFragmentData( - title = getString(R.string.holder_noDigiD_title), - description = getString(R.string.holder_noDigiD_message), - firstNavigationButtonData = noDigidScreenDataUtil.requestDigidButton(), - secondNavigationButtonData = noDigidScreenDataUtil.continueWithoutDigidButton( - args.originType - ), - originType = args.originType - ) - ) - ) - } else { - navigateSafety( - GetEventsFragmentDirections.actionPap(args.originType) - ) - } - } - - if (args.originType == RemoteOriginType.Vaccination) { - binding.checkboxWithHeader.visibility = View.VISIBLE - binding.checkboxWithHeader.header(R.string.holder_addVaccination_alsoCollectPositiveTestResults_message) - } - } - - override fun onDestroyView() { - super.onDestroyView() - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(false) - } - - override fun onDigidLoading(loading: Boolean) { - binding.button.isEnabled = !loading - binding.checkboxWithHeader.binding.checkbox.isEnabled = !loading - } - - override fun onGetEventsLoading(loading: Boolean) { - if (loading) { - binding.loadingOverlay.progressBar.makeIndeterminateAccessible( - context = requireContext(), - isLoading = true, - message = R.string.holder_fetchevents_loading - ) - binding.loadingOverlay.root.isVisible = true - } - } - - override fun getOriginTypes(): List { - val originTypes = mutableListOf() - originTypes.add(args.originType) - if (binding.checkboxWithHeader.binding.checkbox.isChecked) { - originTypes.add(RemoteOriginType.Recovery) - } - return originTypes - } - - override fun onNavigateToYourEvents( - remoteProtocols: Map, - eventProviders: List - ) { - val flow = getFlow() - val checkedPositiveTest = binding.checkboxWithHeader.binding.checkbox.isChecked - navigateSafety( - GetEventsFragmentDirections.actionYourEvents( - type = YourEventsFragmentType.RemoteProtocol3Type( - remoteEvents = remoteProtocols, - eventProviders = eventProviders - ), - toolbarTitle = getCopyForOriginType().toolbarTitle, - flow = if (flow == HolderFlow.Vaccination && checkedPositiveTest) { - HolderFlow.VaccinationAndPositiveTest - } else { - flow - } - ) - ) - } - - override fun yourEventsFragmentType( - remoteProtocols: Map, - eventProviders: List - ): YourEventsFragmentType { - return YourEventsFragmentType.RemoteProtocol3Type( - remoteEvents = remoteProtocols, - eventProviders = eventProviders - ) - } - - override fun dialogPresented() { - binding.loadingOverlay.progressBar.makeIndeterminateAccessible( - context = requireContext(), - isLoading = false, - message = R.string.holder_fetchevents_loading - ) - binding.loadingOverlay.root.isVisible = false - } - - override fun openDialog(data: DialogFragmentData) { - navigateSafety(GetEventsFragmentDirections.actionDialog(data)) - } -} - -data class GetEventsFragmentCopy( - val title: String, - val description: String, - val toolbarTitle: String, - val hasNoEventsTitle: String, - val hasNoEventsDescription: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/GetEventsViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/GetEventsViewModel.kt deleted file mode 100644 index ede5fe7aa..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/GetEventsViewModel.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.get_events.models.EventsResult -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.usecases.ConfigProvidersUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetEventsUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetMijnCnEventsUsecase -import nl.rijksoverheid.ctr.shared.livedata.Event - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class GetEventsViewModel : ViewModel() { - val loading: LiveData> = MutableLiveData() - val eventsResult: LiveData> = MutableLiveData() - - abstract fun getDigidEvents( - loginType: LoginType, - jwt: String, - originTypes: List, - getPositiveTestWithVaccination: Boolean = false - ) - - abstract fun getMijnCnEvents( - jwt: String, - originType: RemoteOriginType, - getPositiveTestWithVaccination: Boolean = false - ) -} - -class GetEventsViewModelImpl( - private val configProvidersUseCase: ConfigProvidersUseCase, - private val getEventsUseCase: GetEventsUseCase, - private val mijnCnEventsUsecase: GetMijnCnEventsUsecase -) : GetEventsViewModel() { - - override fun getDigidEvents( - loginType: LoginType, - jwt: String, - originTypes: List, - getPositiveTestWithVaccination: Boolean - ) { - getEvents { - getEventsUseCase.getEvents( - eventProvidersResult = configProvidersUseCase.eventProviders(), - loginType = loginType, - jwt = jwt, - originTypes = originTypes - ) - } - } - - override fun getMijnCnEvents( - jwt: String, - originType: RemoteOriginType, - getPositiveTestWithVaccination: Boolean - ) { - getEvents { - mijnCnEventsUsecase.getEvents( - jwt = jwt, - originType = originType, - withIncompleteVaccination = getPositiveTestWithVaccination - ) - } - } - - fun getEvents(function: suspend () -> EventsResult) { - (loading as MutableLiveData).value = Event(true) - viewModelScope.launch { - try { - val events = function.invoke() - (eventsResult as MutableLiveData).value = Event(events) - } finally { - loading.value = Event(false) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/LoginResult.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/LoginResult.kt deleted file mode 100644 index e86925add..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/LoginResult.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events - -import nl.rijksoverheid.ctr.shared.models.ErrorResult - -sealed class LoginResult { - data class Success(val jwt: String) : LoginResult() - data class Failed(val errorResult: ErrorResult) : LoginResult() - object Cancelled : LoginResult() - object NoBrowserFound : LoginResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/LoginViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/LoginViewModel.kt deleted file mode 100644 index 6e4d2e75f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/LoginViewModel.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events - -import android.content.ActivityNotFoundException -import android.content.Intent -import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import net.openid.appauth.AuthorizationException -import net.openid.appauth.AuthorizationResponse -import net.openid.appauth.AuthorizationService -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.models.HolderStep.DigidNetworkRequest -import nl.rijksoverheid.ctr.shared.exceptions.OpenIdAuthorizationException -import nl.rijksoverheid.ctr.shared.livedata.Event -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import nl.rijksoverheid.ctr.shared.models.OpenIdErrorResult.Error -import nl.rijksoverheid.ctr.shared.models.OpenIdErrorResult.ServerBusy -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil -import nl.rijksoverheid.rdo.modules.openidconnect.OpenIDConnectRepository - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class LoginViewModel( - private val digidAuthenticationRepository: OpenIDConnectRepository, - private val androidUtil: AndroidUtil -) : ViewModel() { - - private companion object { - const val USER_CANCELLED_FLOW_CODE = 1 - const val NETWORK_ERROR = 3 - const val LOGIN_REQUIRED_ERROR = "login_required" - const val SAML_AUTHN_FAILED_ERROR = "saml_authn_failed" - const val CANCELLED = "cancelled" - } - - val loading: LiveData> = MutableLiveData() - val loginResultLiveData = MutableLiveData>() - - fun login( - loginType: LoginType, - activityResultLauncher: ActivityResultLauncher, - authService: AuthorizationService - ) { - (loading as MutableLiveData).value = Event(true) - val issuerUrl = when (loginType) { - LoginType.Max -> BuildConfig.MAX_BASE_URL - LoginType.Pap -> BuildConfig.PAP_BASE_URL - } - viewModelScope.launch { - try { - digidAuthenticationRepository.requestAuthorization(issuerUrl, activityResultLauncher, authService) - } catch (e: Exception) { - postExceptionResult(e) - loading.value = Event(false) - } - } - } - - fun handleActivityResult(loginType: LoginType, activityResult: ActivityResult, authService: AuthorizationService) { - viewModelScope.launch { - val intent = activityResult.data - if (intent != null) { - val authResponse = AuthorizationResponse.fromIntent(intent) - val authError = AuthorizationException.fromIntent(intent) - when { - authError != null -> postAuthErrorResult(authError) - authResponse != null -> postAuthResponseResult(loginType, authService, authResponse) - else -> postAuthNullResult() - } - } else { - loginResultLiveData.postValue(Event(LoginResult.Cancelled)) - } - } - } - - private fun postAuthErrorResult(authError: AuthorizationException) { - val digidResult = when { - isUserCancelled(authError) -> LoginResult.Cancelled - isNetworkError(authError) -> getNetworkErrorResult(authError) - isServerBusy(authError) -> getServerBusyResult(authError) - else -> LoginResult.Failed(Error(DigidNetworkRequest, mapToOpenIdException(authError))) - } - loginResultLiveData.postValue(Event(digidResult)) - } - - private fun mapToOpenIdException(authError: AuthorizationException) = - OpenIdAuthorizationException(type = authError.type, code = authError.code) - - private fun isNetworkError(authError: AuthorizationException): Boolean { - return authError.type == AuthorizationException.TYPE_GENERAL_ERROR && authError.code == NETWORK_ERROR - } - - private fun getNetworkErrorResult(authError: AuthorizationException): LoginResult { - return if (!androidUtil.isNetworkAvailable()) { - LoginResult.Failed(NetworkRequestResult.Failed.ClientNetworkError(DigidNetworkRequest)) - } else { - LoginResult.Failed( - NetworkRequestResult.Failed.ServerNetworkError( - DigidNetworkRequest, - mapToOpenIdException(authError) - ) - ) - } - } - - private fun isServerBusy(authError: AuthorizationException) = - authError.error == LOGIN_REQUIRED_ERROR - - private fun getServerBusyResult(authError: AuthorizationException) = - LoginResult.Failed( - ServerBusy(DigidNetworkRequest, mapToOpenIdException(authError)) - ) - - private fun isUserCancelled(authError: AuthorizationException) = - (authError.type == AuthorizationException.TYPE_GENERAL_ERROR && authError.code == USER_CANCELLED_FLOW_CODE) || - authError.error == SAML_AUTHN_FAILED_ERROR || - authError.error == CANCELLED - - private suspend fun postAuthResponseResult( - loginType: LoginType, - authService: AuthorizationService, - authResponse: AuthorizationResponse - ) { - try { - val tokenResponse = - digidAuthenticationRepository.tokenResponse(authService, authResponse) - val jwt = when (loginType) { - LoginType.Max -> tokenResponse.idToken!! - LoginType.Pap -> tokenResponse.accessToken!! - } - loginResultLiveData.postValue(Event(LoginResult.Success(jwt))) - } catch (e: Exception) { - postExceptionResult(e) - } - } - - private fun postExceptionResult(e: Exception) { - if (e is AuthorizationException) { - postAuthErrorResult(e) - } else { - // App Auth will launch a browser intent to log in with DigiD. - // When it throws an ActivityNotFoundException it means there is no browser app to handle the intent. - val result = if (e is ActivityNotFoundException) { - LoginResult.NoBrowserFound - } else { - LoginResult.Failed(Error(DigidNetworkRequest, e)) - } - loginResultLiveData.postValue(Event(result)) - } - } - - private fun postAuthNullResult() { - loginResultLiveData.postValue( - Event(LoginResult.Failed(Error(DigidNetworkRequest, NullPointerException()))) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/EventProvider.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/EventProvider.kt deleted file mode 100644 index e73b58117..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/EventProvider.kt +++ /dev/null @@ -1,14 +0,0 @@ -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class EventProvider(val identifier: String, val name: String) : Parcelable diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/EventsResult.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/EventsResult.kt deleted file mode 100644 index 7c5faeca6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/EventsResult.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.shared.exceptions.NoProvidersException -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -sealed class EventsResult { - data class Success( - val remoteEvents: Map, - val missingEvents: Boolean, - val eventProviders: List - ) : EventsResult() - - data class HasNoEvents(val missingEvents: Boolean, val errorResults: List = emptyList()) : EventsResult() - - data class Error constructor(val errorResults: List) : EventsResult() { - constructor(errorResult: ErrorResult) : this(listOf(errorResult)) - - fun accessTokenSessionExpiredError(): Boolean { - val accessTokenCallError = errorResults.find { it.getCurrentStep() == HolderStep.AccessTokensNetworkRequest } - accessTokenCallError?.let { - return hasErrorCode(it, 99710) - } - return false - } - - fun accessTokenNoBsn(): Boolean { - val accessTokenCallError = errorResults.find { it.getCurrentStep() == HolderStep.AccessTokensNetworkRequest } - accessTokenCallError?.let { - return hasErrorCode(it, 99782) - } - return false - } - - private fun hasErrorCode(errorResult: ErrorResult, expectedErrorCode: Int): Boolean { - return if (errorResult is NetworkRequestResult.Failed.CoronaCheckWithErrorResponseHttpError) { - errorResult.getCode() == expectedErrorCode - } else { - false - } - } - - fun unomiOrEventErrors(): Boolean { - val unomiOrEventErrors = errorResults.find { it.getCurrentStep() == HolderStep.UnomiNetworkRequest || it.getCurrentStep() == HolderStep.EventNetworkRequest } - return unomiOrEventErrors != null - } - - fun isMijnCnMissingDataErrors(): Boolean { - val returnedError = errorResults.find { it.getCurrentStep() == HolderStep.EventNetworkRequest } - returnedError?.let { errorResult -> - return errorResult is NetworkRequestResult.Failed.CoronaCheckWithErrorResponseHttpError && errorResult.getCode() in 777706..777716 - } - return false - } - - companion object { - fun noProvidersError(originType: RemoteOriginType) = Error(object : ErrorResult { - override fun getCurrentStep() = HolderStep.ConfigProvidersNetworkRequest - - override fun getException() = when (originType) { - RemoteOriginType.Recovery -> NoProvidersException.Recovery - RemoteOriginType.Test -> NoProvidersException.Test - RemoteOriginType.Vaccination -> NoProvidersException.Vaccination - } - }) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/Holder.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/Holder.kt deleted file mode 100644 index 677045a17..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/Holder.kt +++ /dev/null @@ -1,21 +0,0 @@ -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import com.squareup.moshi.JsonClass -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -@JsonClass(generateAdapter = true) -data class Holder( - val firstNameInitial: String, - val lastNameInitial: String, - val birthDay: String, - val birthMonth: String -) : Parcelable diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/LoginType.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/LoginType.kt deleted file mode 100644 index 5913677d4..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/LoginType.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -sealed class LoginType { - object Max : LoginType() - object Pap : LoginType() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/MijnCNTokenResponse.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/MijnCNTokenResponse.kt deleted file mode 100644 index a00236944..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/MijnCNTokenResponse.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class MijnCNTokenResponse( - @Json(name = "access_token") val accessToken: String, - @Json(name = "token_type") val tokenType: String, - @Json(name = "expires_in") val expiresIn: Int, - @Json(name = "id_token") val idToken: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteAccessTokens.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteAccessTokens.kt deleted file mode 100644 index 44aa5db73..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteAccessTokens.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class RemoteAccessTokens( - val tokens: List = listOf() -) { - @JsonClass(generateAdapter = true) - data class Token( - @Json(name = "provider_identifier") val providerIdentifier: String, - @Json(name = "unomi") val unomi: String, - @Json(name = "event") val event: String - ) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteConfigProviders.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteConfigProviders.kt deleted file mode 100644 index 82cd62951..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteConfigProviders.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class RemoteConfigProviders( - @Json(name = "tokenProviders") val testProviders: List, - @Json(name = "eventProviders") val eventProviders: List, - @Json(name = "eventProvidersBes") val eventProvidersBes: List -) { - - @JsonClass(generateAdapter = true) - data class TestProvider( - val name: String, - @Json(name = "identifier") val providerIdentifier: String, - @Json(name = "url") val resultUrl: String, - @Json(name = "cms") val cms: List, - @Json(name = "tls") val tls: List, - @Json(name = "usage") val usage: List - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as TestProvider - - if (name != other.name) return false - if (providerIdentifier != other.providerIdentifier) return false - if (resultUrl != other.resultUrl) return false - if (!cms.first().contentEquals(other.cms.first())) return false - - return true - } - - override fun hashCode(): Int { - var result = name.hashCode() - result = 31 * result + providerIdentifier.hashCode() - result = 31 * result + resultUrl.hashCode() - result = 31 * result + cms.first().contentHashCode() - return result - } - } - - data class EventProvider( - val name: String, - @Json(name = "identifier") val providerIdentifier: String, - @Json(name = "unomiUrl") val unomiUrl: String, - @Json(name = "eventUrl") val eventUrl: String, - val cms: List, - val tls: List, - val usage: List, - val auth: List - ) { - fun supports(originType: RemoteOriginType, loginType: LoginType): Boolean { - return when (originType) { - RemoteOriginType.Recovery -> usage.contains("r") && auth.contains(getAuthForLoginType(loginType)) - RemoteOriginType.Test -> (usage.contains("nt") || usage.contains("pt")) && auth.contains(getAuthForLoginType(loginType)) - RemoteOriginType.Vaccination -> usage.contains("v") && auth.contains(getAuthForLoginType(loginType)) - } - } - - private fun getAuthForLoginType(loginType: LoginType): String { - return when (loginType) { - is LoginType.Pap -> "pap" - is LoginType.Max -> "max" - } - } - - companion object { - const val PROVIDER_IDENTIFIER_DCC = "dcc" - const val PROVIDER_IDENTIFIER_DCC_SUFFIX = "dcc_[unique]" - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as EventProvider - - if (name != other.name) return false - if (providerIdentifier != other.providerIdentifier) return false - if (unomiUrl != other.unomiUrl) return false - if (eventUrl != other.eventUrl) return false - if (!cms.first().contentEquals(other.cms.first())) return false - if (!tls.first().contentEquals(other.tls.first())) return false - - return true - } - - override fun hashCode(): Int { - var result = name.hashCode() - result = 31 * result + providerIdentifier.hashCode() - result = 31 * result + unomiUrl.hashCode() - result = 31 * result + eventUrl.hashCode() - result = 31 * result + cms.first().contentHashCode() - result = 31 * result + tls.first().contentHashCode() - return result - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEvent.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEvent.kt deleted file mode 100644 index 405f45a72..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEvent.kt +++ /dev/null @@ -1,28 +0,0 @@ -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import java.time.OffsetDateTime - -abstract class RemoteEvent(open val unique: String?, open val type: String?) : Parcelable { - - companion object { - const val TYPE_VACCINATION = "vaccination" - const val TYPE_NEGATIVE_TEST = "negativetest" - const val TYPE_POSITIVE_TEST = "positivetest" - const val TYPE_RECOVERY = "recovery" - const val TYPE_TEST = "test" - - fun getRemoteEventClassFromType(type: String): Class { - return when (type) { - "positivetest" -> RemoteEventPositiveTest::class.java - "recovery" -> RemoteEventRecovery::class.java - "negativetest" -> RemoteEventNegativeTest::class.java - "test" -> RemoteEventNegativeTest::class.java - "vaccination" -> RemoteEventVaccination::class.java - else -> RemoteEvent::class.java - } - } - } - - abstract fun getDate(): OffsetDateTime? -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventNegativeTest.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventNegativeTest.kt deleted file mode 100644 index 65125d530..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventNegativeTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import java.time.OffsetDateTime -import kotlinx.parcelize.Parcelize - -@Parcelize -@JsonClass(generateAdapter = true) -data class RemoteEventNegativeTest( - override val type: String?, - override val unique: String?, - val isSpecimen: Boolean?, - @Json(name = "negativetest") val negativeTest: NegativeTest? -) : Parcelable, RemoteEvent(unique, type) { - - @Parcelize - @JsonClass(generateAdapter = true) - data class NegativeTest( - val sampleDate: OffsetDateTime?, - val negativeResult: Boolean?, - val facility: String?, - val type: String?, - val name: String?, - val country: String?, - val manufacturer: String? - ) : Parcelable - - override fun getDate(): OffsetDateTime? { - return negativeTest?.sampleDate - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventPositiveTest.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventPositiveTest.kt deleted file mode 100644 index 4cfa75ce6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventPositiveTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import java.time.OffsetDateTime -import kotlinx.parcelize.Parcelize - -@Parcelize -@JsonClass(generateAdapter = true) -data class RemoteEventPositiveTest( - override val type: String?, - override val unique: String?, - val isSpecimen: Boolean?, - @Json(name = "positivetest") val positiveTest: PositiveTest? -) : Parcelable, RemoteEvent(unique, type) { - - @Parcelize - @JsonClass(generateAdapter = true) - data class PositiveTest( - val sampleDate: OffsetDateTime?, - val positiveResult: Boolean?, - val facility: String?, - val type: String?, - val name: String?, - val country: String?, - val manufacturer: String? - ) : Parcelable - - override fun getDate(): OffsetDateTime? { - return positiveTest?.sampleDate - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventRecovery.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventRecovery.kt deleted file mode 100644 index 4deaa942c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventRecovery.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset -import kotlinx.parcelize.Parcelize - -@Parcelize -@JsonClass(generateAdapter = true) -data class RemoteEventRecovery( - override val type: String?, - override val unique: String, - val isSpecimen: Boolean, - @Json(name = "recovery") val recovery: Recovery? -) : Parcelable, RemoteEvent(unique, type) { - - @Parcelize - @JsonClass(generateAdapter = true) - data class Recovery( - val sampleDate: LocalDate?, - val validFrom: LocalDate?, - val validUntil: LocalDate?, - val country: String? - ) : Parcelable - - override fun getDate(): OffsetDateTime? { - return recovery?.sampleDate?.atStartOfDay()?.atOffset(ZoneOffset.UTC) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventVaccination.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventVaccination.kt deleted file mode 100644 index 07fab70b2..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteEventVaccination.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.ZoneOffset -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -@JsonClass(generateAdapter = true) -data class RemoteEventVaccination( - override val type: String?, - override val unique: String?, - @Json(name = "vaccination") val vaccination: Vaccination? -) : Parcelable, RemoteEvent(unique, type) { - - @Parcelize - @JsonClass(generateAdapter = true) - data class Vaccination( - val date: LocalDate?, - val hpkCode: String?, - val type: String?, - val brand: String?, - val completedByMedicalStatement: Boolean?, - val completedByPersonalStatement: Boolean?, - val completionReason: String?, - val doseNumber: String?, - val totalDoses: String?, - val country: String?, - val manufacturer: String? - ) : Parcelable - - override fun getDate(): OffsetDateTime? { - return vaccination?.date?.atStartOfDay()?.atOffset(ZoneOffset.UTC) - } - - override fun equals(other: Any?): Boolean { - val otherVaccination = (other as? RemoteEventVaccination)?.vaccination ?: return false - return this.vaccination?.date == otherVaccination.date && - ((this.vaccination?.hpkCode != null && otherVaccination.hpkCode != null && this.vaccination.hpkCode == otherVaccination.hpkCode) || - (this.vaccination?.manufacturer != null && otherVaccination.manufacturer != null && this.vaccination.manufacturer == otherVaccination.manufacturer)) - } - - override fun hashCode(): Int { - return vaccination?.date.hashCode() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteOriginType.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteOriginType.kt deleted file mode 100644 index ed4dfbc02..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteOriginType.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -/** - * Types of origins that we communicate with the backend. - * The difference with [OriginType] is that this is used for sending - * and [OriginType] for retrieving from backend and database - */ -sealed class RemoteOriginType : Parcelable { - - @Parcelize - object Vaccination : RemoteOriginType(), Parcelable - - @Parcelize - object Recovery : RemoteOriginType(), Parcelable - - @Parcelize - object Test : RemoteOriginType(), Parcelable - - fun toOriginType(): OriginType { - return when (this) { - is Vaccination -> OriginType.Vaccination - is Recovery -> OriginType.Recovery - is Test -> OriginType.Test - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteProtocol.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteProtocol.kt deleted file mode 100644 index 8e37de159..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteProtocol.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.models - -import android.os.Parcelable -import com.squareup.moshi.JsonClass -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Parcelize -data class RemoteProtocol( - val providerIdentifier: String, - val protocolVersion: String, - val status: Status, - val holder: Holder?, - val events: List? -) : Parcelable { - - @Parcelize - @JsonClass(generateAdapter = true) - data class Holder( - val infix: String?, - val firstName: String?, - val lastName: String?, - val birthDate: String? - ) : Parcelable - - fun hasEvents(): Boolean { - return events?.isNotEmpty() ?: false - } - - enum class Status(val apiStatus: String) { - UNKNOWN(""), - PENDING("pending"), - INVALID_TOKEN("invalid_token"), - VERIFICATION_REQUIRED("verification_required"), - RESULT_BLOCKED("result_blocked"), - COMPLETE("complete"); - - companion object { - fun fromValue(value: String?): Status { - return values().firstOrNull { it.apiStatus == value } ?: UNKNOWN - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteUnomi.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteUnomi.kt deleted file mode 100644 index 6039c8c8b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/models/RemoteUnomi.kt +++ /dev/null @@ -1,17 +0,0 @@ -package nl.rijksoverheid.ctr.holder.get_events.models - -import com.squareup.moshi.JsonClass - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class RemoteUnomi( - val protocolVersion: String, - val providerIdentifier: String, - val informationAvailable: Boolean -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/ConfigProvidersUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/ConfigProvidersUseCase.kt deleted file mode 100644 index a462c92cd..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/ConfigProvidersUseCase.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepository -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface ConfigProvidersUseCase { - suspend fun eventProviders(): EventProvidersResult - suspend fun eventProvidersBES(): EventProvidersResult - suspend fun testProviders(): TestProvidersResult -} - -class ConfigProvidersUseCaseImpl(private val coronaCheckRepository: CoronaCheckRepository) : - ConfigProvidersUseCase { - - override suspend fun testProviders(): TestProvidersResult { - return when (val result = coronaCheckRepository.configProviders()) { - is NetworkRequestResult.Success -> TestProvidersResult.Success( - result.response.testProviders - ) - is NetworkRequestResult.Failed -> TestProvidersResult.Error(result) - } - } - - override suspend fun eventProviders(): EventProvidersResult { - return when (val result = coronaCheckRepository.configProviders()) { - is NetworkRequestResult.Success -> { - EventProvidersResult.Success(result.response.eventProviders) - } - is NetworkRequestResult.Failed -> EventProvidersResult.Error(result) - } - } - - override suspend fun eventProvidersBES(): EventProvidersResult { - return when (val result = coronaCheckRepository.configProviders()) { - is NetworkRequestResult.Success -> { - EventProvidersResult.Success(result.response.eventProvidersBes) - } - is NetworkRequestResult.Failed -> EventProvidersResult.Error(result) - } - } -} - -sealed class TestProvidersResult { - class Success(val testProviders: List) : - TestProvidersResult() - - class Error(val errorResult: ErrorResult) : TestProvidersResult() -} - -sealed class EventProvidersResult { - class Success(val eventProviders: List) : - EventProvidersResult() - - class Error(val errorResult: ErrorResult) : EventProvidersResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetEventProvidersWithTokensUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetEventProvidersWithTokensUseCase.kt deleted file mode 100644 index 73b343363..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetEventProvidersWithTokensUseCase.kt +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import android.annotation.SuppressLint -import nl.rijksoverheid.ctr.holder.api.repositories.EventProviderRepository -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteAccessTokens -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteUnomi -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.ext.filterNotNullValues -import nl.rijksoverheid.ctr.shared.ext.parallelMap -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -/** - * Get all event providers that have events for the [OriginType] - */ -interface GetEventProvidersWithTokensUseCase { - - /** - * @param eventProviders A list of all the event providers - * @param tokens A list of all tokens - * @param filter events to filter by type - * @param scope optional parameter to specify the scope of the filter - * @param targetProviderIds used to filter on specific target providers - */ - suspend fun get( - eventProviders: List, - tokens: List, - filter: String, - scope: String?, - targetProviderIds: List? = null - ): List -} - -class GetEventProvidersWithTokensUseCaseImpl( - private val eventProviderRepository: EventProviderRepository -) : GetEventProvidersWithTokensUseCase { - - @SuppressLint("DefaultLocale") - override suspend fun get( - eventProviders: List, - tokens: List, - filter: String, - scope: String?, - targetProviderIds: List? - ): List { - - // Map event providers to tokens - val allEventProvidersWithTokens = - eventProviders - .associateWith { eventProvider -> - tokens - .firstOrNull { eventProvider.providerIdentifier == it.providerIdentifier } - } - .filterNotNullValues() - - // If we want to only target specific providers ids we filter others out - val targetEventProvidersWithTokens = allEventProvidersWithTokens.filter { - targetProviderIds?.contains(it.key.providerIdentifier.lowercase()) ?: true - } - - return targetEventProvidersWithTokens.toList().parallelMap { - val eventProvider = it.first - val token = it.second - - val unomiResult = eventProviderRepository.getUnomi( - url = eventProvider.unomiUrl, - token = token.unomi, - filter = filter, - scope = scope, - signingCertificateBytes = eventProvider.cms, - provider = eventProvider.providerIdentifier, - tlsCertificateBytes = eventProvider.tls - ) - - when (unomiResult) { - is NetworkRequestResult.Success -> { - if (unomiResult.response.informationAvailable) { - EventProviderWithTokenResult.Success( - eventProvider = eventProvider, - token = token - ) - } else { - null - } - } - is NetworkRequestResult.Failed -> EventProviderWithTokenResult.Error(unomiResult) - } - }.filterNotNull() - } -} - -sealed class EventProviderWithTokenResult { - data class Success( - val eventProvider: RemoteConfigProviders.EventProvider, - val token: RemoteAccessTokens.Token - ) : EventProviderWithTokenResult() - - data class Error(val errorResult: ErrorResult) : EventProviderWithTokenResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetEventsUseCase.kt deleted file mode 100644 index 7936b7f4e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetEventsUseCase.kt +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepository -import nl.rijksoverheid.ctr.holder.api.repositories.EventProviderRepository -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.EventsResult -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteAccessTokens -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.utils.ScopeUtil -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.ext.parallelMap -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -/** - * Get events for a specific [OriginType] - * This is the entry point class for getting Events and will take care of: - * - getting all event providers - * - getting tokens based on JWT - * - getting events at event providers - * - map result to success or error states - */ -interface GetEventsUseCase { - suspend fun getEvents( - eventProvidersResult: EventProvidersResult, - jwt: String, - originTypes: List, - loginType: LoginType - ): EventsResult -} - -class GetEventsUseCaseImpl( - private val coronaCheckRepository: CoronaCheckRepository, - private val getEventProvidersWithTokensUseCase: GetEventProvidersWithTokensUseCase, - private val getRemoteEventsUseCase: GetRemoteEventsUseCase, - private val scopeUtil: ScopeUtil -) : GetEventsUseCase { - - override suspend fun getEvents( - eventProvidersResult: EventProvidersResult, - jwt: String, - originTypes: List, - loginType: LoginType - ): EventsResult { - - val (tokens, remoteEventProviders) = when (eventProvidersResult) { - is EventProvidersResult.Error -> return EventsResult.Error(eventProvidersResult.errorResult) - is EventProvidersResult.Success -> { - when (loginType) { - is LoginType.Pap -> { - val fakeRemoteAccessTokens = RemoteAccessTokens( - tokens = eventProvidersResult.eventProviders.map { - RemoteAccessTokens.Token( - providerIdentifier = it.providerIdentifier, - unomi = jwt, - event = jwt - ) - } - ) - Pair( - fakeRemoteAccessTokens, - eventProvidersResult.eventProviders - ) - } - is LoginType.Max -> { - when (val tokensResult = coronaCheckRepository.accessTokens(jwt)) { - is NetworkRequestResult.Failed -> return EventsResult.Error(tokensResult) - is NetworkRequestResult.Success -> Pair( - tokensResult.response, - eventProvidersResult.eventProviders - ) - } - } - } - } - } - val eventProviders = eventProvidersResult.eventProviders - - val eventProviderWithTokensResults = - mutableMapOf>() - - val eventsResults = originTypes.parallelMap { originType -> - val targetProviderIds = eventProviders.filter { - it.supports(originType, loginType) - }.map { it.providerIdentifier.lowercase() } - - if (targetProviderIds.isEmpty()) { - return@parallelMap EventsResult.Error.noProvidersError(originType) - } - - val filter = EventProviderRepository.getFilter(originType) - - val scope = scopeUtil.getScopeForRemoteOriginType( - remoteOriginType = originType, - getPositiveTestWithVaccination = originTypes.size > 1 - ) - - // Fetch event providers that have events for us - eventProviderWithTokensResults[originType] = getEventProvidersWithTokensUseCase.get( - eventProviders = eventProviders, - tokens = tokens.tokens, - filter = filter, - scope = scope, - targetProviderIds = targetProviderIds - ) - } - - val eventsResultsErrors = eventsResults.filterIsInstance() - if (eventsResultsErrors.isNotEmpty()) { - return eventsResultsErrors.first() - } - - val eventProvidersWithTokensSuccessResults = - eventProviderWithTokensResults.mapValues { - it.value.filterIsInstance() - }.filterValues { it.isNotEmpty() } - val eventProvidersWithTokensErrorResults = - eventProviderWithTokensResults.values.flatten() - .filterIsInstance() - - return if (eventProvidersWithTokensSuccessResults.isNotEmpty()) { - val eventResults = mutableMapOf>() - eventProvidersWithTokensSuccessResults.forEach { (originType, eventProviders) -> - // We have received providers that claim to have events for us so we get those events for each provider - val events = eventProviders.map { - getRemoteEventsUseCase.getRemoteEvents( - eventProvider = it.eventProvider, - token = it.token, - filter = EventProviderRepository.getFilter(originType), - scope = scopeUtil.getScopeForRemoteOriginType( - remoteOriginType = originType, - getPositiveTestWithVaccination = originTypes.size > 1 - ) - ) - } - eventResults[originType] = events - } - - // All successful responses - val eventSuccessResults = - eventResults.mapValues { - it.value.filterIsInstance() - } - // All failed responses - val eventFailureResults = - eventResults.values.flatten().filterIsInstance() - - if (eventSuccessResults.flatMap { it.value }.isNotEmpty()) { - // If we have success responses - val signedModels = eventSuccessResults - .mapValues { events -> - events.value - .map { it.signedModel } - .filter { it.model.hasEvents() } - } - .filterValues { it.isNotEmpty() } - - if (signedModels.isEmpty()) { - // But we do not have any events - val missingEvents = - eventProvidersWithTokensErrorResults.isNotEmpty() || eventFailureResults.isNotEmpty() - val errorResults: List = if (missingEvents) { - eventProvidersWithTokensErrorResults.map { it.errorResult } + eventFailureResults.map { it.errorResult } - } else { - emptyList() - } - EventsResult.HasNoEvents( - missingEvents = missingEvents, - errorResults = errorResults - ) - } else { - // We do have events - EventsResult.Success( - remoteEvents = signedModels.map { - it.value.associate { signedModel -> - signedModel.model to signedModel.rawResponse - } - }.fold(mapOf()) { protocol, byteArray -> protocol + byteArray }, - missingEvents = eventProvidersWithTokensErrorResults.isNotEmpty() || eventFailureResults.isNotEmpty(), - eventProviders = remoteEventProviders.map { - EventProvider( - it.providerIdentifier, - it.name - ) - } - ) - } - } else { - // We don't have any successful responses from retrieving events for providers - EventsResult.Error(eventFailureResults.map { it.errorResult }) - } - } else { - if (eventProvidersWithTokensErrorResults.isEmpty()) { - // There are no successful responses and no error responses so no events - EventsResult.HasNoEvents(missingEvents = false) - } else { - // We don't have any successful responses but do have error responses - EventsResult.Error(eventProvidersWithTokensErrorResults.map { it.errorResult }) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetMijnCnEventsUsecase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetMijnCnEventsUsecase.kt deleted file mode 100644 index 51831a388..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetMijnCnEventsUsecase.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import nl.rijksoverheid.ctr.holder.api.repositories.EventProviderRepository -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.EventsResult -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteAccessTokens -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.utils.ScopeUtil -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.models.ErrorResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -/** - * Get events for a specific [OriginType] - * This is the entry point class for getting Events and will take care of: - * - getting all event providers - * - getting tokens based on JWT - * - getting events at event providers - * - map result to success or error states - */ -interface GetMijnCnEventsUsecase { - suspend fun getEvents( - jwt: String, - originType: RemoteOriginType, - withIncompleteVaccination: Boolean - ): EventsResult -} - -class GetMijnCnEventsUsecaseImpl( - private val configProvidersUseCase: ConfigProvidersUseCase, - private val getRemoteEventsUseCase: GetRemoteEventsUseCase, - private val scopeUtil: ScopeUtil -) : GetMijnCnEventsUsecase { - - override suspend fun getEvents( - jwt: String, - originType: RemoteOriginType, - withIncompleteVaccination: Boolean - ): EventsResult { - // Fetch event providers - val eventProvidersResult = configProvidersUseCase.eventProvidersBES() - when (eventProvidersResult) { - is EventProvidersResult.Error -> return EventsResult.Error(eventProvidersResult.errorResult) - is EventProvidersResult.Success -> { - eventProvidersResult.eventProviders - } - } - - val eventProviders = eventProvidersResult.eventProviders - val targetProviderIds = eventProviders.filter { - // TODO Support LoginType MijnCN - it.supports(originType, LoginType.Max) - }.map { it.providerIdentifier.lowercase() } - - if (targetProviderIds.isEmpty()) { - return EventsResult.Error.noProvidersError(originType) - } - - return if (eventProviders.isNotEmpty()) { - // We have received providers that claim to have events for us so we get those events for each provider - val filter = EventProviderRepository.getFilter(originType) - val scope = scopeUtil.getScopeForRemoteOriginType( - remoteOriginType = originType, - getPositiveTestWithVaccination = withIncompleteVaccination - ) - - val eventResults = eventProviders.map { eventProvider -> - getRemoteEventsUseCase.getRemoteEvents( - eventProvider = eventProvider, - token = RemoteAccessTokens.Token( - providerIdentifier = eventProvider.providerIdentifier, - unomi = "", - event = jwt - ), - filter = filter, - scope = scope - ) - } - - // All successful responses - val eventSuccessResults = - eventResults.filterIsInstance() - - // All failed responses - val eventFailureResults = - eventResults.filterIsInstance() - - if (eventSuccessResults.isNotEmpty()) { - // If we have success responses - val signedModels = eventSuccessResults.map { it.signedModel } - val allEvents = signedModels.map { it.model }.mapNotNull { it.events }.flatten() - val hasEvents = allEvents.isNotEmpty() - - if (!hasEvents) { - // But we do not have any events - val missingEvents = eventFailureResults.isNotEmpty() - val errorResults: List = if (missingEvents) { - eventFailureResults.map { it.errorResult } - } else { - emptyList() - } - EventsResult.HasNoEvents( - missingEvents = missingEvents, - errorResults = errorResults - ) - } else { - // We do have events - EventsResult.Success( - remoteEvents = signedModels.associate { signedModel -> - signedModel.model to signedModel.rawResponse - }, - missingEvents = eventFailureResults.isNotEmpty(), - eventProviders = eventProviders.map { - EventProvider( - it.providerIdentifier, - it.name - ) - } - ) - } - } else { - // We don't have any successful responses from retrieving events for providers - EventsResult.Error(eventFailureResults.map { it.errorResult }) - } - } else { - if (eventProviders.isEmpty()) { - // There are no successful responses and no error responses so no events - EventsResult.HasNoEvents(missingEvents = false) - } else { - // We don't have any successful responses but do have error responses - EventsResult.Error((eventProvidersResult as EventProvidersResult.Error).errorResult) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetRemoteEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetRemoteEventsUseCase.kt deleted file mode 100644 index 1284e7102..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetRemoteEventsUseCase.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import nl.rijksoverheid.ctr.holder.api.models.SignedResponseWithModel -import nl.rijksoverheid.ctr.holder.api.repositories.EventProviderRepository -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteAccessTokens -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/** - * Get events for a event provider - */ -interface GetRemoteEventsUseCase { - suspend fun getRemoteEvents( - eventProvider: RemoteConfigProviders.EventProvider, - filter: String, - scope: String?, - token: RemoteAccessTokens.Token - ): RemoteEventsResult -} - -class GetRemoteEventsUseCaseImpl(private val eventProviderRepository: EventProviderRepository) : - GetRemoteEventsUseCase { - - override suspend fun getRemoteEvents( - eventProvider: RemoteConfigProviders.EventProvider, - filter: String, - scope: String?, - token: RemoteAccessTokens.Token - ): RemoteEventsResult { - - return when (val eventsResult = eventProviderRepository - .getEvents( - url = eventProvider.eventUrl, - token = token.event, - filter = filter, - scope = scope, - signingCertificateBytes = eventProvider.cms, - provider = eventProvider.providerIdentifier, - tlsCertificateBytes = eventProvider.tls - )) { - - is NetworkRequestResult.Success> -> - RemoteEventsResult.Success(eventsResult.response) - is NetworkRequestResult.Failed -> { - RemoteEventsResult.Error(eventsResult) - } - } - } -} - -sealed class RemoteEventsResult { - data class Success(val signedModel: SignedResponseWithModel) : - RemoteEventsResult() - - data class Error(val errorResult: ErrorResult) : RemoteEventsResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetRemoteProtocolFromEventGroupUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetRemoteProtocolFromEventGroupUseCase.kt deleted file mode 100644 index 58469a974..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/GetRemoteProtocolFromEventGroupUseCase.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetEventsFromPaperProofQrUseCase -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import org.json.JSONObject - -interface GetRemoteProtocolFromEventGroupUseCase { - fun get(eventGroup: EventGroupEntity): RemoteProtocol? -} - -class GetRemoteProtocolFromEventGroupUseCaseImpl( - private val remoteEventUtil: RemoteEventUtil, - private val getEventsFromPaperProofQrUseCase: GetEventsFromPaperProofQrUseCase -) : GetRemoteProtocolFromEventGroupUseCase { - - override fun get(eventGroup: EventGroupEntity): RemoteProtocol? { - val isDccEvent = remoteEventUtil.isDccEvent( - providerIdentifier = eventGroup.providerIdentifier - ) - - return if (isDccEvent) { - val credential = - JSONObject(eventGroup.jsonData.decodeToString()).getString("credential") - getEventsFromPaperProofQrUseCase.get(credential) - } else { - remoteEventUtil.getRemoteProtocol3FromNonDcc( - eventGroupEntity = eventGroup - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/PersistBlockedEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/PersistBlockedEventsUseCase.kt deleted file mode 100644 index 581a886f6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/usecases/PersistBlockedEventsUseCase.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.usecases - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason - -interface PersistBlockedEventsUseCase { - suspend fun persist( - newEvents: List, - removedEvents: List, - reason: RemovedEventReason - ) -} - -class PersistBlockedEventsUseCaseImpl( - private val holderDatabase: HolderDatabase -) : PersistBlockedEventsUseCase { - - override suspend fun persist( - newEvents: List, - removedEvents: List, - reason: RemovedEventReason - ) { - val eventsToPersist = removedEvents.filter { !newEvents.contains(it) } - eventsToPersist.forEach { remoteEvent -> - val removedEventEntity = RemovedEventEntity( - walletId = 1, - type = remoteEvent.type ?: "", - eventTime = remoteEvent.getDate(), - reason = reason - ) - - holderDatabase.removedEventDao().insert(removedEventEntity) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/utils/LoginTypeUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/utils/LoginTypeUtil.kt deleted file mode 100644 index 545c4b1f8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/utils/LoginTypeUtil.kt +++ /dev/null @@ -1,53 +0,0 @@ -package nl.rijksoverheid.ctr.holder.get_events.utils - -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface LoginTypeUtil { - @StringRes - fun getCanceledDialogTitle(loginType: LoginType): Int - @StringRes - fun getCanceledDialogDescription(loginType: LoginType, originType: RemoteOriginType): Int - @StringRes - fun getNoBrowserDialogDescription(loginType: LoginType): Int -} - -class LoginTypeUtilImpl : LoginTypeUtil { - override fun getCanceledDialogTitle(loginType: LoginType): Int { - return when (loginType) { - LoginType.Max -> R.string.holder_authentication_popup_digid_title - LoginType.Pap -> R.string.holder_authentication_popup_portal_title - } - } - - override fun getCanceledDialogDescription(loginType: LoginType, originType: RemoteOriginType): Int { - return when (loginType) { - LoginType.Max -> if (originType == RemoteOriginType.Vaccination) { - R.string.holder_authentication_popup_digid_message_vaccinationFlow - } else { - R.string.holder_authentication_popup_digid_message_testFlow - } - LoginType.Pap -> if (originType == RemoteOriginType.Vaccination) { - R.string.holder_authentication_popup_portal_message_vaccinationFlow - } else { - R.string.holder_authentication_popup_portal_message_testFlow - } - } - } - - override fun getNoBrowserDialogDescription(loginType: LoginType): Int { - return when (loginType) { - LoginType.Max -> R.string.holder_authentication_popup_digid - LoginType.Pap -> R.string.holder_authentication_popup_portal - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/utils/ScopeUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/utils/ScopeUtil.kt deleted file mode 100644 index 62b4d64fe..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/get_events/utils/ScopeUtil.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.get_events.utils - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -interface ScopeUtil { - - fun getScopeForRemoteOriginType( - remoteOriginType: RemoteOriginType, - getPositiveTestWithVaccination: Boolean - ): String - - fun getScopeForOriginType( - originType: OriginType, - getPositiveTestWithVaccination: Boolean - ): String -} - -class ScopeUtilImpl : ScopeUtil { - - override fun getScopeForRemoteOriginType( - remoteOriginType: RemoteOriginType, - getPositiveTestWithVaccination: Boolean - ): String { - return getScopeForOriginType( - originType = remoteOriginType.toOriginType(), - getPositiveTestWithVaccination = getPositiveTestWithVaccination - ) - } - - override fun getScopeForOriginType( - originType: OriginType, - getPositiveTestWithVaccination: Boolean - ): String { - return if (originType is OriginType.Recovery) { - return if (getPositiveTestWithVaccination) { - "firstepisode" - } else { - "recovery" - } - } else { - "" - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/CommercialTestInputTokenFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/CommercialTestInputTokenFragment.kt deleted file mode 100644 index f7f7d81c2..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/CommercialTestInputTokenFragment.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.input_token - -import androidx.navigation.fragment.findNavController -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.input_token.usecases.TestResult -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.your_events.YourEventsFragmentType -import nl.rijksoverheid.ctr.shared.models.Flow - -class CommercialTestInputTokenFragment : InputTokenFragment() { - - private val args: CommercialTestInputTokenFragmentArgs by navArgs() - - override fun getFlow(): Flow { - return HolderFlow.CommercialTest - } - - override fun getFragmentData(): InputTokenFragmentData { - return InputTokenFragmentData.CommercialTest - } - - override fun navigateCouldNotCreateQr() { - CommercialTestInputTokenFragmentDirections.actionCouldNotCreateQr( - toolbarTitle = getString(getFragmentData().noResultScreenToolbarTitle), - title = getString(getFragmentData().noResultScreenTitle), - description = getString(getFragmentData().noResultScreenDescription), - buttonTitle = getString(R.string.back_to_overview) - ) - } - - override fun navigateMyEvents(result: TestResult.NegativeTestResult) { - findNavController().navigate( - CommercialTestInputTokenFragmentDirections.actionYourEvents( - type = YourEventsFragmentType.RemoteProtocol3Type( - remoteEvents = mapOf(result.remoteTestResult to result.signedResponseWithTestResult.rawResponse) - ), - flow = HolderFlow.CommercialTest, - toolbarTitle = getString(R.string.your_negative_test_results_toolbar) - ) - ) - } - - override fun getDeeplinkToken(): String? { - return args.token - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenFragment.kt deleted file mode 100644 index 81e45756a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenFragment.kt +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.input_token - -import android.os.Bundle -import android.text.InputFilter -import android.view.View -import android.view.inputmethod.EditorInfo -import androidx.annotation.StringRes -import androidx.core.view.isVisible -import androidx.core.widget.addTextChangedListener -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.BaseFragment -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentInputTokenBinding -import nl.rijksoverheid.ctr.holder.input_token.usecases.TestResult -import nl.rijksoverheid.ctr.shared.ext.hideKeyboard -import nl.rijksoverheid.ctr.shared.ext.showKeyboard -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.utils.Accessibility -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.stateViewModel -import org.koin.androidx.viewmodel.scope.emptyState - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class InputTokenFragment : BaseFragment(R.layout.fragment_input_token) { - - private var _binding: FragmentInputTokenBinding? = null - private val binding get() = _binding!! - private val viewModel: InputTokenViewModel by stateViewModel( - state = emptyState() - ) - - private val dialogUtil: DialogUtil by inject() - private val infoFragmentUtil: InfoFragmentUtil by inject() - - override fun onButtonClickWithRetryAction() { - fetchTestResults(binding) - } - - private fun setCopies() { - val data = getFragmentData() - - binding.description.text = getString(data.description) - binding.uniqueCodeInput.hint = getString(data.uniqueCodeInputHeader) - binding.noTokenReceivedBtn.text = getString(data.noCodeText) - binding.noTokenReceivedBtn.contentDescription = getString(data.noCodeText) - binding.bottom.setButtonText(getString(data.buttonText)) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - _binding = FragmentInputTokenBinding.bind(view) - - binding.uniqueCodeText.filters = arrayOf(InputFilter.AllCaps()) - binding.uniqueCodeText.addTextChangedListener { - viewModel.testCode = it?.toString()?.uppercase() ?: "" - } - - binding.verificationCodeText.addTextChangedListener { - if (binding.verificationCodeInput.isVisible) { - viewModel.verificationCode = it.toString().takeIf { it.isNotEmpty() } - } - } - - setCopies() - - viewModel.viewState.observe(viewLifecycleOwner) { - binding.uniqueCodeText.imeOptions = - (if (it.verificationRequired) EditorInfo.IME_ACTION_NEXT else EditorInfo.IME_ACTION_SEND) - binding.verificationCodeInput.visibility = - if (it.verificationRequired) View.VISIBLE else View.GONE - binding.noVerificationRecievedBtn.visibility = - if (it.verificationRequired) View.VISIBLE else View.GONE - binding.noTokenReceivedBtn.visibility = - if (!it.verificationRequired) View.VISIBLE else View.GONE - - // Start with empty text for possible empty field error when field is visible - if (it.verificationRequired && viewModel.verificationCode == null) { - viewModel.verificationCode = "" - } - - if (it.fromDeeplink && it.verificationRequired) { - binding.uniqueCodeInput.isVisible = false - binding.noTokenReceivedBtn.isVisible = false - binding.description.setText(getFragmentData().descriptionDeeplink) - } - - binding.uniqueCodeText.setHint( - if (Accessibility.screenReader(context)) { - getFragmentData().uniqueCodeInputHintScreenReader - } else { - getFragmentData().uniqueCodeInputHint - } - ) - } - - binding.uniqueCodeText.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_SEND && !viewModel.verificationRequired) { - fetchTestResults(binding) - true - } else { - false - } - } - - viewModel.testResult.observe(viewLifecycleOwner, EventObserver { - when (it) { - is TestResult.EmptyToken -> showTokenError(getFragmentData().noUniqueCodeEntered) - is TestResult.InvalidToken -> showTokenError(getFragmentData().invalidTokenText) - is TestResult.ResultBlocked -> showTokenError(R.string.holder_inputRetrievalCode_error_blocked) - is TestResult.UnknownTestProvider -> showTokenError(R.string.commercial_test_error_unknown_test_provider) - is TestResult.NegativeTestResult -> showNegativeTestResult(it) - is TestResult.NoNegativeTestResult -> { - navigateCouldNotCreateQr() - } - is TestResult.Pending -> { - navigateCouldNotCreateQr() - } - is TestResult.VerificationRequired -> { - // If we come here a second time, it means the inputted verification code is not valid - if (binding.verificationCodeText.text?.isNotEmpty() == true) { - binding.verificationCodeInput.error = getString(R.string.commercial_test_error_invalid_combination) - } - binding.verificationCodeInput.requestFocus() - } - is TestResult.EmptyVerificationCode -> { - binding.verificationCodeInput.error = getString(R.string.commercial_test_error_empty_verification_code) - } - is TestResult.Error -> { - presentError(it.errorResult) - } - } - }) - - // Show dialog to send verification code again - binding.noVerificationRecievedBtn.setOnClickListener { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.dialog_verification_code_title, - message = getString(R.string.dialog_verification_code_message), - positiveButtonText = R.string.dialog_verification_code_positive_button, - positiveButtonCallback = { - viewModel.sendVerificationCode() - }, - negativeButtonText = R.string.dialog_close - ) - } - - binding.bottom.setButtonClick { - onButtonClickWithRetryAction() - } - - // If a location token is set, automatically fill it in. Else we show the keyboard focussing on first code input field - getDeeplinkToken()?.let { token -> - // Only run this once. If the token has been handled once don't try to retrieve a testresult automatically again. - if (viewModel.testCode.isEmpty()) { - binding.uniqueCodeText.setText(token) - fetchTestResults(binding, fromDeeplink = true) - } - } ?: showKeyboard(binding.uniqueCodeText) - - viewModel.loading.observe(viewLifecycleOwner, EventObserver { - if (!viewModel.fromDeeplink) { - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(it) - } else { - // Show different loading state when loading from deeplink - binding.loadingOverlay.isVisible = it - } - }) - - binding.noTokenReceivedBtn.setOnClickListener { - infoFragmentUtil.presentAsBottomSheet(childFragmentManager, InfoFragmentData.TitleDescription( - title = getString(getFragmentData().noCodeDialogTitle), - descriptionData = DescriptionData( - htmlText = getFragmentData().noCodeDialogDescription, - htmlLinksEnabled = true - ) - )) - } - } - - private fun showNegativeTestResult(result: TestResult.NegativeTestResult) { - navigateMyEvents(result) - } - - private fun showTokenError(@StringRes errorMessageRes: Int) { - binding.uniqueCodeInput.error = getString(errorMessageRes) - binding.verificationCodeInput.isVisible = false - } - - override fun onPause() { - super.onPause() - hideKeyboard() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - private fun fetchTestResults( - binding: FragmentInputTokenBinding, - fromDeeplink: Boolean = false - ) { - binding.verificationCodeInput.error = null - binding.uniqueCodeInput.error = null - viewModel.getTestResult(fromDeeplink) - } - - abstract fun getFragmentData(): InputTokenFragmentData - abstract fun navigateCouldNotCreateQr() - abstract fun navigateMyEvents(result: TestResult.NegativeTestResult) - abstract fun getDeeplinkToken(): String? -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenFragmentData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenFragmentData.kt deleted file mode 100644 index b55cc5931..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenFragmentData.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.input_token - -import android.os.Parcelable -import androidx.annotation.StringRes -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.holder.R - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -sealed class InputTokenFragmentData( - @StringRes val description: Int, - @StringRes val descriptionDeeplink: Int, - @StringRes val uniqueCodeInputHeader: Int, - @StringRes val uniqueCodeInputHint: Int, - @StringRes val uniqueCodeInputHintScreenReader: Int, - @StringRes val noUniqueCodeEntered: Int, - @StringRes val noCodeText: Int, - @StringRes val noCodeDialogTitle: Int, - @StringRes val noCodeDialogDescription: Int, - @StringRes val noResultScreenToolbarTitle: Int, - @StringRes val noResultScreenTitle: Int, - @StringRes val noResultScreenDescription: Int, - @StringRes val invalidTokenText: Int, - @StringRes val buttonText: Int -) : Parcelable { - @Parcelize - object CommercialTest : InputTokenFragmentData( - description = R.string.commercial_test_code_description, - descriptionDeeplink = R.string.commercial_test_verification_code_description_deeplink, - uniqueCodeInputHeader = R.string.commercial_test_unique_code_header, - uniqueCodeInputHint = R.string.commercial_test_unique_code_hint, - uniqueCodeInputHintScreenReader = R.string.commercial_test_unique_code_hint_screenreader, - noUniqueCodeEntered = R.string.commercial_test_error_empty_retrieval_code, - noCodeText = R.string.commercial_test_type_no_code_title, - noCodeDialogTitle = R.string.commercial_test_type_no_code_title, - noCodeDialogDescription = R.string.commercial_test_type_no_code_description, - noResultScreenToolbarTitle = R.string.commercial_test_type_title, - noResultScreenTitle = R.string.no_negative_test_result_title, - noResultScreenDescription = R.string.no_negative_test_result_description, - invalidTokenText = R.string.commercial_test_error_invalid_code, - buttonText = R.string.commercial_test_button - ) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenViewModel.kt deleted file mode 100644 index f4f70043f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/InputTokenViewModel.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.input_token - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.input_token.usecases.TestResult -import nl.rijksoverheid.ctr.holder.input_token.usecases.TestResultUseCase -import nl.rijksoverheid.ctr.shared.livedata.Event - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -abstract class InputTokenViewModel : ViewModel() { - abstract fun updateViewState() - abstract fun getTestResult(fromDeeplink: Boolean = false) - abstract fun sendVerificationCode() - - open var verificationCode: String? = null - open var verificationRequired: Boolean = false - open var testCode: String = "" - open var fromDeeplink: Boolean = false - - val testResult: LiveData> = MutableLiveData() - val loading: LiveData> = MutableLiveData() - val viewState: LiveData = MutableLiveData(ViewState()) -} - -class InputTokenViewModelImpl( - private val savedStateHandle: SavedStateHandle, - private val testResultUseCase: TestResultUseCase -) : InputTokenViewModel() { - - override var verificationCode: String? = savedStateHandle["verification_code"] - set(value) { - field = value - savedStateHandle["verification_code"] = value - updateViewState() - } - - override var verificationRequired: Boolean = savedStateHandle["verification_required"] ?: false - set(value) { - field = value - savedStateHandle["verification_required"] = value - updateViewState() - } - - override var testCode: String = savedStateHandle["test_code"] ?: "" - set(value) { - field = value - savedStateHandle["test_code"] = value - updateViewState() - } - - override var fromDeeplink: Boolean = savedStateHandle["from_deeplink"] ?: false - set(value) { - field = value - savedStateHandle["from_deeplink"] = value - updateViewState() - } - - private val currentViewState: ViewState - get() = viewState.value!! - - init { - updateViewState() - } - - override fun updateViewState() { - (viewState as MutableLiveData).value = currentViewState.copy( - verificationRequired = verificationRequired, - fromDeeplink = fromDeeplink - ) - } - - override fun getTestResult(fromDeeplink: Boolean) { - this.fromDeeplink = fromDeeplink - (loading as MutableLiveData).value = Event(true) - viewModelScope.launch { - try { - val result = testResultUseCase.testResult(testCode, verificationCode) - if (result == TestResult.VerificationRequired) { - verificationRequired = true - } - (testResult as MutableLiveData).value = Event(result) - } finally { - loading.value = Event(false) - } - } - } - - override fun sendVerificationCode() { - viewModelScope.launch { - val result = testResultUseCase.testResult(testCode, "") - - // Only notify the UI of errors, since this is just about resending a sms verification on the backend - if (result is TestResult.Error) { - (testResult as MutableLiveData).value = Event(result) - } - } - } -} - -data class ViewState( - val verificationRequired: Boolean = false, - val fromDeeplink: Boolean = false -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/usecases/TestResultUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/usecases/TestResultUseCase.kt deleted file mode 100644 index 9fd0c2903..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/input_token/usecases/TestResultUseCase.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.input_token.usecases - -import nl.rijksoverheid.ctr.holder.api.models.SignedResponseWithModel -import nl.rijksoverheid.ctr.holder.api.repositories.TestProviderRepository -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.get_events.usecases.ConfigProvidersUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.TestProvidersResult -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.shared.ext.removeWhitespace -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import nl.rijksoverheid.rdo.modules.luhncheck.TokenValidator -import nl.rijksoverheid.rdo.modules.luhncheck.TokenValidatorImpl - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class TestResultUseCase( - private val configProviderUseCase: ConfigProvidersUseCase, - private val testProviderRepository: TestProviderRepository, - private val tokenValidatorUtil: TokenValidator, - private val configUseCase: HolderCachedAppConfigUseCase -) { - - suspend fun testResult(uniqueCode: String, verificationCode: String? = null): TestResult { - try { - if (uniqueCode.isEmpty()) { - return TestResult.EmptyToken - } - - if (uniqueCode.indexOf("-") == -1) { - return TestResult.InvalidToken - } - - val uniqueCodeAttributes = uniqueCode.split("-") - - if (uniqueCodeAttributes.size != 3) { - return TestResult.InvalidToken - } - - val providerIdentifier = uniqueCodeAttributes[0] - val token = uniqueCodeAttributes[1] - val checksum = uniqueCodeAttributes[2] - - // We need to check for valid chars - token.toCharArray().forEach { - if (!TokenValidatorImpl.CODE_POINTS.contains(it)) { - return TestResult.InvalidToken - } - } - - // Enable the luhn check based on the current config value - if (configUseCase.getCachedAppConfig().luhnCheckEnabled) { - if (!tokenValidatorUtil.validate(token = token, checksum = checksum)) { - return TestResult.InvalidToken - } - } - - val testProvider = - when (val testProvidersResult = configProviderUseCase.testProviders()) { - is TestProvidersResult.Success -> { - testProvidersResult.testProviders - .firstOrNull { it.providerIdentifier == providerIdentifier } - ?: return TestResult.UnknownTestProvider - } - is TestProvidersResult.Error -> { - return TestResult.Error(testProvidersResult.errorResult) - } - } - - if (verificationCode != null && verificationCode.isEmpty()) { - return TestResult.EmptyVerificationCode - } - - val signedResponseWithTestResultRequestResult = testProviderRepository.remoteTestResult( - url = testProvider.resultUrl, - token = token.removeWhitespace(), - verifierCode = verificationCode?.removeWhitespace() ?: "", - signingCertificateBytes = testProvider.cms, - provider = providerIdentifier, - tlsCertificateBytes = testProvider.tls - ) - - val signedResponseWithTestResult = when (signedResponseWithTestResultRequestResult) { - is NetworkRequestResult.Success -> { - signedResponseWithTestResultRequestResult.response - } - is NetworkRequestResult.Failed -> { - return TestResult.Error(signedResponseWithTestResultRequestResult) - } - } - - val remoteTestResult = signedResponseWithTestResult.model - - when (remoteTestResult.status) { - RemoteProtocol.Status.VERIFICATION_REQUIRED -> return TestResult.VerificationRequired - RemoteProtocol.Status.INVALID_TOKEN -> return TestResult.InvalidToken - RemoteProtocol.Status.PENDING -> return TestResult.Pending - RemoteProtocol.Status.RESULT_BLOCKED -> return TestResult.ResultBlocked - RemoteProtocol.Status.COMPLETE -> { - if (!remoteTestResult.hasEvents()) { - return TestResult.NoNegativeTestResult - } - - return TestResult.NegativeTestResult( - remoteTestResult, - signedResponseWithTestResult - ) - } - else -> throw IllegalStateException("Unsupported status ${remoteTestResult.status}") - } - } catch (e: Exception) { - return TestResult.Error( - errorResult = AppErrorResult( - step = HolderStep.TestResultNetworkRequest, - e = e - ) - ) - } - } -} - -sealed class TestResult { - object NoNegativeTestResult : TestResult() - data class NegativeTestResult( - val remoteTestResult: RemoteProtocol, - val signedResponseWithTestResult: SignedResponseWithModel - ) : TestResult() - - object Pending : TestResult() - object EmptyToken : TestResult() - object InvalidToken : TestResult() - object UnknownTestProvider : TestResult() - object EmptyVerificationCode : TestResult() - object VerificationRequired : TestResult() - object ResultBlocked : TestResult() - data class Error(val errorResult: ErrorResult) : TestResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/AboutThisAppDataModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/AboutThisAppDataModel.kt deleted file mode 100644 index 4c749418c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/AboutThisAppDataModel.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.menu - -import android.content.Context -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.fragments.menu.MenuFragmentDirections -import nl.rijksoverheid.ctr.design.menu.about.AboutThisAppData -import nl.rijksoverheid.ctr.design.utils.DialogButtonData -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase - -interface AboutThisAppDataModel { - fun get(context: Context): AboutThisAppData -} - -class AboutThisAppDataModelImpl( - private val cachedAppConfigUseCase: CachedAppConfigUseCase, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val appConfigPersistenceManager: AppConfigPersistenceManager -) : AboutThisAppDataModel { - override fun get(context: Context): AboutThisAppData { - - val dialogDirection = MenuFragmentDirections.actionDialog( - data = DialogFragmentData( - title = nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_title, - message = nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_description, - positiveButtonData = DialogButtonData.ResetApp( - textId = nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_confirm - ), - negativeButtonData = DialogButtonData.Dismiss(nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_cancel) - ) - ) - return AboutThisAppData( - description = if (featureFlagUseCase.isInArchiveMode()) { - context.getString(R.string.holder_aboutThisApp_archiveMode_description) - } else { - context.getString(R.string.about_this_app_description) - }, - deeplinkScannerUrl = BuildConfig.DEEPLINK_SCANNER_TEST_URL, - versionName = BuildConfig.VERSION_NAME, - versionCode = BuildConfig.VERSION_CODE.toString(), - resetAppDialogDirection = AboutThisAppData.Destination( - text = context.getString(R.string.about_this_app_clear_data_confirm), - dialogDirection.actionId, - dialogDirection.arguments - ), - sections = listOf( - AboutThisAppData.AboutThisAppSection( - header = R.string.about_this_app_read_more, - items = mutableListOf( - AboutThisAppData.Url( - text = context.getString(R.string.privacy_statement), - url = context.getString(R.string.url_privacy_statement) - ), - AboutThisAppData.Url( - text = context.getString(R.string.about_this_app_accessibility), - url = context.getString(R.string.url_accessibility) - ), - AboutThisAppData.Url( - text = context.getString(R.string.about_this_app_colofon), - url = context.getString(R.string.about_this_app_colofon_url) - ) - ) - ) - ), - configVersionHash = cachedAppConfigUseCase.getCachedAppConfigHash(), - configVersionTimestamp = appConfigPersistenceManager.getAppConfigLastFetchedSeconds() - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/HelpMenuDataModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/HelpMenuDataModel.kt deleted file mode 100644 index 150dcd049..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/HelpMenuDataModel.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.menu - -import android.content.Context -import java.time.DayOfWeek -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset -import java.time.format.TextStyle -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYearTimeNumerical -import nl.rijksoverheid.ctr.design.fragments.menu.MenuFragmentDirections -import nl.rijksoverheid.ctr.design.fragments.menu.MenuSection -import nl.rijksoverheid.ctr.design.menu.about.HelpdeskData -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.shared.ext.locale - -interface HelpMenuDataModel { - fun get(context: Context): Array -} - -class HelpMenuDataModelImpl( - private val aboutThisAppDataModel: AboutThisAppDataModel, - private val cachedAppConfigUseCase: CachedAppConfigUseCase, - private val appConfigPersistenceManager: AppConfigPersistenceManager -) : HelpMenuDataModel { - - override fun get(context: Context): Array { - val configFetchDate = OffsetDateTime.ofInstant( - Instant.ofEpochSecond(appConfigPersistenceManager.getAppConfigLastFetchedSeconds()), - ZoneOffset.UTC - ).formatDayMonthYearTimeNumerical() - val contactInformation = cachedAppConfigUseCase.getCachedAppConfig().contactInfo - val startDay = DayOfWeek.of(contactInformation.startDay).getDisplayName(TextStyle.FULL, context.locale()) - val endDay = DayOfWeek.of(contactInformation.endDay).getDisplayName(TextStyle.FULL, context.locale()) - val actionHelpdesk = MenuFragmentDirections.actionHelpdesk( - data = HelpdeskData( - contactTitle = context.getString(R.string.holder_helpdesk_contact_title), - contactMessageLines = listOf( - context.getString( - R.string.holder_helpdesk_contact_message_line1, - contactInformation.phoneNumber, - contactInformation.phoneNumber - ), - context.getString( - R.string.holder_helpdesk_contact_message_line2, - contactInformation.phoneNumberAbroad, - contactInformation.phoneNumberAbroad - ), - context.getString( - R.string.holder_helpdesk_contact_message_line3, - startDay, - endDay, - contactInformation.startHour, - contactInformation.endHour - ) - ), - supportTitle = context.getString(R.string.holder_helpdesk_support_title), - supportMessage = context.getString(R.string.holder_helpdesk_support_message), - appVersionTitle = context.getString(R.string.holder_helpdesk_appVersion), - appVersion = "${BuildConfig.VERSION_NAME} (build ${BuildConfig.VERSION_CODE})", - configurationTitle = context.getString(R.string.holder_helpdesk_configuration), - configuration = "${cachedAppConfigUseCase.getCachedAppConfigHash()}, $configFetchDate" - ) - ) - - val aboutThisAppAction = MenuFragmentDirections.actionAboutThisApp( - data = aboutThisAppDataModel.get(context) - ) - return listOf( - MenuSection( - menuItems = listOf( - MenuSection.MenuItem( - icon = R.drawable.ic_menu_chatbubble, - title = R.string.frequently_asked_questions, - onClick = MenuSection.MenuItem.OnClick.OpenBrowser( - url = context.getString(R.string.url_faq) - ) - ), - MenuSection.MenuItem( - icon = R.drawable.ic_menu_helpdesk, - title = R.string.holder_helpInfo_helpdesk, - onClick = MenuSection.MenuItem.OnClick.Navigate( - navigationActionId = actionHelpdesk.actionId, - navigationArguments = actionHelpdesk.arguments - ) - ) - ) - ), - MenuSection( - menuItems = listOf( - MenuSection.MenuItem( - icon = R.drawable.ic_menu_smartphone, - title = R.string.about_this_app, - onClick = MenuSection.MenuItem.OnClick.Navigate( - navigationActionId = aboutThisAppAction.actionId, - navigationArguments = aboutThisAppAction.arguments - ) - ) - ) - ) - ).toTypedArray() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/MenuViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/MenuViewModel.kt deleted file mode 100644 index 46fc04f52..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/menu/MenuViewModel.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.menu - -import android.content.Context -import androidx.lifecycle.MutableLiveData -import nl.rijksoverheid.ctr.design.fragments.menu.MenuFragmentDirections -import nl.rijksoverheid.ctr.design.fragments.menu.MenuSection -import nl.rijksoverheid.ctr.design.menu.MenuViewModel -import nl.rijksoverheid.ctr.design.utils.DialogButtonData -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.shared.livedata.Event -import nl.rijksoverheid.ctr.shared.models.Environment - -class MenuViewModelImpl( - private val helpMenuDataModel: HelpMenuDataModel, - private val featureFlagUseCase: HolderFeatureFlagUseCase -) : MenuViewModel() { - - override fun click(context: Context) { - (menuSectionLiveData as MutableLiveData).value = Event(menuSections(context)) - } - - private fun menuSections(context: Context): Array { - val actionExportIntroduction = MenuFragmentDirections.actionExportIntroduction() - val actionChooseProofType = MenuFragmentDirections.actionChooseProofType() - val actionSavedEvents = MenuFragmentDirections.actionSavedEvents() - val actionHelpInfo = MenuFragmentDirections.actionMenu( - toolbarTitle = context.getString(R.string.holder_helpInfo_title), - menuSections = helpMenuDataModel.get(context) - ) - - val exportPdfMenuItem = MenuSection.MenuItem( - icon = R.drawable.ic_menu_export_pdf, - title = R.string.holder_menu_exportPDF, - onClick = MenuSection.MenuItem.OnClick.Navigate( - navigationActionId = actionExportIntroduction.actionId - ) - ) - - val addVaccinationOrTestMenuItem = MenuSection.MenuItem( - icon = R.drawable.ic_menu_add, - title = R.string.holder_menu_listItem_addVaccinationOrTest_title, - onClick = MenuSection.MenuItem.OnClick.Navigate( - navigationActionId = actionChooseProofType.actionId, - navigationArguments = actionChooseProofType.arguments - ) - ) - - val savedEventsMenuItem = MenuSection.MenuItem( - icon = R.drawable.ic_menu_saved_events, - title = R.string.holder_menu_storedEvents, - onClick = MenuSection.MenuItem.OnClick.Navigate( - navigationActionId = actionSavedEvents.actionId - ) - ) - - val helpInfoMenuItem = MenuSection.MenuItem( - icon = R.drawable.ic_menu_info, - title = R.string.holder_menu_helpInfo, - onClick = MenuSection.MenuItem.OnClick.Navigate( - navigationActionId = actionHelpInfo.actionId, - navigationArguments = actionHelpInfo.arguments - ) - ) - - val firstSectionItems = listOfNotNull( - if (featureFlagUseCase.isInArchiveMode()) { - exportPdfMenuItem - } else { - null - }, - if (featureFlagUseCase.getAddEventsButtonEnabled()) { - addVaccinationOrTestMenuItem - } else { - null - } - ) - - val menuSections: List = listOfNotNull( - MenuSection( - menuItems = firstSectionItems - ), - MenuSection( - menuItems = listOfNotNull( - if (featureFlagUseCase.isInArchiveMode()) { - null - } else { - savedEventsMenuItem - } - ) - ), - MenuSection( - menuItems = listOfNotNull( - if (featureFlagUseCase.isInArchiveMode()) { - savedEventsMenuItem - } else { - null - }, - helpInfoMenuItem - ) - ), - if (Environment.get(context) == Environment.Prod) { - null - } else { - val dialogDirection = MenuFragmentDirections.actionDialog( - data = DialogFragmentData( - title = nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_title, - message = nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_description, - positiveButtonData = DialogButtonData.ResetApp( - textId = nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_confirm - ), - negativeButtonData = DialogButtonData.Dismiss(nl.rijksoverheid.ctr.design.R.string.about_this_app_clear_data_cancel) - ) - ) - MenuSection( - menuItems = listOf( - MenuSection.MenuItem( - icon = R.drawable.ic_warning, - iconColor = R.color.error, - titleColor = R.color.error, - title = R.string.general_menu_resetApp, - onClick = MenuSection.MenuItem.OnClick.Navigate( - dialogDirection.actionId, - dialogDirection.arguments - ) - ) - ) - ) - } - ) - - return menuSections.toTypedArray() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/models/HolderFlow.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/models/HolderFlow.kt index 8e61e6a38..d76b0e0cb 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/models/HolderFlow.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/models/HolderFlow.kt @@ -9,7 +9,6 @@ package nl.rijksoverheid.ctr.holder.models import android.os.Parcelable import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol import nl.rijksoverheid.ctr.shared.models.Flow sealed class HolderFlow(code: Int) : Flow(code), Parcelable { @@ -32,9 +31,6 @@ sealed class HolderFlow(code: Int) : Flow(code), Parcelable { @Parcelize object HkviScan : HolderFlow(5) - @Parcelize - data class HkviScanned(val remoteProtocol: RemoteProtocol) : HolderFlow(5) - @Parcelize object SyncGreenCards : HolderFlow(7) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/AppModules.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/AppModules.kt index 48de6e011..e63a48114 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/AppModules.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/AppModules.kt @@ -1,24 +1,10 @@ package nl.rijksoverheid.ctr.holder.modules -import android.content.Context -import androidx.work.WorkerFactory import nl.rijksoverheid.ctr.appconfig.usecases.DeviceRootedUseCase import nl.rijksoverheid.ctr.appconfig.usecases.DeviceRootedUseCaseImpl -import nl.rijksoverheid.ctr.appconfig.usecases.ReturnToExternalAppUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ReturnToExternalAppUseCaseImpl -import nl.rijksoverheid.ctr.design.BuildConfig import nl.rijksoverheid.ctr.holder.ui.device_secure.DeviceSecureUseCase import nl.rijksoverheid.ctr.holder.ui.device_secure.DeviceSecureUseCaseImpl -import nl.rijksoverheid.ctr.holder.ui.priority_notification.PriorityNotificationUseCase -import nl.rijksoverheid.ctr.holder.ui.priority_notification.PriorityNotificationUseCaseImpl import nl.rijksoverheid.ctr.holder.usecases.BuildConfigUseCaseImpl -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCaseImpl -import nl.rijksoverheid.ctr.holder.workers.HolderWorkerFactory -import nl.rijksoverheid.ctr.holder.workers.WorkerManagerUtil -import nl.rijksoverheid.ctr.holder.workers.WorkerManagerUtilImpl -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCaseImpl import nl.rijksoverheid.ctr.shared.BuildConfigUseCase import org.koin.android.ext.koin.androidContext import org.koin.dsl.module @@ -31,30 +17,11 @@ import org.koin.dsl.module * */ val appModule = module { - factory { PriorityNotificationUseCaseImpl(get(), get()) } factory { DeviceRootedUseCaseImpl(androidContext()) } factory { DeviceSecureUseCaseImpl(androidContext()) } - factory { - HolderCachedAppConfigUseCaseImpl( - get() - ) - } - - factory { - ReturnToExternalAppUseCaseImpl(get()) - } factory { BuildConfigUseCaseImpl() } - factory { - HolderFeatureFlagUseCaseImpl(get(), get()) - } - - factory { WorkerManagerUtilImpl(androidContext(), get(), get()) } - factory { HolderWorkerFactory(get(), get(), get(), get()) } } - -private fun isDebugApp(androidContext: Context) = - BuildConfig.DEBUG || androidContext.packageName == "nl.rijksoverheid.ctr.holder.acc" diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/CardUtilsModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/CardUtilsModule.kt deleted file mode 100644 index c5d69f795..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/CardUtilsModule.kt +++ /dev/null @@ -1,49 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import java.time.Clock -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardGreenCardAdapterItemExpiryUtil -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardGreenCardAdapterItemExpiryUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardRefreshUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardRefreshUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtilImpl -import nl.rijksoverheid.ctr.holder.qrcodes.utils.LastVaccinationDoseUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.LastVaccinationDoseUtilImpl -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrCodeUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrCodeUtilImpl -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrInfoScreenUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrInfoScreenUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreenUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreenUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.RecoveryInfoScreenUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RecoveryInfoScreenUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.TestInfoScreenUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.TestInfoScreenUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.VaccinationInfoScreenUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.VaccinationInfoScreenUtilImpl -import org.koin.android.ext.koin.androidContext -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val cardUtilsModule = module { - factory { QrCodeUtilImpl() } - factory { DashboardGreenCardAdapterItemExpiryUtilImpl(get(), androidContext()) } - factory { InfoScreenUtilImpl(get(), get(), get()) } - factory { TestInfoScreenUtilImpl(androidContext().resources, get(), get(), get()) } - factory { RecoveryInfoScreenUtilImpl(androidContext().resources, get(), get()) } - factory { QrInfoScreenUtilImpl(get(), get(), get(), get(), get()) } - factory { - VaccinationInfoScreenUtilImpl(get(), androidContext().resources, get(), get(), get()) - } - factory { LastVaccinationDoseUtilImpl(androidContext().resources) } - factory { GreenCardUtilImpl(get(), Clock.systemUTC(), get(), get()) } - factory { - GreenCardRefreshUtilImpl(get(), get(), get(), get(), get(), get()) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ErrorsModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ErrorsModule.kt index 981357672..366ce6522 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ErrorsModule.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ErrorsModule.kt @@ -1,10 +1,7 @@ package nl.rijksoverheid.ctr.holder.modules -import nl.rijksoverheid.ctr.api.factory.NetworkRequestResultFactory -import nl.rijksoverheid.ctr.holder.modules.qualifier.ErrorResponseQualifier import nl.rijksoverheid.ctr.shared.factories.ErrorCodeStringFactory import nl.rijksoverheid.ctr.shared.factories.ErrorCodeStringFactoryImpl -import org.koin.core.qualifier.named import org.koin.dsl.module /* @@ -15,13 +12,6 @@ import org.koin.dsl.module * */ fun errorsModule(flavor: String) = module { - factory { - NetworkRequestResultFactory( - get(named(ErrorResponseQualifier.CORONA_CHECK)), - get(), - get(named(ErrorResponseQualifier.MIJN_CN)) - ) - } factory { ErrorCodeStringFactoryImpl(!flavor.contains("fdroid")) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/EventsUseCasesModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/EventsUseCasesModule.kt deleted file mode 100644 index bf1f1376f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/EventsUseCasesModule.kt +++ /dev/null @@ -1,92 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import java.time.Clock -import nl.rijksoverheid.ctr.holder.dashboard.usecases.ShowBlockedEventsDialogUseCase -import nl.rijksoverheid.ctr.holder.dashboard.usecases.ShowBlockedEventsDialogUseCaseImpl -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetEventProvidersWithTokensUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetEventProvidersWithTokensUseCaseImpl -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetEventsUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetEventsUseCaseImpl -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetMijnCnEventsUsecase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetMijnCnEventsUsecaseImpl -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteEventsUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteEventsUseCaseImpl -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCaseImpl -import nl.rijksoverheid.ctr.holder.get_events.usecases.PersistBlockedEventsUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.PersistBlockedEventsUseCaseImpl -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetEventsFromPaperProofQrUseCase -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetEventsFromPaperProofQrUseCaseImpl -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.ValidatePaperProofDomesticInputCodeUseCase -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.ValidatePaperProofDomesticInputCodeUseCaseImpl -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.ValidatePaperProofDomesticUseCase -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.ValidatePaperProofDomesticUseCaseImpl -import nl.rijksoverheid.ctr.holder.pdf.PreviewPdfUseCase -import nl.rijksoverheid.ctr.holder.pdf.PreviewPdfUseCaseImpl -import nl.rijksoverheid.ctr.holder.pdf.PrintExportDccUseCase -import nl.rijksoverheid.ctr.holder.pdf.PrintExportDccUseCaseImpl -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodeUseCase -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodeUseCaseImpl -import nl.rijksoverheid.ctr.holder.saved_events.usecases.GetSavedEventsUseCase -import nl.rijksoverheid.ctr.holder.saved_events.usecases.GetSavedEventsUseCaseImpl -import nl.rijksoverheid.ctr.holder.your_events.usecases.SaveEventsUseCase -import nl.rijksoverheid.ctr.holder.your_events.usecases.SaveEventsUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.DraftEventUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.DraftEventUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveExpiredEventsUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveExpiredEventsUseCaseImpl -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val eventsUseCasesModule = module { - factory { - ValidatePaperProofDomesticInputCodeUseCaseImpl() - } - - factory { - ValidatePaperProofDomesticUseCaseImpl(get(), get(), get()) - } - - factory { - GetEventProvidersWithTokensUseCaseImpl(get()) - } - factory { - GetRemoteEventsUseCaseImpl(get()) - } - factory { - QrCodeUseCaseImpl( - get(), - get(), - get(), - get() - ) - } - factory { GetEventsUseCaseImpl(get(), get(), get(), get()) } - factory { GetMijnCnEventsUsecaseImpl(get(), get(), get()) } - factory { SaveEventsUseCaseImpl(get(), get(), get(), get(), get()) } - - factory { - GetEventsFromPaperProofQrUseCaseImpl(get(), get()) - } - - factory { - RemoveExpiredEventsUseCaseImpl(Clock.systemUTC(), get(), get()) - } - factory { - GetSavedEventsUseCaseImpl(get(), get(), get(), get(), get(), get(), get()) - } - factory { - GetRemoteProtocolFromEventGroupUseCaseImpl(get(), get()) - } - factory { PersistBlockedEventsUseCaseImpl(get()) } - factory { ShowBlockedEventsDialogUseCaseImpl(get()) } - factory { DraftEventUseCaseImpl(get()) } - factory { PrintExportDccUseCaseImpl(get(), get(), get()) } - factory { PreviewPdfUseCaseImpl() } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/GreenCardUseCasesModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/GreenCardUseCasesModule.kt deleted file mode 100644 index 78d6d44cc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/GreenCardUseCasesModule.kt +++ /dev/null @@ -1,58 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.dashboard.usecases.RemoveExpiredGreenCardsUseCase -import nl.rijksoverheid.ctr.dashboard.usecases.RemoveExpiredGreenCardsUseCaseImpl -import nl.rijksoverheid.ctr.holder.dashboard.usecases.GetDashboardItemsUseCase -import nl.rijksoverheid.ctr.holder.dashboard.usecases.GetDashboardItemsUseCaseImpl -import nl.rijksoverheid.ctr.holder.dashboard.usecases.SortGreenCardItemsUseCase -import nl.rijksoverheid.ctr.holder.dashboard.usecases.SortGreenCardItemsUseCaseImpl -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetDccFromEuropeanCredentialUseCase -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetDccFromEuropeanCredentialUseCaseImpl -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetPaperProofTypeUseCase -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetPaperProofTypeUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.CreateEuGreenCardUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.CreateEuGreenCardUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.GetRemoteGreenCardsUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.GetRemoteGreenCardsUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.SyncRemoteGreenCardsUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.SyncRemoteGreenCardsUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.UpdateEventExpirationUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.UpdateEventExpirationUseCaseImpl -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val greenCardUseCasesModule = module { - factory { - GetRemoteGreenCardsUseCaseImpl(get(), get(), get()) - } - factory { - SyncRemoteGreenCardsUseCaseImpl(get(), get(), get()) - } - factory { - CreateEuGreenCardUseCaseImpl(get(), get()) - } - factory { - GetDashboardItemsUseCaseImpl(get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) - } - factory { - SortGreenCardItemsUseCaseImpl(get(), get()) - } - factory { - RemoveExpiredGreenCardsUseCaseImpl(get(), get()) - } - factory { - GetPaperProofTypeUseCaseImpl(get(), get(), get()) - } - factory { - GetDccFromEuropeanCredentialUseCaseImpl(get()) - } - factory { - UpdateEventExpirationUseCaseImpl(get()) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderAppStatusModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderAppStatusModule.kt deleted file mode 100644 index 0e7cff31c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderAppStatusModule.kt +++ /dev/null @@ -1,42 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.appconfig.models.AppUpdateData -import nl.rijksoverheid.ctr.appconfig.models.NewFeatureItem -import nl.rijksoverheid.ctr.appconfig.models.NewTerms -import nl.rijksoverheid.ctr.appconfig.usecases.AppStatusUseCase -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.usecases.HolderAppStatusUseCaseImpl -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val holderAppStatusModule = module { - factory { - AppUpdateData( - newFeatures = listOf( - NewFeatureItem( - imageResource = R.drawable.illustration_new_in_the_app_archive_mode, - subTitleColor = R.color.link, - titleResource = R.string.holder_newintheapp_archiveMode_title, - description = R.string.holder_newintheapp_archiveMode_body - ) - ), - newTerms = NewTerms( - version = 2, - needsConsent = false - ), - newFeatureVersion = 4, - hideConsent = true - ) - } - factory { - HolderAppStatusUseCaseImpl( - get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get() - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderIntroductionModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderIntroductionModule.kt deleted file mode 100644 index e51f5f875..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderIntroductionModule.kt +++ /dev/null @@ -1,39 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.usecases.HolderIntroductionStatusUseCaseImpl -import nl.rijksoverheid.ctr.introduction.privacy_consent.models.PrivacyPolicyItem -import nl.rijksoverheid.ctr.introduction.status.models.IntroductionData -import nl.rijksoverheid.ctr.introduction.status.usecases.IntroductionStatusUseCase -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val holderIntroductionModule = module { - factory { - IntroductionData( - onboardingItems = listOf(), - privacyPolicyItems = listOf( - PrivacyPolicyItem( - R.drawable.ic_shield, - R.string.privacy_policy_1 - ), - PrivacyPolicyItem( - R.drawable.ic_shield, - R.string.privacy_policy_2 - ) - ), - hideConsent = true - ) - } - factory { - HolderIntroductionStatusUseCaseImpl( - get(), get() - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderMobilecoreModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderMobilecoreModule.kt deleted file mode 100644 index 027e24ea2..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/HolderMobilecoreModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.MobileCoreWrapperImpl -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val holderMobileCoreModule = module { - single { - MobileCoreWrapperImpl(get()) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/QrsModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/QrsModule.kt deleted file mode 100644 index 284093606..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/QrsModule.kt +++ /dev/null @@ -1,28 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodeAnimationUseCase -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodeAnimationUseCaseImpl -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodesResultUseCase -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodesResultUseCaseImpl -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val qrsModule = module { - factory { - QrCodesResultUseCaseImpl( - get(), - get(), - get(), - get(), - get(), - get() - ) - } - factory { QrCodeAnimationUseCaseImpl(get()) } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/RepositoriesModules.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/RepositoriesModules.kt deleted file mode 100644 index 1b16bfdef..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/RepositoriesModules.kt +++ /dev/null @@ -1,70 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.api.HolderApiClientUtil -import nl.rijksoverheid.ctr.holder.api.HolderApiClientUtilImpl -import nl.rijksoverheid.ctr.holder.api.TestProviderApiClientUtil -import nl.rijksoverheid.ctr.holder.api.TestProviderApiClientUtilImpl -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepository -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepositoryImpl -import nl.rijksoverheid.ctr.holder.api.repositories.EventProviderRepository -import nl.rijksoverheid.ctr.holder.api.repositories.EventProviderRepositoryImpl -import nl.rijksoverheid.ctr.holder.api.repositories.MijnCNAuthenticationRepository -import nl.rijksoverheid.ctr.holder.api.repositories.TestProviderRepository -import nl.rijksoverheid.ctr.holder.api.repositories.TestProviderRepositoryImpl -import nl.rijksoverheid.ctr.holder.modules.qualifier.ErrorResponseQualifier -import nl.rijksoverheid.ctr.holder.modules.qualifier.LoginQualifier -import nl.rijksoverheid.rdo.modules.openidconnect.OpenIDConnectRepository -import nl.rijksoverheid.rdo.modules.openidconnect.OpenIDConnectRepositoryImpl -import org.koin.core.qualifier.named -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val repositoriesModule = module { - single(named(LoginQualifier.DIGID)) { OpenIDConnectRepositoryImpl(BuildConfig.OPEN_ID_CLIENT_ID, BuildConfig.OPEN_ID_REDIRECT_URL) } - single(named(LoginQualifier.MIJN_CN)) { - MijnCNAuthenticationRepository( - get(), - get() - ) - } - factory { - HolderApiClientUtilImpl(get(), get()) - } - single { - CoronaCheckRepositoryImpl( - get(), - get(), - get(), - get(named(ErrorResponseQualifier.CORONA_CHECK)), - get(), - get() - ) - } - factory { - TestProviderRepositoryImpl( - get(), - get(), - get(named("SignedResponseWithModel")) - ) - } - factory { - TestProviderApiClientUtilImpl( - get(), - get(), - get() - ) - } - factory { - EventProviderRepositoryImpl( - get(), - get() - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ResponsesModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ResponsesModule.kt deleted file mode 100644 index d124a8c37..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ResponsesModule.kt +++ /dev/null @@ -1,76 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import com.squareup.moshi.Moshi -import com.squareup.moshi.Types -import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import nl.rijksoverheid.ctr.holder.api.OriginTypeJsonAdapter -import nl.rijksoverheid.ctr.holder.api.RemoteCouplingStatusJsonAdapter -import nl.rijksoverheid.ctr.holder.api.RemoteTestStatusJsonAdapter -import nl.rijksoverheid.ctr.holder.api.models.SignedResponseWithModel -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.modules.qualifier.ErrorResponseQualifier -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.shared.models.CoronaCheckErrorResponse -import nl.rijksoverheid.ctr.shared.models.MijnCnErrorResponse -import okhttp3.ResponseBody -import org.koin.core.qualifier.named -import org.koin.dsl.module -import retrofit2.Converter -import retrofit2.Retrofit - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val responsesModule = module { - single>>(named("SignedResponseWithModel")) { - get(Retrofit::class).responseBodyConverter( - Types.newParameterizedType( - SignedResponseWithModel::class.java, - RemoteProtocol::class.java - ), emptyArray() - ) - } - - single> { - get(Retrofit::class).responseBodyConverter( - RemoteGreenCards::class.java, - emptyArray() - ) - } - - single>(named(ErrorResponseQualifier.CORONA_CHECK)) { - get(Retrofit::class).responseBodyConverter( - CoronaCheckErrorResponse::class.java, emptyArray() - ) - } - - single>(named(ErrorResponseQualifier.MIJN_CN)) { - get(Retrofit::class).responseBodyConverter( - MijnCnErrorResponse::class.java, emptyArray() - ) - } - - single { - get(Moshi.Builder::class) - .add(RemoteTestStatusJsonAdapter()) - .add(OriginTypeJsonAdapter()) - .add(RemoteCouplingStatusJsonAdapter()) - .add( - PolymorphicJsonAdapterFactory.of( - RemoteEvent::class.java, "type" - ) - .withSubtype(RemoteEvent.getRemoteEventClassFromType(RemoteEvent.TYPE_POSITIVE_TEST), RemoteEvent.TYPE_POSITIVE_TEST) - .withSubtype(RemoteEvent.getRemoteEventClassFromType(RemoteEvent.TYPE_RECOVERY), RemoteEvent.TYPE_RECOVERY) - .withSubtype(RemoteEvent.getRemoteEventClassFromType(RemoteEvent.TYPE_NEGATIVE_TEST), RemoteEvent.TYPE_NEGATIVE_TEST) - .withSubtype(RemoteEvent.getRemoteEventClassFromType(RemoteEvent.TYPE_VACCINATION), RemoteEvent.TYPE_VACCINATION) - ) - .add(KotlinJsonAdapterFactory()) - .build() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/RetrofitModules.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/RetrofitModules.kt deleted file mode 100644 index 86913f847..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/RetrofitModules.kt +++ /dev/null @@ -1,46 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.api.MijnCnApiClient -import nl.rijksoverheid.ctr.holder.api.RemoteConfigApiClient -import okhttp3.OkHttpClient -import okhttp3.tls.HandshakeCertificates -import org.koin.dsl.module -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -fun retrofitModule(baseUrl: String, cdnUrl: String) = module { - single { - get(Retrofit::class).newBuilder().baseUrl(cdnUrl).build().create(RemoteConfigApiClient::class.java) - } - - single { - val okHttpClient = get(OkHttpClient::class) - .newBuilder() - .apply { - if (BuildConfig.FEATURE_TEST_PROVIDER_API_CHECKS) { - val handshakeCertificates = HandshakeCertificates.Builder() - .build() - - sslSocketFactory( - handshakeCertificates.sslSocketFactory(), - handshakeCertificates.trustManager - ) - } - }.build() - - Retrofit.Builder() - .client(okHttpClient) - .baseUrl(baseUrl) - .addConverterFactory(MoshiConverterFactory.create(get())) - .build() - .create(MijnCnApiClient::class.java) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/StorageModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/StorageModule.kt index f9960a5ae..30a9c7eec 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/StorageModule.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/StorageModule.kt @@ -3,10 +3,6 @@ package nl.rijksoverheid.ctr.holder.modules import nl.rijksoverheid.ctr.persistence.PersistenceManager import nl.rijksoverheid.ctr.persistence.SharedPreferencesPersistenceManager import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncer -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncerImpl -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveCTBUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveCTBUseCaseImpl import nl.rijksoverheid.ctr.shared.models.Environment import org.koin.android.ext.koin.androidContext import org.koin.dsl.module @@ -19,38 +15,10 @@ import org.koin.dsl.module * */ val storageModule = module { - single { - HolderDatabase.createInstance( - androidContext(), - get(), - get(), - Environment.get(androidContext()) - ) - } - - factory { - HolderDatabaseSyncerImpl( - get(), - get(), - get(), - get(), - get(), - get(), - get(), - get(), - get(), - get(), - get() - ) - } single { SharedPreferencesPersistenceManager( get() ) } - - factory { - RemoveCTBUseCaseImpl(get(), get()) - } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/TestProvidersUseCasesModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/TestProvidersUseCasesModule.kt deleted file mode 100644 index 5df4be7d8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/TestProvidersUseCasesModule.kt +++ /dev/null @@ -1,28 +0,0 @@ -package nl.rijksoverheid.ctr.holder.modules - -import nl.rijksoverheid.ctr.holder.get_events.usecases.ConfigProvidersUseCase -import nl.rijksoverheid.ctr.holder.get_events.usecases.ConfigProvidersUseCaseImpl -import nl.rijksoverheid.ctr.holder.input_token.usecases.TestResultUseCase -import org.koin.dsl.module - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -val testProvidersUseCasesModule = module { - factory { - ConfigProvidersUseCaseImpl(get()) - } - - factory { - TestResultUseCase( - get(), - get(), - get(), - get() - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/UtilsModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/UtilsModule.kt index 5849f3547..3f3c7c523 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/UtilsModule.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/UtilsModule.kt @@ -1,69 +1,11 @@ package nl.rijksoverheid.ctr.holder.modules -import java.time.Clock -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardGreenCardAdapterItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardGreenCardAdapterItemUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardHeaderAdapterItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardHeaderAdapterItemUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardInfoCardAdapterItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.items.DashboardInfoCardAdapterItemUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.CardItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.CardItemUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.CredentialUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.CredentialUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardItemEmptyStateUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardItemEmptyStateUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardItemUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardItemUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardPageInfoItemHandlerUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.DashboardPageInfoItemHandlerUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.OriginUtilImpl -import nl.rijksoverheid.ctr.holder.dashboard.util.RemovedEventsBottomSheetUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.RemovedEventsBottomSheetUtilImpl -import nl.rijksoverheid.ctr.holder.get_events.utils.LoginTypeUtil -import nl.rijksoverheid.ctr.holder.get_events.utils.LoginTypeUtilImpl -import nl.rijksoverheid.ctr.holder.get_events.utils.ScopeUtil -import nl.rijksoverheid.ctr.holder.get_events.utils.ScopeUtilImpl -import nl.rijksoverheid.ctr.holder.menu.AboutThisAppDataModel -import nl.rijksoverheid.ctr.holder.menu.AboutThisAppDataModelImpl -import nl.rijksoverheid.ctr.holder.menu.HelpMenuDataModel -import nl.rijksoverheid.ctr.holder.menu.HelpMenuDataModelImpl -import nl.rijksoverheid.ctr.holder.no_digid.NoDigidScreenDataUtil -import nl.rijksoverheid.ctr.holder.no_digid.NoDigidScreenDataUtilImpl -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtil -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtilImpl -import nl.rijksoverheid.ctr.holder.qrcodes.models.ReadEuropeanCredentialUtil -import nl.rijksoverheid.ctr.holder.qrcodes.models.ReadEuropeanCredentialUtilImpl -import nl.rijksoverheid.ctr.holder.qrcodes.utils.MultipleQrCodesUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.MultipleQrCodesUtilImpl -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrCodesFragmentUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrCodesFragmentUtilImpl import nl.rijksoverheid.ctr.holder.utils.CountryUtil import nl.rijksoverheid.ctr.holder.utils.CountryUtilImpl import nl.rijksoverheid.ctr.holder.utils.LocalDateUtil import nl.rijksoverheid.ctr.holder.utils.LocalDateUtilImpl import nl.rijksoverheid.ctr.holder.utils.StringUtil import nl.rijksoverheid.ctr.holder.utils.StringUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.EventGroupEntityUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.EventGroupEntityUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventHolderUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventHolderUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventStringUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventStringUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteProtocol3Util -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteProtocol3UtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsEndStateUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsEndStateUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtilImpl -import nl.rijksoverheid.ctr.holder.your_events.widgets.YourEventWidgetUtil -import nl.rijksoverheid.ctr.holder.your_events.widgets.YourEventWidgetUtilImpl -import nl.rijksoverheid.rdo.modules.luhncheck.TokenValidator -import nl.rijksoverheid.rdo.modules.luhncheck.TokenValidatorImpl -import org.koin.android.ext.koin.androidContext import org.koin.dsl.module /* @@ -74,57 +16,7 @@ import org.koin.dsl.module * */ val utilsModule = module { - factory { - DashboardGreenCardAdapterItemUtilImpl( - Clock.systemUTC(), - androidContext(), - get(), - get() - ) - } - factory { TokenValidatorImpl() } - factory { CredentialUtilImpl(Clock.systemUTC(), get(), get(), get(), get()) } - factory { OriginUtilImpl(Clock.systemUTC()) } - factory { RemoteEventHolderUtilImpl(get(), get(), get(), get()) } - factory { RemoteProtocol3UtilImpl() } - factory { RemoteEventUtilImpl(get()) } - factory { RemoteEventStringUtilImpl(androidContext()::getString) } - factory { ReadEuropeanCredentialUtilImpl(get()) } - factory { - DashboardItemUtilImpl( - get(), - get(), - get(), - get(), - get(), - get() - ) - } factory { CountryUtilImpl() } factory { LocalDateUtilImpl(get(), get()) } - factory { MultipleQrCodesUtilImpl() } - factory { - DashboardPageInfoItemHandlerUtilImpl( - get(), - get(), - get() - ) - } - factory { QrCodesFragmentUtilImpl(Clock.systemUTC()) } - factory { YourEventsFragmentUtilImpl(get()) } - factory { YourEventWidgetUtilImpl() } - factory { DashboardInfoCardAdapterItemUtilImpl() } - factory { DashboardItemEmptyStateUtilImpl() } - factory { AboutThisAppDataModelImpl(get(), get(), get()) } - factory { HelpMenuDataModelImpl(get(), get(), get()) } - factory { ScopeUtilImpl() } - factory { LoginTypeUtilImpl() } - factory { DashboardHeaderAdapterItemUtilImpl() } - factory { CardItemUtilImpl() } - factory { EventGroupEntityUtilImpl(get()) } - factory { PaperProofUtilImpl(get(), get(), get()) } - factory { NoDigidScreenDataUtilImpl(get(), get(), get()) } factory { StringUtilImpl(get()) } - factory { YourEventsEndStateUtilImpl(get()) } - factory { RemovedEventsBottomSheetUtilImpl(get(), get(), get(), get(), get(), get()) } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ViewModels.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ViewModels.kt index 6f205ae61..5429149d1 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ViewModels.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/modules/ViewModels.kt @@ -1,37 +1,10 @@ package nl.rijksoverheid.ctr.holder.modules -import nl.rijksoverheid.ctr.design.menu.MenuViewModel -import nl.rijksoverheid.ctr.holder.HolderMainActivityViewModel -import nl.rijksoverheid.ctr.holder.HolderMainActivityViewModelImpl -import nl.rijksoverheid.ctr.holder.dashboard.DashboardViewModel -import nl.rijksoverheid.ctr.holder.dashboard.DashboardViewModelImpl -import nl.rijksoverheid.ctr.holder.get_events.GetEventsViewModel -import nl.rijksoverheid.ctr.holder.get_events.GetEventsViewModelImpl -import nl.rijksoverheid.ctr.holder.get_events.LoginViewModel -import nl.rijksoverheid.ctr.holder.input_token.InputTokenViewModel -import nl.rijksoverheid.ctr.holder.input_token.InputTokenViewModelImpl -import nl.rijksoverheid.ctr.holder.menu.MenuViewModelImpl -import nl.rijksoverheid.ctr.holder.modules.qualifier.LoginQualifier -import nl.rijksoverheid.ctr.holder.pdf.PdfPreviewViewModel -import nl.rijksoverheid.ctr.holder.pdf.PdfPreviewViewModelImpl -import nl.rijksoverheid.ctr.holder.pdf.PdfWebViewModel -import nl.rijksoverheid.ctr.holder.pdf.PdfWebViewModelImpl -import nl.rijksoverheid.ctr.holder.qrcodes.QrCodesViewModel -import nl.rijksoverheid.ctr.holder.qrcodes.QrCodesViewModelImpl -import nl.rijksoverheid.ctr.holder.saved_events.SavedEventsViewModel -import nl.rijksoverheid.ctr.holder.sync_greencards.SyncGreenCardsViewModel -import nl.rijksoverheid.ctr.holder.sync_greencards.SyncGreenCardsViewModelImpl import nl.rijksoverheid.ctr.holder.ui.device_rooted.DeviceRootedViewModel import nl.rijksoverheid.ctr.holder.ui.device_rooted.DeviceRootedViewModelImpl import nl.rijksoverheid.ctr.holder.ui.device_secure.DeviceSecureViewModel import nl.rijksoverheid.ctr.holder.ui.device_secure.DeviceSecureViewModelImpl -import nl.rijksoverheid.ctr.holder.ui.priority_notification.PriorityNotificationViewModel -import nl.rijksoverheid.ctr.holder.ui.priority_notification.PriorityNotificationViewModelImpl -import nl.rijksoverheid.ctr.holder.your_events.YourEventsViewModel -import nl.rijksoverheid.ctr.holder.your_events.YourEventsViewModelImpl -import org.koin.android.ext.koin.androidContext import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.core.qualifier.named import org.koin.dsl.module /* @@ -42,24 +15,6 @@ import org.koin.dsl.module * */ val viewModels = module { - viewModel { QrCodesViewModelImpl(get(), get(), get()) } - viewModel { HolderMainActivityViewModelImpl() } - viewModel { InputTokenViewModelImpl(get(), get()) } - viewModel(named(LoginQualifier.DIGID)) { - LoginViewModel(get(named(LoginQualifier.DIGID)), get()) - } - viewModel(named(LoginQualifier.MIJN_CN)) { - LoginViewModel(get(named(LoginQualifier.MIJN_CN)), get()) - } viewModel { DeviceRootedViewModelImpl(get(), get()) } viewModel { DeviceSecureViewModelImpl(get(), get()) } - viewModel { YourEventsViewModelImpl(get(), get(), get()) } - viewModel { GetEventsViewModelImpl(get(), get(), get()) } - viewModel { DashboardViewModelImpl(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) } - viewModel { SyncGreenCardsViewModelImpl(get(), get()) } - viewModel { SavedEventsViewModel(get(), get()) } - viewModel { MenuViewModelImpl(get(), get()) } - viewModel { PriorityNotificationViewModelImpl(get()) } - viewModel { PdfWebViewModelImpl(androidContext().filesDir.path, get(), get()) } - viewModel { PdfPreviewViewModelImpl(get()) } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidFragment.kt deleted file mode 100644 index dc0630ff0..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidFragment.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.no_digid - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.design.utils.IntentUtil -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentNoDigidBinding -import nl.rijksoverheid.ctr.holder.ui.create_qr.bind -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import org.koin.android.ext.android.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class NoDigidFragment : Fragment(R.layout.fragment_no_digid) { - - private val args: NoDigidFragmentArgs by navArgs() - private val intentUtil: IntentUtil by inject() - private val infoFragmentUtil: InfoFragmentUtil by inject() - - override fun onDestroyView() { - super.onDestroyView() - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentNoDigidBinding.bind(view) - - with(args.data) { - binding.title.text = title - if (description.isNotEmpty()) { - binding.description.text = description - } else { - binding.description.visibility = View.GONE - } - - binding.firstButton.bind( - title = firstNavigationButtonData.title, - subtitle = firstNavigationButtonData.subtitle, - logo = firstNavigationButtonData.icon - ) { - onButtonClick(firstNavigationButtonData) - } - - binding.secondButton.bind( - title = secondNavigationButtonData.title, - subtitle = secondNavigationButtonData.subtitle, - logo = secondNavigationButtonData.icon - ) { - onButtonClick(secondNavigationButtonData) - } - } - } - - private fun onButtonClick(data: NoDigidNavigationButtonData) { - when (data) { - is NoDigidNavigationButtonData.NoDigid -> { - navigateSafety( - NoDigidFragmentDirections.actionNoDigid(data.noDigidFragmentData) - ) - } - is NoDigidNavigationButtonData.Info -> { - infoFragmentUtil.presentFullScreen( - currentFragment = this, - toolbarTitle = getString(R.string.choose_provider_toolbar), - data = data.infoFragmentData - ) - } - is NoDigidNavigationButtonData.Link -> { - intentUtil.openUrl( - context = requireContext(), - url = data.externalUrl - ) - } - is NoDigidNavigationButtonData.Ggd -> { - navigateSafety( - NoDigidFragmentDirections.actionPap(args.data.originType) - ) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidFragmentArguments.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidFragmentArguments.kt deleted file mode 100644 index 9d07eb595..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidFragmentArguments.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.no_digid - -import android.os.Parcelable -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType - -@Parcelize -data class NoDigidFragmentData( - val title: String, - val description: String, - val firstNavigationButtonData: NoDigidNavigationButtonData, - val secondNavigationButtonData: NoDigidNavigationButtonData, - val originType: RemoteOriginType -) : Parcelable - -sealed class NoDigidNavigationButtonData( - @StringRes open val title: Int, - open val subtitle: String? = null, - @DrawableRes open val icon: Int? = null -) : Parcelable { - - @Parcelize - data class NoDigid( - @StringRes override val title: Int, - override val subtitle: String? = null, - @DrawableRes override val icon: Int? = null, - val noDigidFragmentData: NoDigidFragmentData - ) : NoDigidNavigationButtonData(title, subtitle, icon) - - @Parcelize - data class Info( - @StringRes override val title: Int, - override val subtitle: String? = null, - @DrawableRes override val icon: Int? = null, - val infoFragmentData: InfoFragmentData.TitleDescriptionWithButton - ) : NoDigidNavigationButtonData(title, subtitle, icon) - - @Parcelize - data class Link( - @StringRes override val title: Int, - override val subtitle: String? = null, - @DrawableRes override val icon: Int? = null, - val externalUrl: String - ) : NoDigidNavigationButtonData(title, subtitle, icon) - - @Parcelize - data class Ggd( - @StringRes override val title: Int, - override val subtitle: String? = null, - @DrawableRes override val icon: Int? = null - ) : NoDigidNavigationButtonData(title, subtitle, icon) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidScreenDataUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidScreenDataUtil.kt deleted file mode 100644 index 6a1f432ef..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/NoDigidScreenDataUtil.kt +++ /dev/null @@ -1,126 +0,0 @@ -package nl.rijksoverheid.ctr.holder.no_digid - -import android.content.Context -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface NoDigidScreenDataUtil { - fun requestDigidButton(): NoDigidNavigationButtonData - fun continueWithoutDigidButton(originType: RemoteOriginType): NoDigidNavigationButtonData -} - -class NoDigidScreenDataUtilImpl( - private val context: Context, - private val holderFeatureFlagUseCase: HolderFeatureFlagUseCase, - private val cachedAppConfigUseCase: CachedAppConfigUseCase -) : NoDigidScreenDataUtil { - - private fun getString(@StringRes stringId: Int) = context.getString(stringId) - - private fun getStringArgs(@StringRes stringId: Int, args: Array) = - context.getString(stringId, *args) - - val contactInformation = cachedAppConfigUseCase.getCachedAppConfig().contactInfo - val description = getStringArgs( - R.string.holder_contactCoronaCheckHelpdesk_message, - arrayOf( - PapFragment.openDaysString(context, contactInformation), - contactInformation.startHour, - contactInformation.endHour, - contactInformation.phoneNumber, - contactInformation.phoneNumber, - contactInformation.phoneNumberAbroad, - contactInformation.phoneNumberAbroad - ) - ) - private val doesHaveBSNButton = NoDigidNavigationButtonData.Info( - title = R.string.holder_checkForBSN_buttonTitle_doesHaveBSN, - subtitle = getString(R.string.holder_checkForBSN_buttonSubTitle_doesHaveBSN), - infoFragmentData = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_contactCoronaCheckHelpdesk_title), - descriptionData = DescriptionData( - htmlTextString = description, - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ) - ) - - private fun doesNotHaveBSNButton(originType: RemoteOriginType): NoDigidNavigationButtonData { - val title = R.string.holder_checkForBSN_buttonTitle_doesNotHaveBSN - val subtitle = getString( - if (originType == RemoteOriginType.Vaccination) { - R.string.holder_checkForBSN_buttonSubTitle_doesNotHaveBSN_vaccinationFlow - } else { - R.string.holder_checkForBSN_buttonSubTitle_doesNotHaveBSN_testFlow - } - ) - - return if (holderFeatureFlagUseCase.getPapEnabled()) { - NoDigidNavigationButtonData.Ggd( - title = title, - subtitle = subtitle - ) - } else { - NoDigidNavigationButtonData.Info( - title = title, - subtitle = subtitle, - infoFragmentData = InfoFragmentData.TitleDescriptionWithButton( - title = getString( - if (originType == RemoteOriginType.Vaccination) { - R.string.holder_contactProviderHelpdesk_vaccinationFlow_title - } else { - R.string.holder_contactProviderHelpdesk_testFlow_title - } - ), - descriptionData = DescriptionData( - if (originType == RemoteOriginType.Vaccination) { - R.string.holder_contactProviderHelpdesk_vaccinationFlow_message - } else { - R.string.holder_contactProviderHelpdesk_testFlow_message - } - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ) - ) - } - } - - override fun requestDigidButton() = NoDigidNavigationButtonData.Link( - title = R.string.holder_noDigiD_buttonTitle_requestDigiD, - icon = R.drawable.ic_digid_logo, - externalUrl = getString(R.string.holder_noDigiD_url) - ) - - override fun continueWithoutDigidButton(originType: RemoteOriginType) = - NoDigidNavigationButtonData.NoDigid( - title = R.string.holder_noDigiD_buttonTitle_continueWithoutDigiD, - subtitle = getString(R.string.holder_noDigiD_buttonSubTitle_continueWithoutDigiD), - noDigidFragmentData = NoDigidFragmentData( - title = getString(R.string.holder_checkForBSN_title), - description = getString(R.string.holder_checkForBSN_message), - firstNavigationButtonData = doesHaveBSNButton, - secondNavigationButtonData = doesNotHaveBSNButton(originType), - originType = originType - ) - ) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/PapFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/PapFragment.kt deleted file mode 100644 index 29ffdb0d2..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/no_digid/PapFragment.kt +++ /dev/null @@ -1,251 +0,0 @@ -package nl.rijksoverheid.ctr.holder.no_digid - -import android.annotation.SuppressLint -import android.content.Context -import android.os.Bundle -import android.view.View -import androidx.core.view.isVisible -import androidx.navigation.fragment.navArgs -import java.time.DayOfWeek -import java.time.format.TextStyle -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentNoDigidBinding -import nl.rijksoverheid.ctr.holder.get_events.DigiDFragment -import nl.rijksoverheid.ctr.holder.get_events.GetEventsFragmentDirections -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.LoginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteOriginType -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.ui.create_qr.bind -import nl.rijksoverheid.ctr.holder.ui.create_qr.setEnabled -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.holder.your_events.YourEventsFragmentType -import nl.rijksoverheid.ctr.shared.ext.locale -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.utils.Accessibility.makeIndeterminateAccessible -import org.koin.android.ext.android.inject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class PapFragment : DigiDFragment(R.layout.fragment_no_digid) { - - private var _binding: FragmentNoDigidBinding? = null - private val binding: FragmentNoDigidBinding get() = _binding!! - private val args: PapFragmentArgs by navArgs() - private val infoFragmentUtil: InfoFragmentUtil by inject() - private val holderFeatureFlagUseCase: HolderFeatureFlagUseCase by inject() - private val cachedAppConfigUseCase: CachedAppConfigUseCase by inject() - - companion object { - @SuppressLint("StringFormatInvalid") - fun openDaysString(context: Context, contactInformation: AppConfig.ContactInformation): String { - val startDay = contactInformation.startDay - val endDay = contactInformation.endDay - - if (startDay == 1 && endDay == 7) { - return context.getString(R.string.holder_contactCoronaCheckHelpdesk_message_every_day) - } - - val startDayOfWeek = DayOfWeek.of(contactInformation.startDay).getDisplayName(TextStyle.FULL, context.locale()) - val endDayOfWeek = DayOfWeek.of(contactInformation.endDay).getDisplayName(TextStyle.FULL, context.locale()) - - return context.getString(R.string.holder_contactCoronaCheckHelpdesk_message_until, startDayOfWeek, endDayOfWeek) - } - } - - override fun onButtonClickWithRetryAction() { - loginWithDigiD() - } - - override fun getFlow(): Flow { - return when (args.originType) { - RemoteOriginType.Recovery -> HolderFlow.Recovery - RemoteOriginType.Test -> HolderFlow.DigidTest - RemoteOriginType.Vaccination -> HolderFlow.Vaccination - } - } - - override fun onDestroyView() { - super.onDestroyView() - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(false) - } - - @SuppressLint("StringFormatInvalid") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - _binding = FragmentNoDigidBinding.bind(view) - - if (args.originType is RemoteOriginType.Vaccination) { - binding.title.text = getString(R.string.holder_chooseEventLocation_title) - binding.description.visibility = View.GONE - binding.firstButton.bind( - title = R.string.holder_chooseEventLocation_buttonTitle_GGD, - subtitle = getString(R.string.holder_chooseEventLocation_buttonSubTitle_GGD) - ) { - loginWithDigiD() - } - - binding.secondButton.bind( - title = R.string.holder_chooseEventLocation_buttonTitle_other, - subtitle = getString(R.string.holder_chooseEventLocation_buttonSubTitle_other) - ) { - infoFragmentUtil.presentFullScreen( - currentFragment = this@PapFragment, - toolbarTitle = getString(R.string.choose_provider_toolbar), - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_contactProviderHelpdesk_vaccinationFlow_title), - descriptionData = DescriptionData(R.string.holder_contactProviderHelpdesk_message_ggdPortalEnabled), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ) - ) - } - } else { - binding.title.text = getString(R.string.holder_checkForBSN_title) - binding.description.text = getString(R.string.holder_checkForBSN_message) - binding.description.visibility = View.VISIBLE - binding.firstButton.bind( - title = R.string.holder_checkForBSN_buttonTitle_doesHaveBSN, - subtitle = getString(R.string.holder_checkForBSN_buttonSubTitle_doesHaveBSN) - ) { - val contactInformation = cachedAppConfigUseCase.getCachedAppConfig().contactInfo - infoFragmentUtil.presentFullScreen( - currentFragment = this@PapFragment, - toolbarTitle = getString(R.string.choose_provider_toolbar), - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_contactCoronaCheckHelpdesk_title), - descriptionData = DescriptionData( - htmlTextString = getString( - R.string.holder_contactCoronaCheckHelpdesk_message, - openDaysString(requireContext(), contactInformation), - contactInformation.startHour, - contactInformation.endHour, - contactInformation.phoneNumber, - contactInformation.phoneNumber, - contactInformation.phoneNumberAbroad, - contactInformation.phoneNumberAbroad - ), - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ) - ) - } - - binding.secondButton.bind( - title = R.string.holder_checkForBSN_buttonTitle_doesNotHaveBSN, - subtitle = getString(R.string.holder_checkForBSN_buttonSubTitle_doesNotHaveBSN_testFlow) - ) { - if (holderFeatureFlagUseCase.getPapEnabled()) { - /** disable accessibility, otherwise it is announced when loading is finished - * reenable with [dialogPresented] if a dialog is presented cause then the user will again interact with it - */ - binding.secondButton.root.importantForAccessibility = - View.IMPORTANT_FOR_ACCESSIBILITY_NO - loginWithDigiD() - } else { - infoFragmentUtil.presentFullScreen( - currentFragment = this@PapFragment, - toolbarTitle = getString(R.string.choose_provider_toolbar), - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_contactProviderHelpdesk_testFlow_title), - descriptionData = DescriptionData(R.string.holder_contactProviderHelpdesk_testFlow_message), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ) - ) - } - } - } - - digidViewModel.loading.observe(viewLifecycleOwner, EventObserver { - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(it) - }) - } - - override fun getLoginType(): LoginType { - return LoginType.Pap - } - - private fun setEnabled(enabled: Boolean) { - binding.title.isEnabled = enabled - binding.description.isEnabled = enabled - } - - override fun onDigidLoading(loading: Boolean) { - binding.firstButton.setEnabled(!loading) - binding.secondButton.setEnabled(!loading) - setEnabled(!loading) - } - - override fun onGetEventsLoading(loading: Boolean) { - binding.loadingOverlay.progressBar.makeIndeterminateAccessible( - context = requireContext(), - isLoading = loading, - message = R.string.holder_fetchevents_loading - ) - binding.loadingOverlay.root.isVisible = loading - } - - override fun getOriginTypes(): List { - return listOf(args.originType) - } - - override fun onNavigateToYourEvents( - remoteProtocols: Map, - eventProviders: List - ) { - navigateSafety( - GetEventsFragmentDirections.actionYourEvents( - type = YourEventsFragmentType.RemoteProtocol3Type( - remoteEvents = remoteProtocols, - eventProviders = eventProviders - ), - toolbarTitle = getCopyForOriginType().toolbarTitle, - flow = getFlow() - ) - ) - } - - override fun yourEventsFragmentType( - remoteProtocols: Map, - eventProviders: List - ): YourEventsFragmentType { - return YourEventsFragmentType.RemoteProtocol3Type( - remoteEvents = remoteProtocols, - eventProviders = eventProviders - ) - } - - override fun dialogPresented() { - binding.secondButton.root.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES - } - - override fun openDialog(data: DialogFragmentData) { - navigateSafety(PapFragmentDirections.actionDialog(data)) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofDomesticCodeResult.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofDomesticCodeResult.kt deleted file mode 100644 index eae0e08bb..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofDomesticCodeResult.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.models - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -sealed class PaperProofDomesticCodeResult : Parcelable { - @Parcelize - object Valid : PaperProofDomesticCodeResult(), Parcelable - - @Parcelize - object Invalid : PaperProofDomesticCodeResult(), Parcelable - - @Parcelize - object Empty : PaperProofDomesticCodeResult(), Parcelable -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofDomesticResult.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofDomesticResult.kt deleted file mode 100644 index 5c383924e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofDomesticResult.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.models - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.shared.models.ErrorResult - -sealed class PaperProofDomesticResult { - data class Valid( - val remoteEvent: RemoteProtocol, - val eventGroupJsonData: ByteArray - ) : PaperProofDomesticResult() { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Valid - - if (remoteEvent != other.remoteEvent) return false - if (!eventGroupJsonData.contentEquals(other.eventGroupJsonData)) return false - - return true - } - - override fun hashCode(): Int { - var result = remoteEvent.hashCode() - result = 31 * result + eventGroupJsonData.contentHashCode() - return result - } - } - - sealed class Invalid : PaperProofDomesticResult() { - object RejectedQr : Invalid() - object BlockedQr : Invalid() - data class Error(val errorResult: ErrorResult) : Invalid() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofType.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofType.kt deleted file mode 100644 index c4183a79d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/PaperProofType.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.models - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol - -sealed class PaperProofType { - sealed class DCC : PaperProofType() { - data class Foreign( - val remoteProtocol: RemoteProtocol, - val eventGroupJsonData: ByteArray - ) : DCC() { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Foreign - - if (remoteProtocol != other.remoteProtocol) return false - if (!eventGroupJsonData.contentEquals(other.eventGroupJsonData)) return false - - return true - } - - override fun hashCode(): Int { - var result = remoteProtocol.hashCode() - result = 31 * result + eventGroupJsonData.contentHashCode() - return result - } - } - - data class Dutch(val qrContent: String) : DCC() - } - object CTB : PaperProofType() - object Unknown : PaperProofType() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/RemoteCouplingResponse.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/RemoteCouplingResponse.kt deleted file mode 100644 index cb843c508..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/RemoteCouplingResponse.kt +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.models - -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class RemoteCouplingResponse( - val status: RemoteCouplingStatus -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/RemoteCouplingStatus.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/RemoteCouplingStatus.kt deleted file mode 100644 index 4029f2f9d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/models/RemoteCouplingStatus.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.models - -sealed class RemoteCouplingStatus(val typeString: String) { - - companion object { - const val TYPE_ACCEPTED = "accepted" - const val TYPE_REJECTED = "rejected" - const val TYPE_BLOCKED = "blocked" - const val TYPE_EXPIRED = "expired" - } - - object Accepted : RemoteCouplingStatus(TYPE_ACCEPTED) - object Rejected : RemoteCouplingStatus(TYPE_REJECTED) - object Blocked : RemoteCouplingStatus(TYPE_BLOCKED) - object Expired : RemoteCouplingStatus(TYPE_EXPIRED) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetDccFromEuropeanCredentialUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetDccFromEuropeanCredentialUseCase.kt deleted file mode 100644 index a47703377..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetDccFromEuropeanCredentialUseCase.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.usecases - -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import org.json.JSONObject - -interface GetDccFromEuropeanCredentialUseCase { - fun get(europeanCredential: ByteArray): JSONObject -} - -class GetDccFromEuropeanCredentialUseCaseImpl( - private val mobileCoreWrapper: MobileCoreWrapper -) : GetDccFromEuropeanCredentialUseCase { - - override fun get(europeanCredential: ByteArray): JSONObject { - val credentials = mobileCoreWrapper.readEuropeanCredential(europeanCredential) - return requireNotNull(credentials.optJSONObject("dcc")) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetEventsFromPaperProofQrUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetEventsFromPaperProofQrUseCase.kt deleted file mode 100644 index b5101d35e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetEventsFromPaperProofQrUseCase.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.usecases - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import org.json.JSONException - -interface GetEventsFromPaperProofQrUseCase { - fun get(qrCode: String): RemoteProtocol -} - -class GetEventsFromPaperProofQrUseCaseImpl( - private val mobileCoreWrapper: MobileCoreWrapper, - private val remoteEventUtil: RemoteEventUtil -) : GetEventsFromPaperProofQrUseCase { - - @Throws(NullPointerException::class, JSONException::class) - override fun get(qrCode: String): RemoteProtocol { - val credential = qrCode.toByteArray() - val credentials = mobileCoreWrapper.readEuropeanCredential(credential) - val dcc = credentials.optJSONObject("dcc") - val holder = remoteEventUtil.getHolderFromDcc(dcc!!) - val event = remoteEventUtil.getRemoteEventFromDcc(dcc) - - val providerIdentifier = when (event) { - is RemoteEventVaccination -> { - // For hkvi vaccination events we want to be able to save multiple events (for example you get 2 papers, one with your first vaccination and another with your second) - // The database prevents us from doing so because it has uniques on both providerIdentifier and type - // For hkvi vaccinations we add the unique to the provider identifier so it gets saved as well - RemoteConfigProviders.EventProvider.PROVIDER_IDENTIFIER_DCC_SUFFIX.replace("[unique]", event.unique ?: "") - } - else -> { - RemoteConfigProviders.EventProvider.PROVIDER_IDENTIFIER_DCC - } - } - - return RemoteProtocol( - providerIdentifier = providerIdentifier, - protocolVersion = "3.0", - status = RemoteProtocol.Status.COMPLETE, - holder = holder, - events = listOf(event) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetPaperProofTypeUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetPaperProofTypeUseCase.kt deleted file mode 100644 index 4f2e78f89..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/GetPaperProofTypeUseCase.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.usecases - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.holder.paper_proof.models.PaperProofType -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtil -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper - -interface GetPaperProofTypeUseCase { - suspend fun get(qrContent: String): PaperProofType -} - -class GetPaperProofTypeUseCaseImpl( - private val getEventsFromPaperProofQrUseCase: GetEventsFromPaperProofQrUseCase, - private val paperProofUtil: PaperProofUtil, - private val mobileCoreWrapper: MobileCoreWrapper -) : GetPaperProofTypeUseCase { - override suspend fun get(qrContent: String): PaperProofType { - return withContext(Dispatchers.IO) { - val isForeign = mobileCoreWrapper.isForeignDcc(qrContent.toByteArray()) - val event = try { - getEventsFromPaperProofQrUseCase.get(qrContent) - } catch (exception: Exception) { - return@withContext PaperProofType.Unknown - } - - if (isForeign) { - PaperProofType.DCC.Foreign( - remoteProtocol = event, - eventGroupJsonData = paperProofUtil.getEventGroupJsonData(qrContent = qrContent) - ) - } else { - PaperProofType.DCC.Dutch( - qrContent = qrContent - ) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/ValidatePaperProofDomesticInputCodeUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/ValidatePaperProofDomesticInputCodeUseCase.kt deleted file mode 100644 index 7f542e3c5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/ValidatePaperProofDomesticInputCodeUseCase.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.usecases - -import nl.rijksoverheid.ctr.holder.paper_proof.models.PaperProofDomesticCodeResult - -interface ValidatePaperProofDomesticInputCodeUseCase { - companion object { - const val CODE_POINTS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" - } - - fun validate(code: String): PaperProofDomesticCodeResult -} - -class ValidatePaperProofDomesticInputCodeUseCaseImpl : ValidatePaperProofDomesticInputCodeUseCase { - - override fun validate(code: String): PaperProofDomesticCodeResult { - code.toCharArray().forEach { - if (!ValidatePaperProofDomesticInputCodeUseCase.CODE_POINTS.contains(it)) { - return PaperProofDomesticCodeResult.Invalid - } - } - - return when (code.length) { - 0 -> PaperProofDomesticCodeResult.Empty - 6 -> PaperProofDomesticCodeResult.Valid - else -> PaperProofDomesticCodeResult.Invalid - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/ValidatePaperProofDomesticUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/ValidatePaperProofDomesticUseCase.kt deleted file mode 100644 index 315a3bdc8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/usecases/ValidatePaperProofDomesticUseCase.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.usecases - -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepository -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.paper_proof.models.PaperProofDomesticResult -import nl.rijksoverheid.ctr.holder.paper_proof.models.RemoteCouplingStatus -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtil -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -interface ValidatePaperProofDomesticUseCase { - suspend fun validate(qrContent: String, couplingCode: String): PaperProofDomesticResult -} - -class ValidatePaperProofDomesticUseCaseImpl( - private val coronaCheckRepository: CoronaCheckRepository, - private val getEventsFromPaperProofQr: GetEventsFromPaperProofQrUseCase, - private val paperProofUtil: PaperProofUtil -) : ValidatePaperProofDomesticUseCase { - - override suspend fun validate( - qrContent: String, - couplingCode: String - ): PaperProofDomesticResult { - return try { - val networkRequestResult = coronaCheckRepository.getCoupling( - credential = qrContent, - couplingCode = couplingCode - ) - - when (networkRequestResult) { - is NetworkRequestResult.Success -> { - when (networkRequestResult.response.status) { - RemoteCouplingStatus.Accepted -> validateSuccess(qrContent, couplingCode) - RemoteCouplingStatus.Rejected -> PaperProofDomesticResult.Invalid.RejectedQr - RemoteCouplingStatus.Blocked -> PaperProofDomesticResult.Invalid.BlockedQr - RemoteCouplingStatus.Expired -> validateSuccess(qrContent, couplingCode) - } - } - is NetworkRequestResult.Failed -> { - PaperProofDomesticResult.Invalid.Error(networkRequestResult) - } - } - } catch (e: Exception) { - PaperProofDomesticResult.Invalid.Error( - AppErrorResult( - step = HolderStep.CouplingNetworkRequest, - e = e - ) - ) - } - } - - private fun validateSuccess( - qrContent: String, - couplingCode: String - ) = PaperProofDomesticResult.Valid( - remoteEvent = getEventsFromPaperProofQr.get(qrContent), - eventGroupJsonData = paperProofUtil.getEventGroupJsonData( - qrContent = qrContent, - couplingCode = couplingCode - ) - ) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/utils/PaperProofUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/utils/PaperProofUtil.kt deleted file mode 100644 index dd5eb645f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/paper_proof/utils/PaperProofUtil.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.paper_proof.utils - -import android.content.Context -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetDccFromEuropeanCredentialUseCase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.ext.getStringOrNull -import org.json.JSONObject - -interface PaperProofUtil { - fun getEventGroupJsonData( - qrContent: String, - couplingCode: String? = null - ): ByteArray - - fun getIssuer( - europeanCredential: ByteArray - ): String - - fun getInfoScreenFooterText( - europeanCredential: ByteArray - ): String -} - -class PaperProofUtilImpl( - private val context: Context, - private val mobileCoreWrapper: MobileCoreWrapper, - private val getDccFromEuropeanCredentialUseCase: GetDccFromEuropeanCredentialUseCase -) : PaperProofUtil { - - override fun getEventGroupJsonData( - qrContent: String, - couplingCode: String? - ): ByteArray = JSONObject( - mutableMapOf("credential" to qrContent) - .also { map -> - couplingCode?.let { couplingCode -> - map["couplingCode"] = couplingCode - } - } - .toMap()) - .toString() - .toByteArray() - - override fun getIssuer(europeanCredential: ByteArray): String { - val dcc = getDccFromEuropeanCredentialUseCase.get(europeanCredential) - val proof = dcc.optJSONArray("v") ?: dcc.optJSONArray("t") ?: dcc.optJSONArray("r") - return proof?.optJSONObject(0)?.getStringOrNull("is") ?: "" - } - - override fun getInfoScreenFooterText(europeanCredential: ByteArray): String { - val resource = if (mobileCoreWrapper.isForeignDcc(europeanCredential)) { - R.string.holder_listRemoteEvents_somethingWrong_foreignDCC_body - } else { - R.string.paper_proof_event_explanation_footer - } - return context.getString(resource) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/EUPrintAttributes.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/EUPrintAttributes.kt deleted file mode 100644 index 01eb1c89e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/EUPrintAttributes.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.pdf - -import com.squareup.moshi.JsonClass -import java.time.OffsetDateTime -import org.json.JSONObject - -@JsonClass(generateAdapter = true) -data class EUPrintAttributes( - val dcc: JSONObject, - val expirationTime: OffsetDateTime, - val qr: String -) - -@JsonClass(generateAdapter = true) -data class PrintAttributes(val european: List) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/ExportIntroductionFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/ExportIntroductionFragment.kt deleted file mode 100644 index e013fe1a0..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/ExportIntroductionFragment.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.pdf - -import android.os.Bundle -import android.view.View -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentExportIntroductionBinding -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil -import org.koin.android.ext.android.inject - -class ExportIntroductionFragment : Fragment(R.layout.fragment_export_introduction) { - - private val androidUtil: AndroidUtil by inject() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentExportIntroductionBinding.bind(view) - binding.bottom.setButtonClick { - navigateSafety(ExportIntroductionFragmentDirections.actionPdfWebview()) - } - - if (androidUtil.isSmallScreen()) { - binding.image.isVisible = false - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PDfPreviewInfo.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PDfPreviewInfo.kt deleted file mode 100644 index 316d94d90..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PDfPreviewInfo.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.holder.pdf - -data class PDfPreviewInfo(val content: String, val initialZoom: Int) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfExportedFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfExportedFragment.kt deleted file mode 100644 index 2ddd454db..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfExportedFragment.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.pdf - -import android.content.Intent -import android.os.Bundle -import android.view.View -import androidx.core.content.FileProvider -import androidx.fragment.app.Fragment -import java.io.File -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentPdfExportedBinding -import nl.rijksoverheid.ctr.shared.ext.navigateSafety - -class PdfExportedFragment : Fragment(R.layout.fragment_pdf_exported) { - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentPdfExportedBinding.bind(view) - - binding.previewPdfButton.setOnClickListener { - navigateSafety( - PdfExportedFragmentDirections.actionPdfPreview( - toolbarTitle = PdfWebViewFragment.pdfFileName - ) - ) - } - - binding.savePdfButton.setOnClickListener { - val pdfFile = File(requireContext().filesDir, PdfWebViewFragment.pdfFileName) - startActivity( - Intent.createChooser(Intent().apply { - action = Intent.ACTION_SEND - type = "application/pdf" - putExtra( - Intent.EXTRA_STREAM, - FileProvider.getUriForFile( - requireContext(), - requireContext().applicationContext.packageName + ".provider", - pdfFile - ) - ) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - }, getString(R.string.holder_pdfExport_success_card_action_save)) - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreview.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreview.kt deleted file mode 100644 index 5ee745e18..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreview.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.holder.pdf - -sealed class PdfPreview { - class Success(val info: PDfPreviewInfo) : PdfPreview() - object Error : PdfPreview() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreviewFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreviewFragment.kt deleted file mode 100644 index 58624901b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreviewFragment.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.holder.pdf - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import nl.rijksoverheid.ctr.design.utils.DialogButtonData -import nl.rijksoverheid.ctr.design.utils.DialogFragmentData -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentPdfPreviewBinding -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import org.koin.androidx.viewmodel.ext.android.viewModel - -class PdfPreviewFragment : Fragment(R.layout.fragment_pdf_preview) { - - private val viewModel: PdfPreviewViewModel by viewModel() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentPdfPreviewBinding.bind(view) - - binding.pdfWebView.settings.builtInZoomControls = true - binding.pdfWebView.settings.displayZoomControls = false - binding.pdfWebView.settings.allowFileAccess = true - - viewModel.previewLiveData.observe(viewLifecycleOwner, EventObserver { - when (it) { - is PdfPreview.Success -> { - binding.pdfWebView.setInitialScale(it.info.initialZoom) - binding.pdfWebView.loadDataWithBaseURL( - "file:///android_asset/", - "", - "text/html", - "utf-8", - "" - ) - } - - is PdfPreview.Error -> { - navigateSafety( - PdfPreviewFragmentDirections.actionDialog( - data = DialogFragmentData( - title = R.string.holder_pdfExport_storageFull_errorDialog_title, - message = R.string.holder_pdfExport_storageFull_errorDialog_message, - positiveButtonData = DialogButtonData.NavigateUp(R.string.dialog_close) - ) - ) - ) - } - } - }) - - viewModel.generatePreview( - screenWidth = resources.displayMetrics.widthPixels, - filesDir = requireContext().filesDir - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreviewViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreviewViewModel.kt deleted file mode 100644 index 782e9d831..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfPreviewViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.holder.pdf - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import java.io.File -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.shared.livedata.Event - -abstract class PdfPreviewViewModel : ViewModel() { - val previewLiveData = MutableLiveData>() - abstract fun generatePreview(screenWidth: Int, filesDir: File) -} - -class PdfPreviewViewModelImpl( - private val previewPdfUseCase: PreviewPdfUseCase -) : PdfPreviewViewModel() { - override fun generatePreview(screenWidth: Int, filesDir: File) { - viewModelScope.launch { - val pdfPreviewResult = previewPdfUseCase.generatePreview(screenWidth, filesDir) - if (pdfPreviewResult != null) { - previewLiveData.postValue(Event(PdfPreview.Success(pdfPreviewResult))) - } else { - previewLiveData.postValue(Event(PdfPreview.Error)) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfWebViewFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfWebViewFragment.kt deleted file mode 100644 index ae7567c87..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfWebViewFragment.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.pdf - -import android.annotation.SuppressLint -import android.content.Context -import android.os.Bundle -import android.view.View -import android.webkit.JavascriptInterface -import android.webkit.WebView -import android.webkit.WebViewClient -import androidx.core.view.isVisible -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.holder.BaseFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentPdfWebviewBinding -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.shared.exceptions.LoadFileException -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.utils.Accessibility.makeIndeterminateAccessible -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -class PdfWebViewFragment : BaseFragment(R.layout.fragment_pdf_webview) { - - private val pdfWebViewModel: PdfWebViewModel by viewModel() - - private val cachedAppConfigUseCase: CachedAppConfigUseCase by inject() - - override fun onButtonClickWithRetryAction() { - // there is no action to retry in this screen - } - - override fun getFlow(): Flow { - return HolderFlow.Pdf - } - - // local js script, so we are safe - @SuppressLint("SetJavaScriptEnabled") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentPdfWebviewBinding.bind(view) - - binding.loading.makeIndeterminateAccessible( - context = requireContext(), - isLoading = true, - message = R.string.general_loading_description - ) - - pdfWebViewModel.loadingLiveData.observe(viewLifecycleOwner, EventObserver { - binding.loading.isVisible = it - navigateSafety(PdfWebViewFragmentDirections.actionPdfExported()) - }) - - pdfWebViewModel.errorLiveData.observe(viewLifecycleOwner, EventObserver { - error(AppErrorResult(HolderStep.PdfExport, it)) - }) - - binding.pdfWebView.webViewClient = object : WebViewClient() { - override fun onPageFinished(webView: WebView?, url: String?) { - super.onPageFinished(webView, url) - webView?.let { - pdfWebViewModel.generatePdf(it::evaluateJavascript) - } - } - } - binding.pdfWebView.addJavascriptInterface(this, "android") - binding.pdfWebView.settings.allowFileAccess = true - binding.pdfWebView.settings.javaScriptEnabled = true - - binding.pdfWebView.loadUrl("file:///android_res/raw/print_portal.html") - } - - @SuppressLint("StringFormatInvalid") - private fun error(errorResult: ErrorResult) { - val helpdeskNumber = cachedAppConfigUseCase.getCachedAppConfig().contactInfo.phoneNumber - presentError(errorResult, getString( - R.string.holder_pdfExport_error_body, - helpdeskNumber, - helpdeskNumber, - errorCodeStringFactory.get( - flow = getFlow(), - errorResults = listOf(errorResult) - ) - )) - } - - @JavascriptInterface - fun onData(value: String) { - if (value.startsWith(PdfWebViewModel.pdfMimeType)) { - pdfWebViewModel.storePdf( - requireContext().openFileOutput( - pdfFileName, - Context.MODE_PRIVATE - ), value - ) - } else { - val errorResult = AppErrorResult( - HolderStep.PdfExport, - LoadFileException() - ) - error(errorResult) - } - } - - companion object { - const val pdfFileName = "Coronacheck - International.pdf" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfWebViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfWebViewModel.kt deleted file mode 100644 index d462fbf4c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PdfWebViewModel.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.pdf - -import android.util.Base64 -import android.webkit.ValueCallback -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import java.io.File -import java.io.FileOutputStream -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigStorageManager -import nl.rijksoverheid.ctr.shared.exceptions.AppConfigMissingException -import nl.rijksoverheid.ctr.shared.exceptions.CreatePdfException -import nl.rijksoverheid.ctr.shared.exceptions.NoDccException -import nl.rijksoverheid.ctr.shared.exceptions.StorePdfException -import nl.rijksoverheid.ctr.shared.livedata.Event - -abstract class PdfWebViewModel : ViewModel() { - val loadingLiveData = MutableLiveData>() - val errorLiveData = MutableLiveData>() - abstract fun generatePdf(evaluateJavascript: (script: String, valueCallback: ValueCallback?) -> Unit) - abstract fun storePdf(fileOutputStream: FileOutputStream, contents: String) - - companion object { - const val pdfMimeType = "data:application/pdf;base64," - } -} - -class PdfWebViewModelImpl( - private val filesDirPath: String, - private val appConfigStorageManager: AppConfigStorageManager, - private val printExportDccUseCase: PrintExportDccUseCase -) : PdfWebViewModel() { - - override fun generatePdf(evaluateJavascript: (script: String, valueCallback: ValueCallback?) -> Unit) { - val appConfig = appConfigStorageManager - .getFileAsBufferedSource(File(filesDirPath, "config.json")) - - if (appConfig == null) { - errorLiveData.postValue(Event(AppConfigMissingException())) - return - } - - viewModelScope.launch { - val qrs = try { - printExportDccUseCase.export() - } catch (exception: Exception) { - errorLiveData.postValue(Event(NoDccException())) - return@launch - } - try { - val script = "generatePdf($appConfig, $qrs);" - evaluateJavascript(script, null) - } catch (exception: Exception) { - errorLiveData.postValue(Event(CreatePdfException())) - } - } - } - - override fun storePdf(fileOutputStream: FileOutputStream, contents: String) { - viewModelScope.launch(Dispatchers.IO) { - val base64Content = contents.replace("data:application/pdf;base64,", "") - try { - val base64DecodedContent = Base64.decode(base64Content, Base64.DEFAULT) - fileOutputStream.use { - it.write(base64DecodedContent) - it.flush() - } - } catch (exception: Exception) { - errorLiveData.postValue(Event(StorePdfException())) - } - loadingLiveData.postValue(Event(false)) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PreviewPdfUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PreviewPdfUseCase.kt deleted file mode 100644 index 6c6a30a9b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PreviewPdfUseCase.kt +++ /dev/null @@ -1,67 +0,0 @@ -package nl.rijksoverheid.ctr.holder.pdf - -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.pdf.PdfRenderer -import android.os.ParcelFileDescriptor -import android.util.Base64 -import java.io.ByteArrayOutputStream -import java.io.File -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -interface PreviewPdfUseCase { - suspend fun generatePreview(screenWidth: Int, filesDir: File): PDfPreviewInfo? -} - -class PreviewPdfUseCaseImpl : PreviewPdfUseCase { - override suspend fun generatePreview(screenWidth: Int, filesDir: File): PDfPreviewInfo? { - return withContext(Dispatchers.IO) { - try { - val pdfFile = File(filesDir, PdfWebViewFragment.pdfFileName) - val parcelFileDescriptor = - ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY) - val pdfRenderer = PdfRenderer(parcelFileDescriptor) - val bitmaps = mutableListOf() - for (i in 0 until pdfRenderer.pageCount) { - val currentPage = pdfRenderer.openPage(i) - val bitmap = Bitmap.createBitmap( - currentPage.width, - currentPage.height, - Bitmap.Config.ARGB_8888 - ) - currentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) - bitmaps.add(bitmap) - currentPage.close() - } - pdfRenderer.close() - - val bitmapWidth = bitmaps.first().width - val initialZoom = ((screenWidth.toFloat() / bitmapWidth.toFloat()) * 100).toInt() - - PDfPreviewInfo( - content = pdfBitmap(bitmaps), - initialZoom = initialZoom - ) - } catch (exception: Exception) { - null - } - } - } - - private fun pdfBitmap(bitmaps: List): String { - val width = bitmaps.first().width - val pageHeight = bitmaps.first().height - val height = bitmaps.first().height * bitmaps.size - val comboBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val comboImage = Canvas(comboBitmap) - bitmaps.forEachIndexed { index, bitmap -> - comboImage.drawBitmap(bitmap, 0f, (index * pageHeight).toFloat(), null) - } - - val byteArrayOutputStream = ByteArrayOutputStream() - comboBitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) - val byteArray = byteArrayOutputStream.toByteArray() - return "data:image/png;base64,${Base64.encodeToString(byteArray, Base64.DEFAULT)}" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PrintExportDccUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PrintExportDccUseCase.kt deleted file mode 100644 index bf99f94eb..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/pdf/PrintExportDccUseCase.kt +++ /dev/null @@ -1,37 +0,0 @@ -package nl.rijksoverheid.ctr.holder.pdf - -import com.squareup.moshi.Moshi -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper - -interface PrintExportDccUseCase { - suspend fun export(): String -} - -class PrintExportDccUseCaseImpl( - private val holderDatabase: HolderDatabase, - private val mobileCoreWrapper: MobileCoreWrapper, - private val moshi: Moshi -) : PrintExportDccUseCase { - override suspend fun export(): String { - val credentials = holderDatabase.greenCardDao().getAll() - .filter { it.greenCardEntity.type == GreenCardType.Eu && it.credentialEntities.isNotEmpty() } - .sortedBy { - it.origins.firstOrNull()?.eventTime - } - .map { it.credentialEntities.last() } - - val printAttributes = PrintAttributes( - european = credentials.map { - EUPrintAttributes( - dcc = mobileCoreWrapper.readEuropeanCredential(it.data).getJSONObject("dcc"), - expirationTime = it.expirationTime, - qr = String(it.data) - ) - } - ) - - return moshi.adapter(PrintAttributes::class.java).toJson(printAttributes) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodeAnimationWidget.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodeAnimationWidget.kt deleted file mode 100644 index 6ed4c71f3..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodeAnimationWidget.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes - -import android.content.Context -import android.util.AttributeSet -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.annotation.RawRes -import com.airbnb.lottie.LottieAnimationView -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.shared.models.Environment - -class QrCodeAnimationWidget(context: Context, attrs: AttributeSet?) : - FrameLayout(context, attrs) { - - private val animationView: LottieAnimationView - - private val background: ImageView - - init { - val view = inflate(context, R.layout.widget_qr_code_animation, this) - animationView = view.findViewById(R.id.animation_view) - background = view.findViewById(R.id.image) - animationView.setIgnoreDisabledSystemAnimations(Environment.get(context) is Environment.Prod) - setOnClickListener { - animationView.run { - scaleX = if (scaleX == 1F) -1F else 1F - playAnimation() - } - } - } - - fun setWidget(@RawRes animation: Int) { - animationView.setAnimation(animation) - animationView.playAnimation() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodePagerAdapter.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodePagerAdapter.kt deleted file mode 100644 index 0576b8eec..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodePagerAdapter.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.ViewQrCodeBinding -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeData - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class QrCodePagerAdapter(private val onOverlayExplanationClick: (QrCodeViewHolder.QrCodeVisibility) -> Unit) : - RecyclerView.Adapter() { - - val qrCodeDataList: MutableList = mutableListOf() - - private var currentPosition = 0 - - private val overlayVisibilityStates = mutableListOf() - - fun addData(data: List) { - val hasItems = qrCodeDataList.isNotEmpty() - val hideOverlayInCurrentPosition = - overlayVisibilityStates.getOrNull(currentPosition) == QrCodeViewHolder.QrCodeVisibility.VISIBLE - qrCodeDataList.clear() - overlayVisibilityStates.clear() - data.forEachIndexed { index, it -> - // if user chose to display the QR, don't hide it until scrolled away from it - if (index == currentPosition && hideOverlayInCurrentPosition) { - overlayVisibilityStates.add(QrCodeViewHolder.QrCodeVisibility.VISIBLE) - } else { - overlayVisibilityStates.add(isQrCodeHidden(it)) - } - } - qrCodeDataList.addAll(data) - if (hasItems) { - notifyItemRangeChanged(0, data.size) - } else { - notifyItemRangeInserted(0, data.size) - } - } - - private fun isQrCodeHidden(data: QrCodeData): QrCodeViewHolder.QrCodeVisibility { - val vaccinationData = data as? QrCodeData.European.Vaccination - return when { - (data as? QrCodeData.European)?.isExpired == true -> QrCodeViewHolder.QrCodeVisibility.EXPIRED - vaccinationData?.isDoseNumberSmallerThanTotalDose == true -> QrCodeViewHolder.QrCodeVisibility.HIDDEN - else -> QrCodeViewHolder.QrCodeVisibility.VISIBLE - } - } - - fun onPositionChanged(position: Int) { - currentPosition = position - overlayVisibilityStates.forEachIndexed { index, _ -> - overlayVisibilityStates[index] = isQrCodeHidden(qrCodeDataList[index]) - } - notifyItemChanged(position) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QrCodeViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.view_qr_code, parent, false) - return QrCodeViewHolder(view) - } - - override fun onBindViewHolder(holder: QrCodeViewHolder, position: Int) { - holder.bind( - qrCodeDataList[position], - position == currentPosition, - overlayVisibilityStates[position], - onOverlayExplanationClick - ) { - overlayVisibilityStates[currentPosition] = QrCodeViewHolder.QrCodeVisibility.VISIBLE - notifyItemChanged(currentPosition) - } - } - - override fun getItemCount(): Int { - return qrCodeDataList.size - } -} - -class QrCodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - - private val binding = ViewQrCodeBinding.bind(itemView) - - fun bind( - qrCodeData: QrCodeData, - isCurrentlyDisplayed: Boolean, - qrCodeVisibility: QrCodeVisibility, - onOverlayExplanationClick: (QrCodeVisibility) -> Unit, - onOverlayButtonClick: () -> Unit - ) { - val showOverlay = qrCodeVisibility != QrCodeVisibility.VISIBLE - - binding.image.setImageBitmap(qrCodeData.bitmap) - binding.overlayShowQrButton.setOnClickListener { - onOverlayButtonClick.invoke() - } - - if (qrCodeData is QrCodeData.European.NonVaccination && !qrCodeData.explanationNeeded) { - binding.overlayButton.isVisible = false - } else { - binding.overlayButton.setOnClickListener { - onOverlayExplanationClick.invoke(qrCodeVisibility) - } - } - - // using View.INVISIBLE instead View.GONE cause the latter breaks - // the click listener for physical keyboards accessibility - setOverlay(showOverlay, qrCodeVisibility) - - // not visible pages can also gain focus, so we have to take care of that for hardware keyboard users - binding.image.isFocusable = isCurrentlyDisplayed && !showOverlay - binding.image.importantForAccessibility = if (isCurrentlyDisplayed && !showOverlay) { - View.IMPORTANT_FOR_ACCESSIBILITY_YES - } else { - View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - binding.overlay.isFocusable = isCurrentlyDisplayed && showOverlay - } - - private fun setOverlay( - showOverlay: Boolean, - qrCodeVisibility: QrCodeVisibility - ) { - binding.overlay.visibility = if (showOverlay) { - View.VISIBLE - } else { - View.INVISIBLE - } - binding.overlayText.text = itemView.context.getString( - if (qrCodeVisibility == QrCodeVisibility.HIDDEN) { - R.string.qr_code_hidden_title } else { - R.string.holder_qr_code_expired_overlay_title - } - ) - binding.overlayText.setCompoundDrawablesWithIntrinsicBounds( - 0, - when (qrCodeVisibility) { - QrCodeVisibility.HIDDEN -> R.drawable.ic_visibility_off - QrCodeVisibility.EXPIRED -> R.drawable.ic_qr_hidden - else -> 0 - }, - 0, - 0 - ) - } - - enum class QrCodeVisibility { - HIDDEN, EXPIRED, VISIBLE - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodesFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodesFragment.kt deleted file mode 100644 index 6b9e791a6..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodesFragment.kt +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes - -import android.annotation.SuppressLint -import android.content.ActivityNotFoundException -import android.content.pm.ActivityInfo -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.view.View -import android.view.WindowManager -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import androidx.viewpager2.widget.ViewPager2 -import java.util.concurrent.TimeUnit -import nl.rijksoverheid.ctr.appconfig.models.ExternalReturnAppData -import nl.rijksoverheid.ctr.appconfig.usecases.ClockDeviationUseCase -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.BuildConfig -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentQrCodesBinding -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeAnimation -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeData -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodesResult -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrCodesFragmentUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrInfoScreenUtil -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety -import nl.rijksoverheid.ctr.shared.utils.Accessibility.addAccessibilityAction -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class QrCodesFragment : Fragment(R.layout.fragment_qr_codes) { - - private companion object { - const val ORIENTATION_CHANGED = "orientationChanged" - } - - private var _binding: FragmentQrCodesBinding? = null - private val binding get() = _binding!! - private fun safeBindingBlock(block: (binding: FragmentQrCodesBinding) -> Unit) { - _binding?.run(block) - } - - private val args: QrCodesFragmentArgs by navArgs() - private val featureFlagUseCase: HolderFeatureFlagUseCase by inject() - private val infoScreenUtil: QrInfoScreenUtil by inject() - private val dialogUtil: DialogUtil by inject() - private val infoFragmentUtil: InfoFragmentUtil by inject() - private val cachedAppConfigUseCase: HolderCachedAppConfigUseCase by inject() - private val qrCodesFragmentUtil: QrCodesFragmentUtil by inject() - private lateinit var qrCodePagerAdapter: QrCodePagerAdapter - - private val qrCodeHandler = Handler(Looper.getMainLooper()) - private val qrCodeRunnable = Runnable { - generateQrCodes() - } - - private val qrCodeViewModel: QrCodesViewModel by viewModel() - private val clockDeviationUseCase: ClockDeviationUseCase by inject() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - if (BuildConfig.FLAVOR.lowercase().contains("prod")) { - requireActivity().window.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE - ) - } - val params = requireActivity().window.attributes - params?.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL - requireActivity().window.attributes = params - - // If there is a savedInstanceState and it's not because of orientation change, we treat this as process death occurred. - // In that case QrCodesFragment is launched before the init of the app, which we do not want. - if (savedInstanceState != null && - !savedInstanceState.getBoolean(ORIENTATION_CHANGED, false) - ) { - findNavControllerSafety()?.popBackStack() - } - } - - @SuppressLint("SourceLockedOrientationActivity") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - - _binding = FragmentQrCodesBinding.bind(view) - - setupViewPager() - dispatchTouchEventDoseInfo() - - qrCodeViewModel.qrCodeDataListLiveData.observe(viewLifecycleOwner, ::bindQrCodeDataList) - qrCodeViewModel.returnAppLivedata.observe(viewLifecycleOwner, ::returnToApp) - qrCodeViewModel.animationLiveData.observe(viewLifecycleOwner, ::applyAnimation) - clockDeviationUseCase.serverTimeSyncedLiveData.observe(viewLifecycleOwner) { onServerTimeSynced() } - - args.returnUri?.let { qrCodeViewModel.onReturnUriGiven(it, args.data.type) } - qrCodeViewModel.getAnimation(args.data.type) - } - - /** - * Whenever we sync the server time, generate new qr codes as the qr code holds the (possibly adjusted) time - */ - private fun onServerTimeSynced() { - generateQrCodes() - } - - /** - * Dispatch touch events on the overlapping dose info view to have the animation view mirror itself. - */ - @SuppressLint("ClickableViewAccessibility") - private fun dispatchTouchEventDoseInfo() { - binding.doseInfo.setOnTouchListener { _, event -> - binding.animation.dispatchTouchEvent(event) - true - } - } - - private fun setupViewPager() { - qrCodePagerAdapter = QrCodePagerAdapter(::onOverlayExplanationClick) - binding.viewPager.adapter = qrCodePagerAdapter - } - - private fun onOverlayExplanationClick(qrCodeVisibility: QrCodeViewHolder.QrCodeVisibility) { - infoFragmentUtil.presentAsBottomSheet( - childFragmentManager, InfoFragmentData.TitleDescriptionWithButton( - title = getString( - if (qrCodeVisibility == QrCodeViewHolder.QrCodeVisibility.EXPIRED) { - R.string.holder_qr_code_expired_explanation_title - } else { - R.string.holder_qr_code_hidden_explanation_title - } - ), - descriptionData = DescriptionData( - htmlTextString = getString( - if (qrCodeVisibility == QrCodeViewHolder.QrCodeVisibility.EXPIRED) { - if (featureFlagUseCase.isInArchiveMode()) { - if (args.data.originType is OriginType.Vaccination) { - R.string.holder_qr_code_expired_explanation_description_archive_vaccination - } else { - R.string.holder_qr_code_expired_explanation_description_archive_recovery - } - } else { - R.string.holder_qr_code_expired_explanation_description - } - } else { - if (featureFlagUseCase.isInArchiveMode()) { - R.string.holder_qr_code_hidden_explanation_description_archive_vaccination - } else { - R.string.holder_qr_code_hidden_explanation_description - } - } - ), - htmlLinksEnabled = true - ), - primaryButtonData = if (featureFlagUseCase.isInArchiveMode()) { - null - } else { - ButtonData.LinkButton( - text = getString( - if (qrCodeVisibility == QrCodeViewHolder.QrCodeVisibility.EXPIRED) { - R.string.holder_qr_code_expired_explanation_action - } else { - R.string.holder_qr_code_hidden_explanation_action - } - ), - link = getString( - if (qrCodeVisibility == QrCodeViewHolder.QrCodeVisibility.EXPIRED) { - R.string.holder_qr_code_expired_explanation_url - } else { - R.string.holder_qr_code_hidden_explanation_url - } - ) - ) - } - ) - ) - } - - private fun applyAnimation(qrCodeAnimation: QrCodeAnimation) { - binding.animation.setWidget(qrCodeAnimation.animationResource) - binding.animation.contentDescription = getString(qrCodeAnimation.contentDescription) - binding.animation.addAccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - getString(R.string.holder_showqr_animation_voiceover_hint) - ) - } - - private fun returnToApp(externalReturnAppData: ExternalReturnAppData) { - binding.button.run { - visibility = View.VISIBLE - text = getString(R.string.qr_code_return_app_button, externalReturnAppData.appName) - setOnClickListener { startIntent(externalReturnAppData) } - } - } - - private fun startIntent(externalReturnAppData: ExternalReturnAppData) { - try { - startActivity(externalReturnAppData.intent) - } catch (exception: ActivityNotFoundException) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.dialog_error_title, - message = getString(R.string.dialog_error_message), - positiveButtonText = R.string.dialog_close, - positiveButtonCallback = {} - ) - } - } - - private fun bindQrCodeDataList(qrCodesResult: QrCodesResult) { - presentQrLoading(false) - - when (qrCodesResult) { - is QrCodesResult.SingleQrCode -> { - qrCodePagerAdapter.addData(listOf(qrCodesResult.qrCodeData)) - binding.vaccinationQrsContainer.visibility = View.GONE - } - is QrCodesResult.MultipleQrCodes -> { - qrCodePagerAdapter.addData(qrCodesResult.europeanVaccinationQrCodeDataList) - if (binding.qrVaccinationIndicators.visibility == View.GONE) { - // Setup extra viewpager UI only once - setupEuropeanVaccinationQr( - qrCodesResult.europeanVaccinationQrCodeDataList, - qrCodesResult.mostRelevantVaccinationIndex - ) - } - } - } - - // Nullable so tests don't trip over parentFragment - (parentFragment?.parentFragment as HolderMainFragment?)?.getToolbar().let { toolbar -> - if (toolbar?.menu?.size() == 0) { - toolbar.apply { - inflateMenu(R.menu.my_qr_toolbar) - - setOnMenuItemClickListener { - val qrCodeData = - qrCodePagerAdapter.qrCodeDataList.get(binding.viewPager.currentItem) - if (it.itemId == R.id.action_show_qr_explanation) { - val infoScreen = when (qrCodeData) { - is QrCodeData.European -> { - when (args.data.originType) { - is OriginType.Test -> { - infoScreenUtil.getForEuropeanTestQr( - qrCodeData.readEuropeanCredential - ) - } - is OriginType.Vaccination -> { - infoScreenUtil.getForEuropeanVaccinationQr( - qrCodeData.readEuropeanCredential - ) - } - is OriginType.Recovery -> { - infoScreenUtil.getForEuropeanRecoveryQr( - qrCodeData.readEuropeanCredential - ) - } - } - } - } - - infoFragmentUtil.presentAsBottomSheet( - childFragmentManager, InfoFragmentData.TitleDescriptionWithFooter( - title = infoScreen.title, - descriptionData = DescriptionData( - htmlTextString = infoScreen.description, - htmlLinksEnabled = true - ), - footerText = infoScreen.footer - ) - ) - } - true - } - } - } - } - } - - /** - * Show extra UI when we are dealing with european vaccination qrs - */ - private fun setupEuropeanVaccinationQr( - europeanVaccinations: List, - mostRelevantVaccinationIndex: Int - ) { - // Make extra UI visible to show more information about the QR - binding.vaccinationQrsContainer.visibility = View.VISIBLE - binding.qrVaccinationDose.visibility = View.VISIBLE - val doses = getString( - R.string.qr_code_dosis, - "${europeanVaccinations.first().dose}/${europeanVaccinations.first().ofTotalDoses}" - ) - binding.qrVaccinationDose.text = doses - binding.qrVaccinationDose.contentDescription = doses - - // If there are more then one vaccinations we update UI based on the selected page - if (europeanVaccinations.size > 1) { - // Initialize our viewpager indicators - binding.qrVaccinationIndicators.visibility = View.VISIBLE - binding.qrVaccinationIndicators.initIndicator(europeanVaccinations.size) - - binding.viewPager.registerOnPageChangeCallback(object : - ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - - // Select current indicator - binding.qrVaccinationIndicators.updateSelected(position) - - binding.root.post { - onPageSelectedPostAction(position, europeanVaccinations) - } - - qrCodePagerAdapter.onPositionChanged(position) - } - }) - - // Default select the last item - binding.viewPager.setCurrentItem(mostRelevantVaccinationIndex, false) - - // Make buttons click to scroll through viewpager - binding.previousQrButton.setOnClickListener { - binding.viewPager.setCurrentItem(binding.viewPager.currentItem - 1, true) - } - - binding.nextQrButton.setOnClickListener { - binding.viewPager.setCurrentItem(binding.viewPager.currentItem + 1, true) - } - } - } - - private fun onPageSelectedPostAction( - position: Int, - europeanVaccinations: List - ) { - safeBindingBlock { binding -> - binding.nextQrButton.visibility = - if (position == europeanVaccinations.size - 1) View.INVISIBLE else View.VISIBLE - binding.previousQrButton.visibility = - if (position == 0) View.INVISIBLE else View.VISIBLE - - val vaccination = europeanVaccinations[position] - val doses = getString( - R.string.qr_code_dosis, - "${vaccination.dose}/${vaccination.ofTotalDoses}" - ) - binding.qrVaccinationDose.text = doses - binding.qrVaccinationDose.contentDescription = doses - } - } - - private fun presentQrLoading(loading: Boolean) { - (parentFragment?.parentFragment as? HolderMainFragment)?.presentLoading(loading) - binding.root.visibility = if (loading) View.GONE else View.VISIBLE - } - - private fun generateQrCodes() { - checkShouldAutomaticallyClose() - qrCodeViewModel.generateQrCodes( - qrCodeFragmentData = args.data, - size = resources.displayMetrics.widthPixels - ) - val refreshMillis = - if (BuildConfig.FLAVOR == "tst") TimeUnit.SECONDS.toMillis(10) else TimeUnit.SECONDS.toMillis( - cachedAppConfigUseCase.getCachedAppConfig().domesticQRRefreshSeconds.toLong() - ) - - // Make sure there is only 1 callback as multiple qr generations can be triggered by onResume and server time LiveData - qrCodeHandler.removeCallbacks(qrCodeRunnable) - qrCodeHandler.postDelayed(qrCodeRunnable, refreshMillis) - } - - /** - * Checks if this fragment should automatically close - */ - private fun checkShouldAutomaticallyClose() { - val shouldClose = qrCodesFragmentUtil.shouldClose( - args.data.credentialsWithExpirationTime.last().second.toEpochSecond(), - args.data.type - ) - if (shouldClose) { - findNavControllerSafety()?.popBackStack() - } - } - - override fun onResume() { - super.onResume() - presentQrLoading(true) - generateQrCodes() - } - - override fun onPause() { - super.onPause() - (parentFragment?.parentFragment as HolderMainFragment).let { - it.getToolbar().menu.clear() - // Reset menu item listener to default - it.resetMenuItemListener() - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - qrCodeHandler.removeCallbacks(qrCodeRunnable) - - // Set brightness back to previous - val params = requireActivity().window.attributes - params?.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE - requireActivity().window.attributes = params - - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(false) - - requireActivity().requestedOrientation = - ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putBoolean(ORIENTATION_CHANGED, requireActivity().isChangingConfigurations) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodesViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodesViewModel.kt deleted file mode 100644 index c47a7c894..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/QrCodesViewModel.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.appconfig.models.ExternalReturnAppData -import nl.rijksoverheid.ctr.appconfig.usecases.ReturnToExternalAppUseCase -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeAnimation -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeFragmentData -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodesResult -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodeAnimationUseCase -import nl.rijksoverheid.ctr.holder.qrcodes.usecases.QrCodesResultUseCase -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -abstract class QrCodesViewModel : ViewModel() { - val qrCodeDataListLiveData = MutableLiveData() - val returnAppLivedata = MutableLiveData() - val animationLiveData = MutableLiveData() - abstract fun generateQrCodes( - qrCodeFragmentData: QrCodeFragmentData, - size: Int - ) - - abstract fun onReturnUriGiven(uri: String, type: GreenCardType) - abstract fun getAnimation(greenCardType: GreenCardType) -} - -class QrCodesViewModelImpl( - private val qrCodesResultUseCase: QrCodesResultUseCase, - private val returnToExternalAppUseCase: ReturnToExternalAppUseCase, - private val qrCodeAnimationUseCase: QrCodeAnimationUseCase -) : QrCodesViewModel() { - - override fun generateQrCodes( - qrCodeFragmentData: QrCodeFragmentData, - size: Int - ) { - viewModelScope.launch { - val result = qrCodesResultUseCase.getQrCodesResult( - qrCodeFragmentData = qrCodeFragmentData, - qrCodeWidth = size, - qrCodeHeight = size - ) - qrCodeDataListLiveData.postValue( - result - ) - } - } - - override fun onReturnUriGiven(uri: String, type: GreenCardType) { - } - - override fun getAnimation(greenCardType: GreenCardType) { - animationLiveData.postValue( - qrCodeAnimationUseCase.get(greenCardType) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeAnimation.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeAnimation.kt deleted file mode 100644 index 6835f4bbf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeAnimation.kt +++ /dev/null @@ -1,18 +0,0 @@ -package nl.rijksoverheid.ctr.holder.qrcodes.models - -import androidx.annotation.RawRes -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ -sealed class QrCodeAnimation(@RawRes val animationResource: Int, @StringRes val contentDescription: Int) { - object DomesticWinter : QrCodeAnimation(R.raw.winter_domestic, R.string.holder_showqr_animation_winterctb_voiceover_label) - object EuWinter : QrCodeAnimation(R.raw.winter_international, R.string.holder_showqr_animation_winterdcc_voiceover_label) - object DomesticSummer : QrCodeAnimation(R.raw.summer_domestic, R.string.holder_showqr_animation_summerctb_voiceover_label) - object EuSummer : QrCodeAnimation(R.raw.summer_international, R.string.holder_showqr_animation_summerdcc_voiceover_label) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeData.kt deleted file mode 100644 index 20b297d96..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeData.kt +++ /dev/null @@ -1,32 +0,0 @@ -package nl.rijksoverheid.ctr.holder.qrcodes.models - -import android.graphics.Bitmap -import org.json.JSONObject - -sealed class QrCodeData( - open val bitmap: Bitmap -) { - - sealed class European( - override val bitmap: Bitmap, - open val isExpired: Boolean, - open val readEuropeanCredential: JSONObject - ) : QrCodeData(bitmap) { - - data class Vaccination( - val dose: String, - val ofTotalDoses: String, - val isDoseNumberSmallerThanTotalDose: Boolean, - override val isExpired: Boolean, - override val bitmap: Bitmap, - override val readEuropeanCredential: JSONObject - ) : European(bitmap, isExpired, readEuropeanCredential) - - data class NonVaccination( - override val isExpired: Boolean, - override val bitmap: Bitmap, - override val readEuropeanCredential: JSONObject, - val explanationNeeded: Boolean - ) : European(bitmap, isExpired, readEuropeanCredential) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeFragmentData.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeFragmentData.kt deleted file mode 100644 index 9a9996cdf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodeFragmentData.kt +++ /dev/null @@ -1,25 +0,0 @@ -package nl.rijksoverheid.ctr.holder.qrcodes.models - -import android.os.Parcelable -import java.time.OffsetDateTime -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -@Parcelize -data class QrCodeFragmentData( - val type: GreenCardType, - val originType: OriginType, - val credentialsWithExpirationTime: List>, - val shouldDisclose: ShouldDisclose -) : Parcelable { - - sealed class ShouldDisclose : Parcelable { - - @Parcelize - object DoNotDisclose : ShouldDisclose(), Parcelable - - @Parcelize - data class Disclose(val greenCardId: Int) : ShouldDisclose(), Parcelable - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodesResult.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodesResult.kt deleted file mode 100644 index 28cfa6547..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/QrCodesResult.kt +++ /dev/null @@ -1,11 +0,0 @@ -package nl.rijksoverheid.ctr.holder.qrcodes.models - -sealed class QrCodesResult { - data class SingleQrCode(val qrCodeData: QrCodeData) : QrCodesResult() - - // Only european vaccinations support multiple qrs - data class MultipleQrCodes( - val europeanVaccinationQrCodeDataList: List, - val mostRelevantVaccinationIndex: Int - ) : QrCodesResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/ReadEuropeanCredentialUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/ReadEuropeanCredentialUtil.kt deleted file mode 100644 index a3fa6a75d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/models/ReadEuropeanCredentialUtil.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.models - -import android.app.Application -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.shared.ext.getStringOrNull -import org.json.JSONObject - -interface ReadEuropeanCredentialUtil { - fun getDose(readEuropeanCredential: JSONObject): String? - fun getOfTotalDoses(readEuropeanCredential: JSONObject): String? - fun getDoseRangeStringForVaccination(readEuropeanCredential: JSONObject): String - fun doseExceedsTotalDoses(readEuropeanCredential: JSONObject): Boolean -} - -class ReadEuropeanCredentialUtilImpl(private val application: Application) : - ReadEuropeanCredentialUtil { - - override fun getDose(readEuropeanCredential: JSONObject): String? { - val vaccination = getVaccination(readEuropeanCredential) - return vaccination?.getStringOrNull("dn") - } - - override fun getOfTotalDoses(readEuropeanCredential: JSONObject): String? { - val vaccination = getVaccination(readEuropeanCredential) - return vaccination?.getStringOrNull("sd") - } - - override fun getDoseRangeStringForVaccination(readEuropeanCredential: JSONObject): String { - val vaccination = getVaccination(readEuropeanCredential) - val dose = vaccination?.getStringOrNull("dn") ?: "" - val totalDoses = vaccination?.getStringOrNull("sd") ?: "" - - val doses = - if (dose.isNotEmpty() && totalDoses.isNotEmpty()) { - application.getString( - R.string.your_vaccination_explanation_doses_answer, dose, totalDoses - ) - } else "" - - return doses - } - - override fun doseExceedsTotalDoses(readEuropeanCredential: JSONObject): Boolean { - val dose = getDose(readEuropeanCredential)?.toInt() ?: 0 - val ofTotalDoses = getOfTotalDoses(readEuropeanCredential)?.toInt() ?: 0 - return dose > ofTotalDoses - } - - private fun getVaccination(readEuropeanCredential: JSONObject): JSONObject? { - val dcc = readEuropeanCredential.optJSONObject("dcc") - val vaccination = dcc?.getJSONArray("v")?.optJSONObject(0) - return vaccination - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodeAnimationUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodeAnimationUseCase.kt deleted file mode 100644 index 42363319e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodeAnimationUseCase.kt +++ /dev/null @@ -1,47 +0,0 @@ -package nl.rijksoverheid.ctr.holder.qrcodes.usecases - -import java.time.Clock -import java.util.Calendar -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeAnimation -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface QrCodeAnimationUseCase { - fun get(greenCardType: GreenCardType): QrCodeAnimation -} - -class QrCodeAnimationUseCaseImpl( - private val clock: Clock -) : QrCodeAnimationUseCase { - - override fun get(greenCardType: GreenCardType): QrCodeAnimation { - val now = Calendar.getInstance().apply { - timeInMillis = clock.millis() - } - val dayOfMonth = now.get(Calendar.DAY_OF_MONTH) - val month = now.get(Calendar.MONTH) - val isSummer = - (dayOfMonth >= startDay && month == Calendar.MARCH) || - month in (Calendar.APRIL..Calendar.NOVEMBER) || - (dayOfMonth <= endDay && month == Calendar.DECEMBER) - - return when (greenCardType) { - GreenCardType.Eu -> if (isSummer) { - QrCodeAnimation.EuSummer - } else { - QrCodeAnimation.EuWinter - } - } - } - - companion object { - private const val startDay = 21 - private const val endDay = 20 - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodeUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodeUseCase.kt deleted file mode 100644 index ded02b148..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodeUseCase.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.usecases - -import android.graphics.Bitmap -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.appconfig.usecases.ClockDeviationUseCase -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeFragmentData -import nl.rijksoverheid.ctr.holder.qrcodes.utils.QrCodeUtil -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface QrCodeUseCase { - suspend fun qrCode(credential: ByteArray, shouldDisclose: QrCodeFragmentData.ShouldDisclose, qrCodeWidth: Int, qrCodeHeight: Int, errorCorrectionLevel: ErrorCorrectionLevel): Bitmap -} - -class QrCodeUseCaseImpl( - private val holderDatabase: HolderDatabase, - private val qrCodeUtil: QrCodeUtil, - private val mobileCoreWrapper: MobileCoreWrapper, - private val clockDeviationUseCase: ClockDeviationUseCase -) : QrCodeUseCase { - - override suspend fun qrCode( - credential: ByteArray, - shouldDisclose: QrCodeFragmentData.ShouldDisclose, - qrCodeWidth: Int, - qrCodeHeight: Int, - errorCorrectionLevel: ErrorCorrectionLevel - ): Bitmap = - withContext(Dispatchers.IO) { - val qrCodeContent = String(credential) - - qrCodeUtil.createQrCode( - qrCodeContent = qrCodeContent, - width = qrCodeWidth, - height = qrCodeHeight, - errorCorrectionLevel = errorCorrectionLevel - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodesResultUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodesResultUseCase.kt deleted file mode 100644 index fcde1a6ad..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/usecases/QrCodesResultUseCase.kt +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.usecases - -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.holder.dashboard.util.CredentialUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeData -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeFragmentData -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodesResult -import nl.rijksoverheid.ctr.holder.qrcodes.models.ReadEuropeanCredentialUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.MultipleQrCodesUtil -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper - -/** - * Get all data needed to display QR codes based on data is send from the dashboard - */ -interface QrCodesResultUseCase { - suspend fun getQrCodesResult( - qrCodeFragmentData: QrCodeFragmentData, - qrCodeWidth: Int, - qrCodeHeight: Int - ): QrCodesResult -} - -class QrCodesResultUseCaseImpl( - private val qrCodeUseCase: QrCodeUseCase, - private val greenCardUtil: GreenCardUtil, - private val mobileCoreWrapper: MobileCoreWrapper, - private val readEuropeanCredentialUtil: ReadEuropeanCredentialUtil, - private val credentialUtil: CredentialUtil, - private val multipleQrCodesUtil: MultipleQrCodesUtil -) : QrCodesResultUseCase { - - override suspend fun getQrCodesResult( - qrCodeFragmentData: QrCodeFragmentData, - qrCodeWidth: Int, - qrCodeHeight: Int - ): QrCodesResult { - val greenCardType = qrCodeFragmentData.type - val credentialsWithExpirationTime = qrCodeFragmentData.credentialsWithExpirationTime - val credentials = credentialsWithExpirationTime.map { it.first } - val shouldDisclose = qrCodeFragmentData.shouldDisclose - val originType = qrCodeFragmentData.originType - - return when (greenCardType) { - is GreenCardType.Eu -> { - if (originType is OriginType.Vaccination) { - getQrCodesResultForEuropeanVaccination( - greenCardType = greenCardType, - credentialsWithExpirationTime = credentialsWithExpirationTime, - shouldDisclose = shouldDisclose, - qrCodeWidth = qrCodeWidth, - qrCodeHeight = qrCodeHeight - ) - } else { - getQrCodesResultForNonVaccination( - greenCardType = greenCardType, - originType = originType, - credentialsWithExpirationTime = credentialsWithExpirationTime, - credentials = credentials, - shouldDisclose = shouldDisclose, - qrCodeWidth = qrCodeWidth, - qrCodeHeight = qrCodeHeight - ) - } - } - } - } - - private suspend fun getQrCodesResultForEuropeanVaccination( - greenCardType: GreenCardType, - credentialsWithExpirationTime: List>, - shouldDisclose: QrCodeFragmentData.ShouldDisclose, - qrCodeWidth: Int, - qrCodeHeight: Int - ): QrCodesResult.MultipleQrCodes { - val europeanVaccinationQrCodeDataList = mapToEuropeanVaccinations( - credentialsWithExpirationTime, qrCodeWidth, qrCodeHeight, shouldDisclose, greenCardType - ) - - return QrCodesResult.MultipleQrCodes( - europeanVaccinationQrCodeDataList = europeanVaccinationQrCodeDataList, - mostRelevantVaccinationIndex = multipleQrCodesUtil.getMostRelevantQrCodeIndex( - europeanVaccinationQrCodeDataList - ) - ) - } - - private suspend fun mapToEuropeanVaccinations( - credentialsWithExpirationTime: List>, - qrCodeWidth: Int, - qrCodeHeight: Int, - shouldDisclose: QrCodeFragmentData.ShouldDisclose, - greenCardType: GreenCardType - ): List { - val readEuropeanCredentials = - credentialsWithExpirationTime.map { mobileCoreWrapper.readEuropeanCredential(it.first) } - return credentialsWithExpirationTime.mapIndexed { index, credentialWithExpirationTime -> - val credentials = credentialWithExpirationTime.first - val credentialExpirationTimeSeconds = - credentialWithExpirationTime.second.toEpochSecond() - val qrCodeBitmap = qrCodeUseCase.qrCode( - credential = credentials, - qrCodeWidth = qrCodeWidth, - qrCodeHeight = qrCodeHeight, - shouldDisclose = shouldDisclose, - errorCorrectionLevel = greenCardUtil.getErrorCorrectionLevel(greenCardType) - ) - - val readEuropeanCredential = readEuropeanCredentials[index] - val dose = readEuropeanCredentialUtil.getDose(readEuropeanCredential) ?: "" - val totalDoses = - readEuropeanCredentialUtil.getOfTotalDoses(readEuropeanCredential) ?: "" - - val isExpired = - credentialUtil.europeanCredentialHasExpired(credentialExpirationTimeSeconds) - val isDoseSmaller = - credentialUtil.vaccinationShouldBeHidden(readEuropeanCredentials, index) - QrCodeData.European.Vaccination( - dose = dose, - ofTotalDoses = totalDoses, - bitmap = qrCodeBitmap, - readEuropeanCredential = readEuropeanCredential, - isExpired = isExpired, - isDoseNumberSmallerThanTotalDose = isDoseSmaller - ) - } - } - - private suspend fun getQrCodesResultForNonVaccination( - greenCardType: GreenCardType, - originType: OriginType, - credentialsWithExpirationTime: List>, - credentials: List, - shouldDisclose: QrCodeFragmentData.ShouldDisclose, - qrCodeWidth: Int, - qrCodeHeight: Int - ): QrCodesResult.SingleQrCode { - val credential = credentials.first() - val qrCodeBitmap = qrCodeUseCase.qrCode( - credential = credential, - qrCodeWidth = qrCodeWidth, - qrCodeHeight = qrCodeHeight, - shouldDisclose = shouldDisclose, - errorCorrectionLevel = greenCardUtil.getErrorCorrectionLevel(greenCardType) - ) - - val credentialsExpired = credentialsWithExpirationTime.mapIndexed { _, credentialWithExpirationTime -> - val credentialExpirationTimeSeconds = - credentialWithExpirationTime.second.toEpochSecond() - - credentialUtil.europeanCredentialHasExpired(credentialExpirationTimeSeconds) - } - - return QrCodesResult.SingleQrCode( - QrCodeData.European.NonVaccination( - isExpired = credentialsExpired.all { it }, - bitmap = qrCodeBitmap, - readEuropeanCredential = mobileCoreWrapper.readEuropeanCredential(credential), - explanationNeeded = originType != OriginType.Test - ) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/LastVaccinationDoseUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/LastVaccinationDoseUtil.kt deleted file mode 100644 index 6fed729b7..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/LastVaccinationDoseUtil.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.utils - -import android.content.res.Resources -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination - -interface LastVaccinationDoseUtil { - - fun getIsLastDoseAnswer(event: RemoteEventVaccination): String -} - -class LastVaccinationDoseUtilImpl( - private val resources: Resources -) : LastVaccinationDoseUtil { - - override fun getIsLastDoseAnswer(event: RemoteEventVaccination) = - event.vaccination?.run { - when { - completed() && completionReason == "first-vaccination-elsewhere" -> resources.getString(R.string.holder_eventdetails_vaccinationStatus_firstVaccinationElsewhere) - completed() && completionReason == "recovery" -> resources.getString(R.string.holder_eventdetails_vaccinationStatus_recovery) - completed() && completionReason.isNullOrEmpty() -> resources.getString(R.string.holder_eventdetails_vaccinationStatus_complete) - else -> "" - } - } ?: "" - - private fun RemoteEventVaccination.Vaccination.completed() = - completedByMedicalStatement == true || completedByPersonalStatement == true - - private fun RemoteEventVaccination.Vaccination.notCompleted() = - completedByMedicalStatement == false || completedByPersonalStatement == false -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/MultipleQrCodesUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/MultipleQrCodesUtil.kt deleted file mode 100644 index 1e2556da9..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/MultipleQrCodesUtil.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.utils - -import nl.rijksoverheid.ctr.holder.qrcodes.models.QrCodeData - -interface MultipleQrCodesUtil { - fun getMostRelevantQrCodeIndex(vaccinations: List): Int -} - -class MultipleQrCodesUtilImpl : MultipleQrCodesUtil { - - override fun getMostRelevantQrCodeIndex(vaccinations: List): Int { - val mostRelevantVaccination = vaccinations.sortedWith( - compareBy { !it.isDoseNumberSmallerThanTotalDose } - .thenBy { it.dose } - .thenBy { it.ofTotalDoses } - ).last() - return vaccinations.indexOfFirst { it == mostRelevantVaccination } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrCodeUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrCodeUtil.kt deleted file mode 100644 index 1ad1b34f3..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrCodeUtil.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.utils - -import android.graphics.Bitmap -import android.graphics.Color -import com.google.zxing.BarcodeFormat -import com.google.zxing.EncodeHintType -import com.google.zxing.MultiFormatWriter -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import java.util.EnumMap - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface QrCodeUtil { - fun createQrCode( - qrCodeContent: String, - width: Int, - height: Int, - errorCorrectionLevel: ErrorCorrectionLevel - ): Bitmap -} - -class QrCodeUtilImpl : QrCodeUtil { - override fun createQrCode( - qrCodeContent: String, - width: Int, - height: Int, - errorCorrectionLevel: ErrorCorrectionLevel - ): Bitmap { - val multiFormatWriter = MultiFormatWriter() - val hints: MutableMap = EnumMap( - EncodeHintType::class.java - ) - hints[EncodeHintType.MARGIN] = 0 - hints[EncodeHintType.ERROR_CORRECTION] = errorCorrectionLevel - val bitMatrix = multiFormatWriter.encode( - qrCodeContent, - BarcodeFormat.QR_CODE, - 0, - 0, - hints - ) - val bitmap = Bitmap.createBitmap( - width, - height, - Bitmap.Config.RGB_565 - ) - val pixels = IntArray(width * height) - for (y in 0 until height) { - val offset = y * width - for (x in 0 until width) { - val xf: Float = x.toFloat() / width - val yf: Float = y.toFloat() / height - pixels[offset + x] = if (bitMatrix.get( - (xf * bitMatrix.width.toFloat()).toInt(), - (yf * bitMatrix.height.toFloat()).toInt() - ) - ) Color.BLACK else Color.WHITE - } - } - bitmap.setPixels(pixels, 0, width, 0, 0, width, height) - return bitmap - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrCodesFragmentUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrCodesFragmentUtil.kt deleted file mode 100644 index d54ec7b20..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrCodesFragmentUtil.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.qrcodes.utils - -import java.time.Clock -import java.time.Instant -import nl.rijksoverheid.ctr.holder.qrcodes.QrCodesFragment -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType - -interface QrCodesFragmentUtil { - /** - * If [QrCodesFragment] should close - */ - fun shouldClose(credentialExpirationTimeSeconds: Long, type: GreenCardType): Boolean -} - -class QrCodesFragmentUtilImpl( - private val utcClock: Clock -) : QrCodesFragmentUtil { - - override fun shouldClose(credentialExpirationTimeSeconds: Long, type: GreenCardType): Boolean { - if (type == GreenCardType.Eu) { - return false - } - val now = Instant.now(utcClock) - val expiration = Instant.ofEpochSecond(credentialExpirationTimeSeconds) - return now.isAfter(expiration) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrInfoScreenUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrInfoScreenUtil.kt deleted file mode 100644 index 66693966a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/qrcodes/utils/QrInfoScreenUtil.kt +++ /dev/null @@ -1,406 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ -package nl.rijksoverheid.ctr.holder.qrcodes.utils - -import android.app.Application -import android.text.TextUtils -import androidx.core.text.HtmlCompat -import java.time.LocalDate -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException -import nl.rijksoverheid.ctr.design.ext.formatDateTimeWithTimeZone -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYearNumerical -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.qrcodes.models.ReadEuropeanCredentialUtil -import nl.rijksoverheid.ctr.holder.utils.CountryUtil -import nl.rijksoverheid.ctr.holder.utils.LocalDateUtil -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.shared.ext.getStringOrNull -import nl.rijksoverheid.ctr.shared.ext.locale -import org.json.JSONObject - -interface QrInfoScreenUtil { - - fun getForEuropeanTestQr(readEuropeanCredential: JSONObject): QrInfoScreen - fun getForEuropeanVaccinationQr(readEuropeanCredential: JSONObject): QrInfoScreen - fun getForEuropeanRecoveryQr(readEuropeanCredential: JSONObject): QrInfoScreen -} - -class QrInfoScreenUtilImpl( - private val application: Application, - private val readEuropeanCredentialUtil: ReadEuropeanCredentialUtil, - private val countryUtil: CountryUtil, - private val localDateUtil: LocalDateUtil, - cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : QrInfoScreenUtil { - - private val holderConfig = cachedAppConfigUseCase.getCachedAppConfig() - - override fun getForEuropeanTestQr(readEuropeanCredential: JSONObject): QrInfoScreen { - val dcc = requireNotNull(readEuropeanCredential.optJSONObject("dcc")) - val test = dcc.getJSONArray("t").optJSONObject(0) - - val title = application.getString(R.string.qr_explanation_title_eu) - - val fullName = "${dcc.optJSONObject("nam").getStringOrNull("fn")}, ${ - dcc.optJSONObject("nam").getStringOrNull("gn") - }" - - val birthDate = dcc.getStringOrNull("dob")?.let { birthDate -> - try { - LocalDate.parse(birthDate, DateTimeFormatter.ISO_DATE).formatDayMonthYearNumerical() - } catch (e: Exception) { - "" - } - } ?: "" - - val disease = application.getString(R.string.your_vaccination_explanation_covid_19_answer) - - val testType = holderConfig.euTestTypes.firstOrNull { - it.code == test.getStringOrNull("tt") - }?.name ?: test.getStringOrNull("tt") ?: "" - - val testName = test.getStringOrNull("nm") ?: "" - - val testDate = test.getStringOrNull("sc")?.let { - try { - OffsetDateTime.parse(it, DateTimeFormatter.ISO_OFFSET_DATE_TIME) - .formatDateTimeWithTimeZone(application) - } catch (e: Exception) { - "" - } - } ?: "" - - val testResult = - application.getString(R.string.holder_showqr_eu_about_test_negative) - - val testLocation = test.getStringOrNull("tc") ?: "" - - val manufacturer = - holderConfig.euTestManufacturers.firstOrNull { - it.code == test.getStringOrNull("ma") - }?.name ?: test.getStringOrNull("ma") ?: "" - - val testCountry = - countryUtil.getCountryForQrInfoScreen( - test.getStringOrNull("co"), - application.applicationContext.locale() - ) - - val issuerValue = test.getStringOrNull("is") - val issuer = if (issuerValue == issuerVWS) { - application.getString(R.string.qr_explanation_certificate_issuer) - } else { - issuerValue - } - - val uniqueCode = test.getStringOrNull("ci") - - val texts = mutableListOf( - application.getString(R.string.qr_explanation_description_eu_test_header), - "

", - application.getString(R.string.qr_explanation_description_eu_test_name), - createQrAnswer(fullName), - application.getString(R.string.qr_explanation_description_eu_test_birth_date), - createQrAnswer(birthDate), - application.getString(R.string.qr_explanation_description_eu_test_disease), - createQrAnswer(disease) - ) - - if (testType.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_test_type), - createQrAnswer(testType) - ) - ) - } - - if (testName.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_test_name), - createQrAnswer(testName) - ) - ) - } - - if (testDate.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_test_date), - createQrAnswer(testDate) - ) - ) - } - - if (testResult.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_test_result), - createQrAnswer(testResult) - ) - ) - } - - if (manufacturer.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_manufacturer), - createQrAnswer(manufacturer) - ) - ) - } - - if (testLocation.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_test_centre), - createQrAnswer(testLocation) - ) - ) - } - - if (testCountry.isNotBlank()) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_test_country), - createQrAnswer(testCountry) - ) - ) - } - - if (issuer?.isNotBlank() == true) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_issuer), - createQrAnswer(issuer) - ) - ) - } - - if (uniqueCode?.isNotBlank() == true) { - texts.addAll( - listOf( - application.getString(R.string.qr_explanation_description_eu_test_certificate_identifier), - createQrAnswer(uniqueCode) - ) - ) - } - - return QrInfoScreen( - title = title, - description = (TextUtils.concat(*texts.toTypedArray()) as String), - footer = application.getString(R.string.qr_explanation_description_eu_test_footer) - ) - } - - override fun getForEuropeanVaccinationQr(readEuropeanCredential: JSONObject): QrInfoScreen { - val dcc = requireNotNull(readEuropeanCredential.optJSONObject("dcc")) - val vaccination = dcc.getJSONArray("v").optJSONObject(0) - - val fullName = "${dcc.optJSONObject("nam").getStringOrNull("fn")}, ${ - dcc.optJSONObject("nam").getStringOrNull("gn") - }" - - val birthDate = dcc.getStringOrNull("dob")?.let { birthDate -> - try { - LocalDate.parse(birthDate, DateTimeFormatter.ISO_DATE).formatDayMonthYearNumerical() - } catch (e: DateTimeParseException) { - // Check if date has removed content, if so return year or string only - if (birthDate.contains("XX")) { - // Retrieve birth year only - birthDate.split("-").first() - } else birthDate - } catch (e: Exception) { - "" - } - } ?: "" - - val disease = application.getString(R.string.your_vaccination_explanation_covid_19_answer) - - val vaccin = holderConfig.euBrands.firstOrNull { - it.code == vaccination.getStringOrNull("mp") - }?.name ?: vaccination.getStringOrNull("mp") ?: "" - - val vaccinType = holderConfig.euVaccinations.firstOrNull { - it.code == vaccination.getStringOrNull("vp") - }?.name ?: vaccination.getStringOrNull("vp") ?: "" - - val manufacturer = - holderConfig.euManufacturers.firstOrNull { - it.code == vaccination.getStringOrNull("ma") - }?.name ?: vaccination.getStringOrNull("ma") ?: "" - - val doses = - readEuropeanCredentialUtil.getDoseRangeStringForVaccination(readEuropeanCredential) - val overDoseLink = - if (readEuropeanCredentialUtil.doseExceedsTotalDoses(readEuropeanCredential)) { - application.getString(R.string.holder_showqr_eu_about_vaccination_dosage_message) - } else "" - - val (vaccinationDate, vaccinationDays) = vaccination.getStringOrNull("dt") - ?.let { vaccinationDate -> - localDateUtil.dateAndDaysSince(vaccinationDate) - } ?: Pair("", "") - - val countryCode = vaccination.getStringOrNull("co") - val vaccinationCountry = - countryUtil.getCountryForQrInfoScreen( - countryCode, - application.applicationContext.locale() - ) - - val issuerValue = vaccination.getStringOrNull("is") - val issuer = if (issuerValue == issuerVWS) { - application.getString(R.string.qr_explanation_certificate_issuer) - } else { - issuerValue - } - - val uniqueCode = vaccination.getStringOrNull("ci") - - val title = application.getString(R.string.qr_explanation_title_eu_vaccination, doses) - - return QrInfoScreen( - title = title, - description = (TextUtils.concat( - application.getString(R.string.qr_explanation_description_eu_vaccination_header), - "

", - application.getString(R.string.qr_explanation_description_eu_vaccination_name), - createQrAnswer(fullName), - application.getString(R.string.qr_explanation_description_eu_vaccination_birth_date), - createQrAnswer(birthDate), - application.getString(R.string.qr_explanation_description_eu_vaccination_disease), - createQrAnswer(disease), - application.getString(R.string.qr_explanation_description_eu_vaccination_vaccine), - createQrAnswer(vaccin), - application.getString(R.string.qr_explanation_description_eu_vaccination_vaccine_type), - createQrAnswer(vaccinType), - application.getString(R.string.qr_explanation_description_eu_vaccination_producer), - createQrAnswer(manufacturer), - application.getString(R.string.qr_explanation_description_eu_vaccination_doses), - createQrAnswer(doses, overDoseLink), - application.getString(R.string.qr_explanation_description_eu_vaccination_vaccination_date), - createQrAnswer(vaccinationDate), - application.getString(R.string.holder_showQR_euAboutVaccination_daysSince), - createQrAnswer(vaccinationDays), - application.getString(R.string.qr_explanation_description_eu_vaccination_vaccinated_in), - createQrAnswer(vaccinationCountry), - application.getString(R.string.qr_explanation_description_eu_vaccination_certificate_issuer), - createQrAnswer(issuer ?: ""), - application.getString(R.string.qr_explanation_description_eu_vaccination_unique_certificate), - createQrAnswer(uniqueCode ?: "") - ) as String), - footer = application.getString(R.string.qr_explanation_description_eu_vaccination_footer) - ) - } - - override fun getForEuropeanRecoveryQr(readEuropeanCredential: JSONObject): QrInfoScreen { - val dcc = requireNotNull(readEuropeanCredential.optJSONObject("dcc")) - val recovery = dcc.getJSONArray("r").optJSONObject(0) - - val title = application.getString(R.string.qr_explanation_title_eu) - - val fullName = "${dcc.optJSONObject("nam").getStringOrNull("fn")}, ${ - dcc.optJSONObject("nam").getStringOrNull("gn") - }" - - val birthDate = dcc.getStringOrNull("dob")?.let { birthDate -> - try { - LocalDate.parse(birthDate, DateTimeFormatter.ISO_DATE).formatDayMonthYearNumerical() - } catch (e: Exception) { - "" - } - } ?: "" - - val disease = application.getString(R.string.your_vaccination_explanation_covid_19_answer) - - val testDate = recovery.getStringOrNull("fr")?.let { testDate -> - try { - LocalDate.parse(testDate, DateTimeFormatter.ISO_DATE).formatDayMonthYearNumerical() - } catch (e: Exception) { - "" - } - } ?: "" - - val country = countryUtil.getCountryForQrInfoScreen( - recovery.getStringOrNull("co"), - application.applicationContext.locale() - ) - - val issuerValue = recovery.getStringOrNull("is") - val issuer = if (issuerValue == issuerVWS) { - application.getString(R.string.qr_explanation_certificate_issuer) - } else { - issuerValue - } - - val validFromDate = recovery.getStringOrNull("df")?.let { - try { - LocalDate.parse(it, DateTimeFormatter.ISO_DATE).formatDayMonthYearNumerical() - } catch (e: Exception) { - "" - } - } ?: "" - - val validUntilDate = recovery.getStringOrNull("du")?.let { - try { - LocalDate.parse(it, DateTimeFormatter.ISO_DATE).formatDayMonthYearNumerical() - } catch (e: Exception) { - "" - } - } ?: "" - - val uniqueCode = recovery.getStringOrNull("ci") - - return QrInfoScreen( - title = title, - description = (TextUtils.concat( - application.getString(R.string.qr_explanation_description_eu_recovery_header), - "

", - application.getString(R.string.qr_explanation_description_eu_recovery_name), - createQrAnswer(fullName), - application.getString(R.string.qr_explanation_description_eu_recovery_birth_date), - createQrAnswer(birthDate), - application.getString(R.string.qr_explanation_description_eu_recovery_disease), - createQrAnswer(disease), - application.getString(R.string.qr_explanation_description_eu_recovery_test_date), - createQrAnswer(testDate), - application.getString(R.string.qr_explanation_description_eu_recovery_country), - createQrAnswer(country), - application.getString(R.string.qr_explanation_description_eu_recovery_producer), - createQrAnswer(issuer ?: ""), - application.getString(R.string.qr_explanation_description_eu_recovery_valid_from_date), - createQrAnswer(validFromDate), - application.getString(R.string.qr_explanation_description_eu_recovery_valid_until_date), - createQrAnswer(validUntilDate), - application.getString(R.string.qr_explanation_description_eu_recovery_unique_code), - createQrAnswer(uniqueCode ?: "") - ) as String), - footer = application.getString(R.string.qr_explanation_description_eu_recovery_footer) - ) - } - - private fun createQrAnswer(answer: String, answerDescription: String = ""): String { - val sanitizedAnswer = HtmlCompat.fromHtml(answer, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() - val sanitizedAnswerDescription = - HtmlCompat.fromHtml(answerDescription, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() - return "
$sanitizedAnswer
${if (answerDescription.isEmpty()) "
" else "$sanitizedAnswerDescription

"}" - } - - companion object { - private const val issuerVWS = "Ministry of Health Welfare and Sport" - } -} - -data class QrInfoScreen( - val title: String, - val description: String, - val footer: String = "" -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEvents.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEvents.kt deleted file mode 100644 index 0017b640d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEvents.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreen -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity - -data class SavedEvents( - val providerName: String, - val eventGroupEntity: EventGroupEntity, - val events: List -) { - data class SavedEvent( - val remoteEvent: RemoteEvent, - val infoScreen: InfoScreen - ) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsFragment.kt deleted file mode 100644 index 17dee9f71..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsFragment.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import com.xwray.groupie.GroupAdapter -import com.xwray.groupie.GroupieViewHolder -import com.xwray.groupie.Section -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentSavedEventsBinding -import nl.rijksoverheid.ctr.holder.saved_events.items.SavedEventsHeaderAdapterItem -import nl.rijksoverheid.ctr.holder.saved_events.items.SavedEventsNoSavedEventsItem -import nl.rijksoverheid.ctr.holder.saved_events.items.SavedEventsSectionAdapterItem -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -class SavedEventsFragment : Fragment(R.layout.fragment_saved_events) { - - private val sections: MutableList
= mutableListOf() - private val adapter = GroupAdapter() - private val savedEventsViewModel: SavedEventsViewModel by viewModel() - private val dialogUtil: DialogUtil by inject() - private val featureFlagUseCase: HolderFeatureFlagUseCase by inject() - private val isInArchiveMode: Boolean = featureFlagUseCase.isInArchiveMode() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentSavedEventsBinding.bind(view) - initRecyclerView(binding) - - listenToSavedEvents() - listenToRemoveSavedEvents() - savedEventsViewModel.getSavedEvents() - } - - private fun initRecyclerView(binding: FragmentSavedEventsBinding) { - sections.clear() - - binding.savedEventsRecyclerView.adapter = adapter - binding.savedEventsRecyclerView.itemAnimator = null - } - - private fun listenToSavedEvents() { - savedEventsViewModel.savedEventsLiveData.observe( - viewLifecycleOwner, - EventObserver { savedEvents -> - sections.clear() - adapter.clear() - val headerSection = Section() - headerSection.add(SavedEventsHeaderAdapterItem()) - sections.add(headerSection) - - if (savedEvents.isEmpty()) { - val emptyItemsSection = Section() - emptyItemsSection.add(SavedEventsNoSavedEventsItem()) - sections.add(emptyItemsSection) - } else { - savedEvents.forEach { - val item = SavedEventsSectionAdapterItem( - isNotInArchiveMode = !isInArchiveMode, - savedEvents = it, - onClickEvent = { toolbarTitle, infoScreen -> - navigateSafety( - SavedEventsFragmentDirections.actionYourEventExplanation( - toolbarTitle = toolbarTitle, - data = arrayOf(infoScreen) - ) - ) - }, - onClickClearData = { eventGroupEntity -> - presentClearDataDialog { - savedEventsViewModel.removeSavedEvents(eventGroupEntity) - } - } - ) - val itemSection = item.section(it) - itemSection.setHeader(item) - sections.add(itemSection) - } - } - adapter.addAll(sections) - }) - } - - private fun listenToRemoveSavedEvents() { - savedEventsViewModel.removedSavedEventsLiveData.observe(viewLifecycleOwner) { - navigateSafety( - SavedEventsFragmentDirections.actionSavedEventsSyncGreenCards() - ) - } - } - - private fun presentClearDataDialog(onClear: () -> Unit) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.holder_storedEvent_alert_removeEvents_title, - message = getString(R.string.holder_storedEvent_alert_removeEvents_message), - positiveButtonText = R.string.general_delete, - negativeButtonText = R.string.general_cancel, - positiveButtonCallback = { - onClear.invoke() - } - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsSyncGreenCardsFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsSyncGreenCardsFragment.kt deleted file mode 100644 index 6df1d00ec..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsSyncGreenCardsFragment.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events - -import android.os.Bundle -import android.view.View -import nl.rijksoverheid.ctr.holder.BaseFragment -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.fuzzy_matching.MatchingBlobIds -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.sync_greencards.SyncGreenCardsViewModel -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.models.Flow -import org.koin.androidx.viewmodel.ext.android.viewModel - -class SavedEventsSyncGreenCardsFragment : BaseFragment(R.layout.fragment_saved_events_sync_green_cards) { - - private val syncGreenCardsViewModel: SyncGreenCardsViewModel by viewModel() - - override fun onButtonClickWithRetryAction() { - // This screen does not have a retry button - } - - override fun getFlow(): Flow { - return HolderFlow.ClearEvents - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - syncGreenCardsViewModel.refresh() - - syncGreenCardsViewModel.databaseSyncerResultLiveData.observe(viewLifecycleOwner, EventObserver { - when (it) { - is DatabaseSyncerResult.Success -> { - navigateSafety( - SavedEventsSyncGreenCardsFragmentDirections.actionSavedEvents() - ) - } - is DatabaseSyncerResult.Failed -> { - presentError( - errorResult = it.errorResult - ) - } - is DatabaseSyncerResult.FuzzyMatchingError -> { - navigateSafety(SavedEventsSyncGreenCardsFragmentDirections.actionFuzzyMatching( - MatchingBlobIds(it.matchingBlobIds) - )) - } - } - }) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsViewModel.kt deleted file mode 100644 index e2842332f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/SavedEventsViewModel.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.saved_events.usecases.GetSavedEventsUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason -import nl.rijksoverheid.ctr.shared.livedata.Event - -class SavedEventsViewModel( - private val holderDatabase: HolderDatabase, - private val getSavedEventsUseCase: GetSavedEventsUseCase -) : ViewModel() { - - val savedEventsLiveData: LiveData>> = MutableLiveData() - val removedSavedEventsLiveData: LiveData = MutableLiveData() - - fun getSavedEvents() { - viewModelScope.launch(Dispatchers.IO) { - val savedEvents = getSavedEventsUseCase.getSavedEvents() - (savedEventsLiveData as MutableLiveData).postValue(Event(savedEvents)) - } - } - - fun removeSavedEvents(eventGroupEntity: EventGroupEntity) { - viewModelScope.launch { - holderDatabase.eventGroupDao().delete(eventGroupEntity) - // the user deleted consciously a stored event and is aware of the currently stored events - // so no point to communicate anymore which conflicted events were deleted - holderDatabase.removedEventDao().deleteAll(RemovedEventReason.FuzzyMatched) - (removedSavedEventsLiveData as MutableLiveData).postValue(Unit) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventAdapterItem.kt deleted file mode 100644 index 4d01665ca..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventAdapterItem.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events.items - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemSavedEventBinding -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.saved_events.SavedEvents -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreen -import nl.rijksoverheid.ctr.shared.ext.capitalize - -class SavedEventAdapterItem( - private val savedEvent: SavedEvents.SavedEvent, - private val onClick: (toolbarTitle: String, infoScreen: InfoScreen) -> Unit -) : BindableItem() { - - override fun bind(viewBinding: AdapterItemSavedEventBinding, position: Int) { - val context = viewBinding.root.context - val title = when (savedEvent.remoteEvent) { - is RemoteEventVaccination -> context.getString(R.string.general_vaccination).capitalize() - is RemoteEventNegativeTest -> context.getString(R.string.general_negativeTest).capitalize() - is RemoteEventPositiveTest -> context.getString(R.string.general_positiveTest).capitalize() - is RemoteEventRecovery -> context.getString(R.string.general_recoverycertificate).capitalize() - else -> "" - } - viewBinding.title.text = title - viewBinding.subtitle.text = savedEvent.remoteEvent.getDate()?.toLocalDate()?.formatDayMonthYear() - viewBinding.root.setOnClickListener { - onClick.invoke(title, savedEvent.infoScreen) - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_saved_event - } - - override fun initializeViewBinding(view: View): AdapterItemSavedEventBinding { - return AdapterItemSavedEventBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsClearDataAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsClearDataAdapterItem.kt deleted file mode 100644 index ef9e9e855..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsClearDataAdapterItem.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events.items - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemSavedEventsClearDataBinding -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity - -class SavedEventsClearDataAdapterItem( - private val eventGroupEntity: EventGroupEntity, - private val onClick: (eventGroupEntity: EventGroupEntity) -> Unit -) : BindableItem() { - - override fun bind(viewBinding: AdapterItemSavedEventsClearDataBinding, position: Int) { - viewBinding.root.setOnClickListener { - onClick.invoke(eventGroupEntity) - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_saved_events_clear_data - } - - override fun initializeViewBinding(view: View): AdapterItemSavedEventsClearDataBinding { - return AdapterItemSavedEventsClearDataBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsHeaderAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsHeaderAdapterItem.kt deleted file mode 100644 index ca3cc1ec8..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsHeaderAdapterItem.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events.items - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.design.utils.IntentUtil -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemSavedEventsHeaderBinding -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class SavedEventsHeaderAdapterItem : BindableItem(), KoinComponent { - - private val intentUtil: IntentUtil by inject() - - override fun bind(viewBinding: AdapterItemSavedEventsHeaderBinding, position: Int) { - val context = viewBinding.root.context - - viewBinding.button.setOnClickListener { - intentUtil.openUrl( - context = context, - url = context.getString(R.string.holder_storedEvents_url) - ) - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_saved_events_header - } - - override fun initializeViewBinding(view: View): AdapterItemSavedEventsHeaderBinding { - return AdapterItemSavedEventsHeaderBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsNoSavedEventsItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsNoSavedEventsItem.kt deleted file mode 100644 index a85abd936..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsNoSavedEventsItem.kt +++ /dev/null @@ -1,27 +0,0 @@ - /* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events.items - -import android.view.View -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemSavedEventsNoSavedEventsBinding - -class SavedEventsNoSavedEventsItem : BindableItem() { - - override fun bind(viewBinding: AdapterItemSavedEventsNoSavedEventsBinding, position: Int) { - } - - override fun getLayout(): Int { - return R.layout.adapter_item_saved_events_no_saved_events - } - - override fun initializeViewBinding(view: View): AdapterItemSavedEventsNoSavedEventsBinding { - return AdapterItemSavedEventsNoSavedEventsBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsSectionAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsSectionAdapterItem.kt deleted file mode 100644 index d7f12097b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/items/SavedEventsSectionAdapterItem.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events.items - -import android.view.View -import com.xwray.groupie.Section -import com.xwray.groupie.viewbinding.BindableItem -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.AdapterItemSavedEventsSectionBinding -import nl.rijksoverheid.ctr.holder.saved_events.SavedEvents -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreen -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class SavedEventsSectionAdapterItem( - private val isNotInArchiveMode: Boolean, - private val savedEvents: SavedEvents, - private val onClickEvent: (toolbarTitle: String, infoScreen: InfoScreen) -> Unit, - private val onClickClearData: (eventGroupEntity: EventGroupEntity) -> Unit -) : BindableItem(), KoinComponent { - - private val remoteEventUtil: RemoteEventUtil by inject() - - override fun bind(viewBinding: AdapterItemSavedEventsSectionBinding, position: Int) { - setReceivedAt( - viewBinding = viewBinding, - providerName = savedEvents.providerName - ) - } - - fun section( - savedEvents: SavedEvents - ): Section { - - val section = Section() - - val items = savedEvents.events.map { - SavedEventAdapterItem( - savedEvent = it, - onClick = onClickEvent - ) - } - section.addAll(items) - - if (isNotInArchiveMode) { - section.add( - SavedEventsClearDataAdapterItem( - eventGroupEntity = savedEvents.eventGroupEntity, - onClick = onClickClearData - ) - ) - } - - return section - } - - private fun setReceivedAt( - viewBinding: AdapterItemSavedEventsSectionBinding, - providerName: String - ) { - - val context = viewBinding.root.context - - viewBinding.retrievedAt.text = if (remoteEventUtil.isDccEvent(providerName)) { - context.getString(R.string.holder_storedEvents_listHeader_paperFlow) - } else { - context.getString(R.string.holder_storedEvents_listHeader_fetchedFromProvider, providerName) - } - } - - override fun getLayout(): Int { - return R.layout.adapter_item_saved_events_section - } - - override fun initializeViewBinding(view: View): AdapterItemSavedEventsSectionBinding { - return AdapterItemSavedEventsSectionBinding.bind(view) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/usecases/GetSavedEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/usecases/GetSavedEventsUseCase.kt deleted file mode 100644 index f67207ddc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/saved_events/usecases/GetSavedEventsUseCase.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.saved_events.usecases - -import android.app.Application -import nl.rijksoverheid.ctr.design.ext.formatDateTime -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYearTime -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCase -import nl.rijksoverheid.ctr.holder.saved_events.SavedEvents -import nl.rijksoverheid.ctr.holder.your_events.utils.EventGroupEntityUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreenUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import org.json.JSONObject - -interface GetSavedEventsUseCase { - suspend fun getSavedEvents(): List -} - -class GetSavedEventsUseCaseImpl( - private val application: Application, - private val holderDatabase: HolderDatabase, - private val remoteEventUtil: RemoteEventUtil, - private val eventGroupEntityUtil: EventGroupEntityUtil, - private val getRemoteProtocolFromEventGroupUseCase: GetRemoteProtocolFromEventGroupUseCase, - private val infoScreenUtil: InfoScreenUtil, - private val yourEventsFragmentUtil: YourEventsFragmentUtil -) : GetSavedEventsUseCase { - - override suspend fun getSavedEvents(): List { - val eventGroups = holderDatabase.eventGroupDao().getAll() - - return eventGroups.map { eventGroupEntity -> - val isDccEvent = remoteEventUtil.isDccEvent( - providerIdentifier = eventGroupEntity.providerIdentifier - ) - val remoteProtocol = getRemoteProtocolFromEventGroupUseCase.get(eventGroupEntity) - - val fullName = yourEventsFragmentUtil.getFullName(remoteProtocol?.holder) - val birthDate = yourEventsFragmentUtil.getBirthDate(remoteProtocol?.holder) - - val providerName = eventGroupEntityUtil.getProviderName( - providerIdentifier = eventGroupEntity.providerIdentifier - ) - SavedEvents( - providerName = providerName, - eventGroupEntity = eventGroupEntity, - events = remoteProtocol?.events?.sortedByDescending { it.getDate() } - ?.mapNotNull { remoteEvent -> - val europeanCredential = if (isDccEvent) { - JSONObject(eventGroupEntity.jsonData.decodeToString()).getString("credential") - .toByteArray() - } else null - - val infoScreen = when (remoteEvent) { - is RemoteEventVaccination -> { - infoScreenUtil.getForVaccination( - event = remoteEvent, - fullName = fullName, - birthDate = birthDate, - providerIdentifier = providerName, - europeanCredential = europeanCredential, - addExplanation = false - ) - } - is RemoteEventRecovery -> { - infoScreenUtil.getForRecovery( - event = remoteEvent, - testDate = remoteEvent.recovery?.sampleDate?.formatDayMonthYear() - ?: "", - fullName = fullName, - birthDate = birthDate, - europeanCredential = europeanCredential, - addExplanation = false - ) - } - is RemoteEventPositiveTest -> { - infoScreenUtil.getForPositiveTest( - event = remoteEvent, - testDate = remoteEvent.positiveTest?.sampleDate?.formatDayMonthYearTime( - application - ) ?: "", - fullName = fullName, - birthDate = birthDate - ) - } - is RemoteEventNegativeTest -> { - infoScreenUtil.getForNegativeTest( - event = remoteEvent, - fullName = fullName, - testDate = remoteEvent.negativeTest?.sampleDate?.formatDateTime( - application - ) ?: "", - birthDate = birthDate, - europeanCredential = europeanCredential, - addExplanation = false - ) - } - else -> { - null - } - } - - if (infoScreen != null) { - SavedEvents.SavedEvent( - remoteEvent = remoteEvent, - infoScreen = infoScreen - ) - } else { - null - } - } ?: listOf() - ) - }.sortedByDescending { it.eventGroupEntity.id } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/sync_greencards/SyncGreenCardsViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/sync_greencards/SyncGreenCardsViewModel.kt deleted file mode 100644 index 4dd3dbae9..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/sync_greencards/SyncGreenCardsViewModel.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.sync_greencards - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.persistence.PersistenceManager -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncer -import nl.rijksoverheid.ctr.shared.livedata.Event -import nl.rijksoverheid.ctr.shared.models.AppErrorResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class SyncGreenCardsViewModel : ViewModel() { - val loading: LiveData> = MutableLiveData() - val databaseSyncerResultLiveData: LiveData> = MutableLiveData() - - abstract fun refresh() -} - -class SyncGreenCardsViewModelImpl( - private val holderDatabaseSyncer: HolderDatabaseSyncer, - private val persistenceManager: PersistenceManager -) : SyncGreenCardsViewModel() { - override fun refresh() { - (loading as MutableLiveData).value = Event(true) - viewModelScope.launch { - try { - val databaseSyncerResult = holderDatabaseSyncer.sync() - if (databaseSyncerResult is DatabaseSyncerResult.Success) { - persistenceManager.setShowSyncGreenCardsItem(false) - } - (databaseSyncerResultLiveData as MutableLiveData).value = Event(databaseSyncerResult) - } catch (e: Exception) { - (databaseSyncerResultLiveData as MutableLiveData).value = Event( - DatabaseSyncerResult.Failed.Error(AppErrorResult(HolderStep.StoringEvents, e)) - ) - } finally { - loading.value = Event(false) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/ui/priority_notification/PriorityNotificationUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/ui/priority_notification/PriorityNotificationUseCase.kt deleted file mode 100644 index 3425276a9..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/ui/priority_notification/PriorityNotificationUseCase.kt +++ /dev/null @@ -1,39 +0,0 @@ -package nl.rijksoverheid.ctr.holder.ui.priority_notification - -import com.squareup.moshi.Moshi -import nl.rijksoverheid.ctr.appconfig.api.model.HolderConfig -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.usecases.AppConfigUseCase -import nl.rijksoverheid.ctr.shared.ext.toObject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -interface PriorityNotificationUseCase { - suspend fun get(): String? -} - -class PriorityNotificationUseCaseImpl( - private val appConfigUseCase: AppConfigUseCase, - private val moshi: Moshi -) : PriorityNotificationUseCase { - override suspend fun get(): String? { - val configResult = appConfigUseCase.get() - - if (configResult is ConfigResult.Success) { - return try { - val config = configResult.appConfig.toObject(moshi) - config.priorityNotification - } catch (exception: Exception) { - null - } - } - - return null - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/ui/priority_notification/PriorityNotificationViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/ui/priority_notification/PriorityNotificationViewModel.kt deleted file mode 100644 index 5da7dd2a5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/ui/priority_notification/PriorityNotificationViewModel.kt +++ /dev/null @@ -1,34 +0,0 @@ -package nl.rijksoverheid.ctr.holder.ui.priority_notification - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.shared.livedata.Event - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -abstract class PriorityNotificationViewModel : ViewModel() { - val showPriorityNotificationLiveData = MutableLiveData>() -} - -class PriorityNotificationViewModelImpl( - private val priorityNotificationUseCase: PriorityNotificationUseCase -) : PriorityNotificationViewModel() { - init { - check() - } - - fun check() = viewModelScope.launch { - val priorityNotificationMessage = priorityNotificationUseCase.get() - if (!priorityNotificationMessage.isNullOrEmpty()) { - showPriorityNotificationLiveData.postValue(Event(priorityNotificationMessage)) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderAppStatusUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderAppStatusUseCase.kt deleted file mode 100644 index d4e99418d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderAppStatusUseCase.kt +++ /dev/null @@ -1,155 +0,0 @@ -package nl.rijksoverheid.ctr.holder.usecases - -import com.squareup.moshi.Moshi -import java.net.UnknownHostException -import java.time.Clock -import java.time.OffsetDateTime -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.appconfig.api.model.HolderConfig -import nl.rijksoverheid.ctr.appconfig.models.AppStatus -import nl.rijksoverheid.ctr.appconfig.models.AppUpdateData -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.persistence.AppConfigPersistenceManager -import nl.rijksoverheid.ctr.appconfig.persistence.AppUpdatePersistenceManager -import nl.rijksoverheid.ctr.appconfig.persistence.RecommendedUpdatePersistenceManager -import nl.rijksoverheid.ctr.appconfig.usecases.AppStatusUseCase -import nl.rijksoverheid.ctr.introduction.persistance.IntroductionPersistenceManager -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.shared.ext.toObject -import nl.rijksoverheid.ctr.shared.factories.ErrorCodeStringFactory -import nl.rijksoverheid.ctr.shared.factories.OnboardingFlow - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -class HolderAppStatusUseCaseImpl( - private val clock: Clock, - private val cachedAppConfigUseCase: HolderCachedAppConfigUseCase, - private val appConfigPersistenceManager: AppConfigPersistenceManager, - private val recommendedUpdatePersistenceManager: RecommendedUpdatePersistenceManager, - private val moshi: Moshi, - private val appUpdateData: AppUpdateData, - private val appUpdatePersistenceManager: AppUpdatePersistenceManager, - private val introductionPersistenceManager: IntroductionPersistenceManager, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabase: HolderDatabase, - private val errorCodeStringFactory: ErrorCodeStringFactory -) : AppStatusUseCase { - - override suspend fun get(config: ConfigResult, currentVersionCode: Int): AppStatus = - withContext(Dispatchers.IO) { - when (config) { - is ConfigResult.Success -> { - if (isArchived()) { - AppStatus.Archived - } else { - checkIfActionRequired( - currentVersionCode = currentVersionCode, - appConfig = config.appConfig.toObject(moshi) - ) - } - } - - is ConfigResult.Error -> { - val cachedAppConfig = cachedAppConfigUseCase.getCachedAppConfigOrNull() - when { - cachedAppConfig != null && appConfigPersistenceManager.getAppConfigLastFetchedSeconds() + cachedAppConfig.configTtlSeconds - >= OffsetDateTime.now(clock).toEpochSecond() -> { - checkIfActionRequired( - currentVersionCode = currentVersionCode, - appConfig = cachedAppConfig - ) - } - - config.error.e is UnknownHostException -> { - AppStatus.Error - } - - else -> { - AppStatus.LaunchError( - errorCodeStringFactory.get( - OnboardingFlow, - listOf(config.error) - ) - ) - } - } - } - } - } - - private fun updateRequired(currentVersionCode: Int, appConfig: AppConfig) = - currentVersionCode < appConfig.minimumVersion - - override fun checkIfActionRequired(currentVersionCode: Int, appConfig: AppConfig): AppStatus { - return when { - updateRequired(currentVersionCode, appConfig) -> AppStatus.UpdateRequired - appConfig.appDeactivated -> AppStatus.Deactivated - shouldShowNewFeatures() -> getNewFeatures() - newTermsAvailable() -> AppStatus.ConsentNeeded(appUpdateData) - currentVersionCode < appConfig.recommendedVersion -> getHolderRecommendUpdateStatus( - appConfig - ) - - else -> AppStatus.NoActionRequired - } - } - - private suspend fun isArchived(): Boolean { - return featureFlagUseCase.isInArchiveMode() && holderDatabase.eventGroupDao().getAll() - .isEmpty() - } - - private fun shouldShowNewFeatures() = - (newFeaturesAvailable()) && introductionPersistenceManager.getIntroductionFinished() - - private fun getHolderRecommendUpdateStatus(appConfig: AppConfig) = - if (appConfig.recommendedVersion > recommendedUpdatePersistenceManager.getHolderVersionUpdateShown()) { - recommendedUpdatePersistenceManager.saveHolderVersionShown(appConfig.recommendedVersion) - AppStatus.UpdateRecommended - } else { - AppStatus.NoActionRequired - } - - override fun isAppActive(currentVersionCode: Int): Boolean { - val config = cachedAppConfigUseCase.getCachedAppConfig() - return !config.appDeactivated && !updateRequired(currentVersionCode, config) - } - - private fun newFeaturesAvailable(): Boolean { - val newFeatureVersion = appUpdateData.newFeatureVersion - return appUpdateData.newFeatures.isNotEmpty() && - newFeatureVersion != null && - !appUpdatePersistenceManager.getNewFeaturesSeen(newFeatureVersion) && - featureFlagUseCase.isInArchiveMode() - } - - /** - * Get the new feature and/or new policy rules as new feature based on policy change - * - * @return New features and/or policy change as new features - */ - private fun getNewFeatures(): AppStatus.NewFeatures { - return when { - newFeaturesAvailable() -> AppStatus.NewFeatures( - appUpdateData.copy( - newFeatures = appUpdateData.newFeatures - ) - ) - - else -> AppStatus.NewFeatures(appUpdateData) - } - } - - private fun newTermsAvailable() = - !appUpdatePersistenceManager.getNewTermsSeen(appUpdateData.newTerms.version) && - introductionPersistenceManager.getIntroductionFinished() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderFeatureFlagUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderFeatureFlagUseCase.kt deleted file mode 100644 index bb945ee87..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderFeatureFlagUseCase.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.usecases - -import java.time.Clock -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase - -interface HolderFeatureFlagUseCase { - fun getGgdEnabled(): Boolean - fun getMijnCnEnabled(): Boolean - fun getPapEnabled(): Boolean - fun getAddEventsButtonEnabled(): Boolean - fun getScanCertificateButtonEnabled(): Boolean - fun getMigrateButtonEnabled(): Boolean - fun isInArchiveMode(): Boolean -} - -class HolderFeatureFlagUseCaseImpl( - private val clock: Clock, - private val cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : HolderFeatureFlagUseCase { - - override fun getGgdEnabled(): Boolean { - return cachedAppConfigUseCase.getCachedAppConfig().ggdEnabled - } - - override fun getMijnCnEnabled(): Boolean { - return cachedAppConfigUseCase.getCachedAppConfig().mijnCnEnabled - } - - override fun getPapEnabled(): Boolean { - return cachedAppConfigUseCase.getCachedAppConfig().papEnabled - } - - override fun getAddEventsButtonEnabled(): Boolean { - return cachedAppConfigUseCase.getCachedAppConfig().addEventsButtonEnabled ?: true - } - - override fun getScanCertificateButtonEnabled(): Boolean { - return cachedAppConfigUseCase.getCachedAppConfig().scanCertificateButtonEnabled ?: true - } - - override fun getMigrateButtonEnabled(): Boolean { - return cachedAppConfigUseCase.getCachedAppConfig().migrateButtonEnabled ?: true - } - - override fun isInArchiveMode(): Boolean { - val archiveOnlyDate = cachedAppConfigUseCase.getCachedAppConfig().archiveOnlyDate ?: return false - val now = OffsetDateTime.now(clock) - return now.isAfter(archiveOnlyDate) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderIntroductionStatusUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderIntroductionStatusUseCase.kt deleted file mode 100644 index 7fe4f2229..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/usecases/HolderIntroductionStatusUseCase.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2022 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.usecases - -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.introduction.onboarding.models.OnboardingItem -import nl.rijksoverheid.ctr.introduction.persistance.IntroductionPersistenceManager -import nl.rijksoverheid.ctr.introduction.status.models.IntroductionData -import nl.rijksoverheid.ctr.introduction.status.usecases.IntroductionStatusUseCase - -class HolderIntroductionStatusUseCaseImpl( - private val introductionPersistenceManager: IntroductionPersistenceManager, - private val introductionData: IntroductionData -) : IntroductionStatusUseCase { - - override fun getIntroductionRequired() = - !introductionPersistenceManager.getIntroductionFinished() - - /** - * Add the current disclosure policy info as onboarding item - * - * @return Onboarding not finished state with disclosure policy onboarding item added - */ - override fun getData(): IntroductionData { - return introductionData.copy( - onboardingItems = getOnboardingItems() - ) - } - - private fun getOnboardingItems(): List { - return listOf( - OnboardingItem( - R.drawable.illustration_onboarding_1_0g, - R.string.holder_onboarding_content_TravelSafe_0G_title, - R.string.holder_onboarding_content_TravelSafe_0G_message - ), - OnboardingItem( - R.drawable.illustration_onboarding_2, - R.string.onboarding_screen_2_title, - R.string.onboarding_screen_2_description - ), - OnboardingItem( - R.drawable.illustration_onboarding_3, - R.string.holder_onboarding_content_onlyInternationalQR_0G_title, - R.string.holder_onboarding_content_onlyInternationalQR_0G_message - ) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/ConfigFetchWorker.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/ConfigFetchWorker.kt deleted file mode 100644 index c2b359aaf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/ConfigFetchWorker.kt +++ /dev/null @@ -1,58 +0,0 @@ -package nl.rijksoverheid.ctr.holder.workers - -import android.content.Context -import androidx.work.CoroutineWorker -import androidx.work.WorkManager -import androidx.work.WorkerParameters -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.appconfig.models.ConfigResult -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCase -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -open class ConfigFetchWorker( - private val context: Context, - params: WorkerParameters, - private val cachedAppConfigUseCase: CachedAppConfigUseCase, - private val holderFeatureFlagUseCase: HolderFeatureFlagUseCase, - private val configResultUseCase: ConfigResultUseCase -) : CoroutineWorker(context, params) { - - override suspend fun doWork(): Result = withContext(Dispatchers.IO) { - val configResult = try { - configResultUseCase.fetch() - } catch (exception: Exception) { - ConfigResult.Error(NetworkRequestResult.Failed.ClientNetworkError(HolderStep.ConfigurationNetworkRequest)) - } - when (configResult) { - is ConfigResult.Error -> { - WorkManager.getInstance(context).cancelAllWork() - Result.failure() - } - is ConfigResult.Success -> { - val appDeactivated = cachedAppConfigUseCase.getCachedAppConfig().appDeactivated - val appInArchiveMode = holderFeatureFlagUseCase.isInArchiveMode() - if (appDeactivated || appInArchiveMode) { - WorkManager.getInstance(context).cancelAllWork() - Result.failure() - } else { - Result.success() - } - } - } - } - - companion object { - const val uniqueWorkNameTag = "fetch_config_worker" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/CredentialRefreshWorker.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/CredentialRefreshWorker.kt deleted file mode 100644 index 2ebf0f15a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/CredentialRefreshWorker.kt +++ /dev/null @@ -1,56 +0,0 @@ -package nl.rijksoverheid.ctr.holder.workers - -import android.content.Context -import androidx.work.WorkerParameters -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCase -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncer - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class CredentialRefreshWorker( - context: Context, - params: WorkerParameters, - configResultUseCase: ConfigResultUseCase, - cachedAppConfigUseCase: CachedAppConfigUseCase, - holderFeatureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabaseSyncer: HolderDatabaseSyncer -) : ConfigFetchWorker(context, params, cachedAppConfigUseCase, holderFeatureFlagUseCase, configResultUseCase) { - - override suspend fun doWork(): Result = withContext(Dispatchers.IO) { - when (val configWorkResult = super.doWork()) { - Result.success() -> { - credentialsRefresh() - } - else -> configWorkResult - } - } - - private suspend fun credentialsRefresh(): Result { - return when (holderDatabaseSyncer.sync( - flow = HolderFlow.SyncGreenCards, - syncWithRemote = true - )) { - is DatabaseSyncerResult.Failed.Error -> Result.failure() - is DatabaseSyncerResult.Failed.NetworkError -> Result.retry() - is DatabaseSyncerResult.Failed.ServerError.FirstTime -> Result.retry() - is DatabaseSyncerResult.Failed.ServerError.MultipleTimes -> Result.failure() - is DatabaseSyncerResult.FuzzyMatchingError -> Result.success() - is DatabaseSyncerResult.Success -> Result.success() - } - } - - companion object { - const val uniqueWorkNameTag = "credentials_refresh_worker" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/HolderWorkerFactory.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/HolderWorkerFactory.kt deleted file mode 100644 index b9c53220e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/HolderWorkerFactory.kt +++ /dev/null @@ -1,49 +0,0 @@ -package nl.rijksoverheid.ctr.holder.workers - -import android.content.Context -import androidx.work.ListenableWorker -import androidx.work.WorkerFactory -import androidx.work.WorkerParameters -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.appconfig.usecases.ConfigResultUseCase -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncer - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderWorkerFactory( - private val configResultUseCase: ConfigResultUseCase, - private val cachedAppConfigUseCase: CachedAppConfigUseCase, - private val holderFeatureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabaseSyncer: HolderDatabaseSyncer -) : WorkerFactory() { - override fun createWorker( - appContext: Context, - workerClassName: String, - workerParameters: WorkerParameters - ): ListenableWorker? { - return when (workerClassName) { - ConfigFetchWorker::class.java.name -> ConfigFetchWorker( - appContext, - workerParameters, - cachedAppConfigUseCase, - holderFeatureFlagUseCase, - configResultUseCase - ) - CredentialRefreshWorker::class.java.name -> CredentialRefreshWorker( - appContext, - workerParameters, - configResultUseCase, - cachedAppConfigUseCase, - holderFeatureFlagUseCase, - holderDatabaseSyncer - ) - else -> null - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/WorkerManagerUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/WorkerManagerUtil.kt deleted file mode 100644 index 1ae9ef421..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/workers/WorkerManagerUtil.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.workers - -import android.content.Context -import androidx.work.Constraints -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.NetworkType -import androidx.work.PeriodicWorkRequest -import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkManager -import java.util.concurrent.TimeUnit -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardRefreshUtil -import nl.rijksoverheid.ctr.holder.dashboard.util.RefreshState -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase -import nl.rijksoverheid.ctr.shared.models.Environment - -interface WorkerManagerUtil { - suspend fun scheduleRefreshCredentialsJob(): PeriodicWorkRequest? - fun cancelRefreshCredentialsJob(context: Context) -} - -class WorkerManagerUtilImpl( - private val context: Context, - private val greenCardRefreshUtil: GreenCardRefreshUtil, - private val appConfigUseCase: HolderCachedAppConfigUseCase, - private val environment: Environment = Environment.get(context) -) : WorkerManagerUtil { - - val acc: Boolean = environment == Environment.Acc - - // for testing, use minutes in acc builds - private val intervalUnit = if (acc) { - TimeUnit.MINUTES - } else { - TimeUnit.DAYS - } - - private fun interval(): Long = if (acc) { - 15 - } else { - appConfigUseCase.getCachedAppConfig().internationalQRRelevancyDays.toLong() - } - - override suspend fun scheduleRefreshCredentialsJob(): PeriodicWorkRequest? { - val refreshState = greenCardRefreshUtil.refreshState() - val appDeactivated = appConfigUseCase.getCachedAppConfig().appDeactivated - - if (refreshState is RefreshState.Refreshable && !appDeactivated) { - val credentialsRefreshInDays = refreshState.days - - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) - .build() - - val request = PeriodicWorkRequestBuilder( - repeatInterval = interval(), - repeatIntervalTimeUnit = intervalUnit) - .setInitialDelay(credentialsRefreshInDays, intervalUnit) - .setConstraints(constraints) - .build() - - WorkManager.getInstance(context) - .enqueueUniquePeriodicWork( - CredentialRefreshWorker.uniqueWorkNameTag, - ExistingPeriodicWorkPolicy.UPDATE, - request - ) - return request - } - return null - } - - override fun cancelRefreshCredentialsJob(context: Context) { - WorkManager.getInstance(context).cancelUniqueWork(CredentialRefreshWorker.uniqueWorkNameTag) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventExplanationFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventExplanationFragment.kt deleted file mode 100644 index 2a3c58226..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventExplanationFragment.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import nl.rijksoverheid.ctr.design.widgets.HtmlTextViewWidget -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentYourEventExplanationBinding -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreen - -class YourEventExplanationAdapter(private val dataSet: Array) : - RecyclerView.Adapter() { - - class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val textView: TextView = view.findViewById(R.id.subheader) - val htmlTextViewWidget: HtmlTextViewWidget = view.findViewById(R.id.description) - } - - override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder { - val view = LayoutInflater.from(viewGroup.context) - .inflate(R.layout.your_event_explanation_item, viewGroup, false) - - return ViewHolder(view) - } - - override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) { - viewHolder.htmlTextViewWidget.setHtmlText( - htmlText = dataSet[position].description - ) - } - - override fun getItemCount() = dataSet.size -} - -class YourEventExplanationFragment : Fragment(R.layout.fragment_your_event_explanation) { - - private val args: YourEventExplanationFragmentArgs by navArgs() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val binding = FragmentYourEventExplanationBinding.bind(view) - - val adapter = YourEventExplanationAdapter( - dataSet = args.data - ) - - binding.scroll.adapter = adapter - val linearLayoutManager = LinearLayoutManager(requireContext()) - val dividerItemDecoration = DividerItemDecoration(requireContext(), linearLayoutManager.orientation) - binding.scroll.layoutManager = linearLayoutManager - if (args.data.size > 1) { - binding.scroll.addItemDecoration(dividerItemDecoration) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragment.kt deleted file mode 100644 index f1c593c0a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragment.kt +++ /dev/null @@ -1,739 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events - -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.core.view.forEachIndexed -import androidx.navigation.fragment.navArgs -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYearTime -import nl.rijksoverheid.ctr.design.fragments.info.ButtonData -import nl.rijksoverheid.ctr.design.fragments.info.DescriptionData -import nl.rijksoverheid.ctr.design.fragments.info.InfoFragmentData -import nl.rijksoverheid.ctr.design.utils.DialogUtil -import nl.rijksoverheid.ctr.design.utils.InfoFragmentUtil -import nl.rijksoverheid.ctr.holder.BaseFragment -import nl.rijksoverheid.ctr.holder.HolderMainFragment -import nl.rijksoverheid.ctr.holder.MainNavDirections -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.FragmentYourEventsBinding -import nl.rijksoverheid.ctr.holder.fuzzy_matching.MatchingBlobIds -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.your_events.models.ConflictingEventResult -import nl.rijksoverheid.ctr.holder.your_events.models.YourEventsEndState -import nl.rijksoverheid.ctr.holder.your_events.models.YourEventsEndStateWithCustomTitle -import nl.rijksoverheid.ctr.holder.your_events.utils.InfoScreenUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteProtocol3Util -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsEndStateUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil -import nl.rijksoverheid.ctr.holder.your_events.widgets.YourEventWidget -import nl.rijksoverheid.ctr.holder.your_events.widgets.YourEventWidgetUtil -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.ext.capitalize -import nl.rijksoverheid.ctr.shared.ext.findNavControllerSafety -import nl.rijksoverheid.ctr.shared.ext.navigateSafety -import nl.rijksoverheid.ctr.shared.livedata.EventObserver -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.BlockedEventException -import nl.rijksoverheid.ctr.shared.models.ErrorResultFragmentData -import nl.rijksoverheid.ctr.shared.models.Flow -import org.json.JSONObject -import org.koin.android.ext.android.inject -import org.koin.androidx.viewmodel.ext.android.viewModel - -class YourEventsFragment : BaseFragment(R.layout.fragment_your_events) { - - private val args: YourEventsFragmentArgs by navArgs() - - private val infoScreenUtil: InfoScreenUtil by inject() - private val dialogUtil: DialogUtil by inject() - private val infoFragmentUtil: InfoFragmentUtil by inject() - - private val remoteProtocol3Util: RemoteProtocol3Util by inject() - private val remoteEventUtil: RemoteEventUtil by inject() - private val yourEventsFragmentUtil: YourEventsFragmentUtil by inject() - private val yourEventWidgetUtil: YourEventWidgetUtil by inject() - private val yourEventsEndStateUtil: YourEventsEndStateUtil by inject() - - private val cachedAppConfigUseCase: CachedAppConfigUseCase by inject() - - private val yourEventsViewModel: YourEventsViewModel by viewModel() - - override fun onButtonClickWithRetryTitle(): Int { - return R.string.dialog_retry - } - - override fun onButtonClickWithRetryAction() { - findNavControllerSafety()?.popBackStack() - } - - override fun getFlow(): Flow { - return args.flow - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val binding = FragmentYourEventsBinding.bind(view) - - presentHeader( - binding = binding - ) - - presentEvents( - binding = binding - ) - - presentFooter( - binding = binding - ) - - handleButton( - binding = binding - ) - - blockBackButton() - - yourEventsViewModel.loading.observe(viewLifecycleOwner, EventObserver { - (parentFragment?.parentFragment as HolderMainFragment).presentLoading(it) - binding.bottom.setButtonEnabled(!it) - binding.eventsGroup.forEachIndexed { index, _ -> - val eventGroup = binding.eventsGroup.getChildAt(index) as YourEventWidget - eventGroup.setButtonsEnabled(!it) - } - }) - - yourEventsViewModel.yourEventsResult.observe( - viewLifecycleOwner, - EventObserver { databaseSyncerResult -> - val fragmentType = args.type - - when (databaseSyncerResult) { - is DatabaseSyncerResult.Success -> { - if (getFlow() == HolderFlow.Migration) { - infoFragmentUtil.presentFullScreen( - currentFragment = this, - toolbarTitle = "", - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_migrationFlow_migrationSuccessful_title), - descriptionData = DescriptionData( - htmlText = R.string.holder_migrationFlow_migrationSuccessful_message, - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.back_to_overview), - navigationActionId = MainNavDirections.actionMyOverview().actionId - ) - ), - hideNavigationIcon = true - ) - } else { - handleEndState( - endState = yourEventsEndStateUtil.getEndState( - context = requireContext(), - hints = databaseSyncerResult.hints, - blockedEvents = databaseSyncerResult.blockedEvents, - newEvents = when (fragmentType) { - is YourEventsFragmentType.DCC -> fragmentType.remoteEvent.events - ?: listOf() - is YourEventsFragmentType.RemoteProtocol3Type -> fragmentType.remoteEvents.keys.toList() - .map { it.events ?: listOf() }.flatten() - } - ) - ) - } - } - is DatabaseSyncerResult.Failed -> { - presentError( - errorResult = databaseSyncerResult.errorResult - ) - } - is DatabaseSyncerResult.FuzzyMatchingError -> { - navigateSafety( - YourEventsFragmentDirections.actionFuzzyMatching( - MatchingBlobIds(databaseSyncerResult.matchingBlobIds) - ) - ) - } - } - }) - - yourEventsViewModel.conflictingEventsResult.observe( - viewLifecycleOwner, - EventObserver { - when (it) { - ConflictingEventResult.Existing -> { - infoFragmentUtil.presentFullScreen( - currentFragment = this, - toolbarTitle = args.toolbarTitle, - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_listRemoteEvents_endStateDuplicate_title), - descriptionData = DescriptionData( - htmlText = R.string.holder_listRemoteEvents_endStateDuplicate_message - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ), - hideNavigationIcon = true - ) - } - ConflictingEventResult.Holder -> replaceCertificateDialog(getEventsFromType()) - ConflictingEventResult.None -> yourEventsViewModel.saveRemoteProtocolEvents( - getFlow(), getEventsFromType(), false - ) - } - } - ) - } - - private fun getEventsFromType() = when (val type = args.type) { - is YourEventsFragmentType.DCC -> type.getRemoteEvents() - is YourEventsFragmentType.RemoteProtocol3Type -> type.remoteEvents - } - - private fun handleEndState(endState: YourEventsEndState) { - when (endState) { - is YourEventsEndState.BlockedEvent -> { - val helpdeskPhoneNumber = - cachedAppConfigUseCase.getCachedAppConfig().contactInfo.phoneNumber - infoFragmentUtil.presentFullScreen( - currentFragment = this, - toolbarTitle = getString(R.string.holder_listRemoteEvents_endStateCantCreateCertificate_title), - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.holder_listRemoteEvents_endStateNoValidCertificate_title), - descriptionData = DescriptionData( - htmlTextString = getString( - R.string.holder_listRemoteEvents_endStateNoValidCertificate_body, - helpdeskPhoneNumber, - helpdeskPhoneNumber, - errorCodeStringFactory.get( - getFlow(), - listOf( - AppErrorResult( - HolderStep.GetCredentialsNetworkRequest, - BlockedEventException() - ) - ) - ) - ), - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ) - ) - } - is YourEventsEndState.Hints -> { - infoFragmentUtil.presentFullScreen( - currentFragment = this, - toolbarTitle = getString(R.string.certificate_created_toolbar_title), - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(R.string.certificate_created_toolbar_title), - descriptionData = DescriptionData( - htmlTextString = endState.localisedHints.joinToString("

"), - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ), - hideNavigationIcon = true - ) - } - is YourEventsEndState.WeCouldntMakeACertificateError -> { - val errorCode = errorCodeStringFactory.get( - flow = getFlow(), - errorResults = listOf( - AppErrorResult(HolderStep.GetCredentialsNetworkRequest, endState.exception) - ) - ) - presentError( - data = ErrorResultFragmentData( - title = getString(R.string.holder_listRemoteEvents_endStateCantCreateCertificate_title), - description = getString( - R.string.holder_listRemoteEvents_endStateCantCreateCertificate_message, - yourEventsEndStateUtil.getErrorStateSubstring( - requireContext(), - getFlow() - ), - errorCode - ), - buttonTitle = getString(R.string.general_toMyOverview), - buttonAction = ErrorResultFragmentData.ButtonAction.Destination(R.id.action_my_overview) - ) - ) - } - is YourEventsEndStateWithCustomTitle -> { - infoFragmentUtil.presentFullScreen( - currentFragment = this, - toolbarTitle = if (endState is YourEventsEndStateWithCustomTitle.RecoveryTooOld || - endState is YourEventsEndStateWithCustomTitle.NoRecoveryButDosisCorrection || - endState is YourEventsEndStateWithCustomTitle.RecoveryAndDosisCorrection - ) { - getString(R.string.your_positive_test_toolbar_title) - } else { - getString(R.string.certificate_created_toolbar_title) - }, - data = InfoFragmentData.TitleDescriptionWithButton( - title = getString(endState.title), - descriptionData = DescriptionData( - htmlTextString = getString(endState.description), - htmlLinksEnabled = true - ), - primaryButtonData = ButtonData.NavigationButton( - text = getString(R.string.general_toMyOverview), - navigationActionId = R.id.action_my_overview - ) - ), - hideNavigationIcon = true - ) - } - else -> { - navigateSafety(YourEventsFragmentDirections.actionMyOverview()) - } - } - } - - private fun replaceCertificateDialog( - remoteEvents: Map - ) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.your_events_replace_dialog_title, - message = getString(R.string.your_events_replace_dialog_message), - positiveButtonText = R.string.your_events_replace_dialog_positive_button, - positiveButtonCallback = { - yourEventsViewModel.saveRemoteProtocolEvents( - flow = getFlow(), - remoteProtocols = remoteEvents, - removePreviousEvents = true - ) - }, - negativeButtonText = R.string.your_events_replace_dialog_negative_button, - negativeButtonCallback = { - findNavControllerSafety()?.popBackStack() - } - ) - } - - private fun presentEvents(binding: FragmentYourEventsBinding) { - when (val type = args.type) { - is YourEventsFragmentType.RemoteProtocol3Type -> presentEvents( - type.remoteEvents, - binding - ) - is YourEventsFragmentType.DCC -> presentEvents( - type.getRemoteEvents(), - binding, - isDccEvent = true - ) - } - } - - private fun presentEvents( - remoteEvents: Map, - binding: FragmentYourEventsBinding, - isDccEvent: Boolean = false - ) { - val protocols = remoteEvents.map { it.key } - - val groupedEvents = remoteProtocol3Util.groupEvents(protocols) - - groupedEvents.forEach { protocolGroupedEvent -> - val holder = protocolGroupedEvent.value.firstOrNull()?.holder - val providerIdentifiers = - protocolGroupedEvent.value.map { it.providerIdentifier } - .map { - yourEventsFragmentUtil.getProviderName( - providers = cachedAppConfigUseCase.getCachedAppConfig().providers, - providerIdentifier = it - ) - } - - val allSameEvents = protocolGroupedEvent.value.map { it.remoteEvent } - val allEventsInformation = protocolGroupedEvent.value.map { - RemoteEventInformation(it.providerIdentifier, holder, it.remoteEvent) - } - remoteEventUtil.removeDuplicateEvents(allSameEvents).forEach { remoteEvent -> - when (remoteEvent) { - is RemoteEventVaccination -> { - presentVaccinationEvent( - binding = binding, - providerIdentifiers = providerIdentifiers.toSet() - .joinToString(" ${getString(R.string.your_events_and)} "), - vaccinationDate = yourEventsFragmentUtil.getVaccinationDate(remoteEvent.vaccination?.date), - fullName = yourEventsFragmentUtil.getFullName(holder), - birthDate = yourEventsFragmentUtil.getBirthDate(holder), - currentEvent = remoteEvent, - allEventsInformation = allEventsInformation, - isDccEvent = isDccEvent || providerIdentifiers.any { it.contains("dcc") } - ) - } - is RemoteEventNegativeTest -> { - presentNegativeTestEvent( - binding = binding, - providerIdentifiers = providerIdentifiers.toSet() - .joinToString(" ${getString(R.string.your_events_and)} "), - fullName = yourEventsFragmentUtil.getFullName(holder), - birthDate = yourEventsFragmentUtil.getBirthDate(holder), - event = remoteEvent - ) - } - is RemoteEventPositiveTest -> { - presentPositiveTestEvent( - binding = binding, - providerIdentifiers = providerIdentifiers.toSet() - .joinToString(" ${getString(R.string.your_events_and)} "), - fullName = yourEventsFragmentUtil.getFullName(holder), - birthDate = yourEventsFragmentUtil.getBirthDate(holder), - event = remoteEvent - ) - } - is RemoteEventRecovery -> { - presentRecoveryEvent( - binding = binding, - providerIdentifiers = providerIdentifiers.toSet() - .joinToString(" ${getString(R.string.your_events_and)} "), - fullName = yourEventsFragmentUtil.getFullName(holder), - birthDate = yourEventsFragmentUtil.getBirthDate(holder), - event = remoteEvent - ) - } - } - } - } - } - - private fun presentVaccinationEvent( - binding: FragmentYourEventsBinding, - providerIdentifiers: String, - vaccinationDate: String, - fullName: String, - birthDate: String, - currentEvent: RemoteEventVaccination, - allEventsInformation: List, - isDccEvent: Boolean - ) { - val type = args.type - val infoScreen = infoScreenUtil.getForVaccination( - event = currentEvent, - fullName = fullName, - birthDate = birthDate, - providerIdentifier = allEventsInformation.first().providerIdentifier, - europeanCredential = if (type is YourEventsFragmentType.DCC) { - JSONObject(type.eventGroupJsonData.decodeToString()).getString("credential") - .toByteArray() - } else { - null - } - ) - - val eventWidget = YourEventWidget(requireContext()).apply { - setContent( - title = yourEventWidgetUtil.getVaccinationEventTitle( - context, - isDccEvent, - currentEvent - ), - subtitle = yourEventWidgetUtil.getVaccinationEventSubtitle( - context, - isDccEvent, - providerIdentifiers, - vaccinationDate, - fullName, - birthDate - ), - infoClickListener = { - navigateSafety( - YourEventsFragmentDirections.actionShowExplanation( - toolbarTitle = infoScreen.title, - data = allEventsInformation.map { - val vaccinationEvent = - it.remoteEvent as RemoteEventVaccination - infoScreenUtil.getForVaccination( - event = vaccinationEvent, - fullName = fullName, - birthDate = birthDate, - providerIdentifier = yourEventsFragmentUtil.getProviderName( - providers = cachedAppConfigUseCase.getCachedAppConfig().providers, - providerIdentifier = it.providerIdentifier - ), - europeanCredential = if (type is YourEventsFragmentType.DCC) { - JSONObject(type.eventGroupJsonData.decodeToString()).getString( - "credential" - ).toByteArray() - } else { - null - } - ) - }.toTypedArray() - ) - ) - } - ) - } - binding.eventsGroup.addView(eventWidget) - } - - private fun presentNegativeTestEvent( - binding: FragmentYourEventsBinding, - providerIdentifiers: String, - fullName: String, - birthDate: String, - event: RemoteEventNegativeTest - ) { - val type = args.type - - val testDate = - event.negativeTest?.sampleDate?.formatDayMonthYearTime(requireContext()) ?: "" - - val infoScreen = infoScreenUtil.getForNegativeTest( - event = event, - fullName = fullName, - testDate = testDate, - birthDate = birthDate, - europeanCredential = if (type is YourEventsFragmentType.DCC) { - JSONObject(type.eventGroupJsonData.decodeToString()).getString("credential") - .toByteArray() - } else { - null - } - ) - - val eventWidget = YourEventWidget(requireContext()).apply { - setContent( - title = getString(R.string.your_negative_test_results_row_title), - subtitle = getString( - R.string.your_negative_test_3_0_results_row_subtitle, - testDate, - fullName, - birthDate, - providerIdentifiers - ), - infoClickListener = { - navigateSafety( - YourEventsFragmentDirections.actionShowExplanation( - toolbarTitle = infoScreen.title, - data = arrayOf(infoScreen) - ) - ) - } - ) - } - binding.eventsGroup.addView(eventWidget) - } - - private fun presentPositiveTestEvent( - binding: FragmentYourEventsBinding, - providerIdentifiers: String, - fullName: String, - birthDate: String, - event: RemoteEventPositiveTest - ) { - val testDate = - event.positiveTest?.sampleDate?.formatDayMonthYearTime(requireContext()) ?: "" - - val infoScreen = infoScreenUtil.getForPositiveTest( - event = event, - testDate = testDate, - fullName = fullName, - birthDate = birthDate - ) - - val eventWidget = YourEventWidget(requireContext()).apply { - setContent( - title = getString(R.string.positive_test_title), - subtitle = getString( - R.string.your_negative_test_3_0_results_row_subtitle, - testDate, - fullName, - birthDate, - providerIdentifiers - ), - infoClickListener = { - navigateSafety( - YourEventsFragmentDirections.actionShowExplanation( - toolbarTitle = infoScreen.title, - data = arrayOf(infoScreen) - ) - ) - } - ) - } - binding.eventsGroup.addView(eventWidget) - } - - private fun presentRecoveryEvent( - binding: FragmentYourEventsBinding, - providerIdentifiers: String, - fullName: String, - birthDate: String, - event: RemoteEventRecovery - ) { - val type = args.type - val testDate = event.recovery?.sampleDate?.formatDayMonthYear() ?: "" - - val infoScreen = infoScreenUtil.getForRecovery( - event = event, - fullName = fullName, - testDate = testDate, - birthDate = birthDate, - europeanCredential = if (type is YourEventsFragmentType.DCC) { - JSONObject(type.eventGroupJsonData.decodeToString()).getString("credential") - .toByteArray() - } else { - null - } - ) - - val eventWidget = YourEventWidget(requireContext()).apply { - setContent( - title = getString(R.string.general_recoverycertificate).capitalize(), - subtitle = getString( - R.string.your_negative_test_3_0_results_row_subtitle, - testDate, - fullName, - birthDate, - providerIdentifiers - ), - infoClickListener = { - navigateSafety( - YourEventsFragmentDirections.actionShowExplanation( - toolbarTitle = infoScreen.title, - data = arrayOf(infoScreen) - ) - ) - } - ) - } - binding.eventsGroup.addView(eventWidget) - } - - private fun handleButton(binding: FragmentYourEventsBinding) { - binding.bottom.setButtonClick { - yourEventsViewModel.checkForConflictingEvents( - remoteProtocols = getEventsFromType() - ) - } - binding.bottom.setButtonText( - getString( - if (getFlow() == HolderFlow.Migration) { - R.string.holder_migrationFlow_scannedDetailsOverview_transferButton - } else { - R.string.my_overview_add_qr_button - } - ) - ) - } - - private fun presentHeader(binding: FragmentYourEventsBinding) { - binding.description.setText(yourEventsFragmentUtil.getHeaderCopy(args.type, args.flow)) - } - - private fun presentFooter(binding: FragmentYourEventsBinding) { - binding.somethingWrongButton.run { - visibility = if ( - args.type is YourEventsFragmentType.DCC || args.flow is HolderFlow.Migration - ) View.GONE else View.VISIBLE - setOnClickListener { - val type = args.type - infoFragmentUtil.presentAsBottomSheet( - childFragmentManager, InfoFragmentData.TitleDescription( - title = getString(R.string.holder_listRemoteEvents_somethingWrong_title), - descriptionData = DescriptionData( - htmlText = if (type is YourEventsFragmentType.RemoteProtocol3Type) { - val origins = type.remoteEvents.keys - .flatMap { it.events ?: emptyList() } - .map { remoteEventUtil.getOriginType(it) } - when { - origins.all { it == OriginType.Vaccination } -> { - if (getFlow() == HolderFlow.VaccinationAndPositiveTest) { - R.string.holder_listRemoteEvents_somethingWrong_vaccinationAndPositiveTest_body - } else { - R.string.holder_listRemoteEvents_somethingWrong_vaccination_body - } - } - origins.all { it == OriginType.Recovery } -> { - R.string.dialog_negative_test_result_something_wrong_description - } - origins.contains(OriginType.Vaccination) && - origins.contains(OriginType.Recovery) -> { - R.string.holder_listRemoteEvents_somethingWrong_vaccinationAndPositiveTest_body - } - else -> R.string.dialog_negative_test_result_something_wrong_description - } - } else { - R.string.dialog_negative_test_result_something_wrong_description - }, - htmlLinksEnabled = true - ) - ) - ) - } - } - } - - private fun blockBackButton() { - // Catch back button to show modal instead - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : - OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - if (isAdded) { - if (getFlow() == HolderFlow.Migration) { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.holder_migrationFlow_goBack_dialog_title, - message = getString(R.string.holder_migrationFlow_goBack_dialog_message), - positiveButtonText = R.string.holder_migrationFlow_goBack_dialog_yesButton, - positiveButtonCallback = { - findNavControllerSafety()?.popBackStack() - }, - negativeButtonText = R.string.holder_migrationFlow_goBack_dialog_noButton - ) - } else { - dialogUtil.presentDialog( - context = requireContext(), - title = R.string.your_events_block_back_dialog_title, - message = getString( - yourEventsFragmentUtil.getCancelDialogDescription( - type = args.type - ) - ), - positiveButtonText = R.string.your_events_block_back_dialog_positive_button, - positiveButtonCallback = { - findNavControllerSafety()?.popBackStack() - }, - negativeButtonText = R.string.your_events_block_back_dialog_negative_button - ) - } - } - } - }) - } - - override fun onDestroyView() { - super.onDestroyView() - (parentFragment?.parentFragment as? HolderMainFragment)?.presentLoading(false) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragmentType.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragmentType.kt deleted file mode 100644 index 89d3600e5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsFragmentType.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.holder.get_events.models.EventProvider -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -sealed class YourEventsFragmentType : Parcelable { - - @Parcelize - data class RemoteProtocol3Type( - val remoteEvents: Map, - val eventProviders: List = emptyList() - ) : YourEventsFragmentType() - - @Parcelize - data class DCC( - val remoteEvent: RemoteProtocol, - val eventGroupJsonData: ByteArray, - val originType: OriginType - ) : YourEventsFragmentType(), Parcelable { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as DCC - - if (remoteEvent != other.remoteEvent) return false - if (!eventGroupJsonData.contentEquals(other.eventGroupJsonData)) return false - if (originType != other.originType) return false - - return true - } - - override fun hashCode(): Int { - var result = remoteEvent.hashCode() - result = 31 * result + eventGroupJsonData.contentHashCode() - result = 31 * result + originType.hashCode() - return result - } - - fun getRemoteEvents(): Map = - mapOf(remoteEvent to eventGroupJsonData) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsViewModel.kt deleted file mode 100644 index b509e9602..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/YourEventsViewModel.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.launch -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.your_events.models.ConflictingEventResult -import nl.rijksoverheid.ctr.holder.your_events.usecases.SaveEventsUseCase -import nl.rijksoverheid.ctr.holder.your_events.usecases.SaveEventsUseCaseImpl -import nl.rijksoverheid.ctr.persistence.database.DatabaseSyncerResult -import nl.rijksoverheid.ctr.persistence.database.HolderDatabaseSyncer -import nl.rijksoverheid.ctr.persistence.database.usecases.DraftEventUseCase -import nl.rijksoverheid.ctr.shared.livedata.Event -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.Flow - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -abstract class YourEventsViewModel : ViewModel() { - val loading: LiveData> = MutableLiveData() - val yourEventsResult: LiveData> = MutableLiveData() - val conflictingEventsResult: LiveData> = MutableLiveData() - - abstract fun saveRemoteProtocolEvents( - flow: Flow, - remoteProtocols: Map, - removePreviousEvents: Boolean - ) - - abstract fun checkForConflictingEvents(remoteProtocols: Map) -} - -data class RemoteEventInformation( - val providerIdentifier: String, - val holder: RemoteProtocol.Holder?, - val remoteEvent: RemoteEvent -) - -class YourEventsViewModelImpl( - private val saveEventsUseCase: SaveEventsUseCase, - private val holderDatabaseSyncer: HolderDatabaseSyncer, - private val draftEventUseCase: DraftEventUseCase -) : YourEventsViewModel() { - - override fun checkForConflictingEvents(remoteProtocols: Map) { - (loading as MutableLiveData).value = Event(true) - viewModelScope.launch { - draftEventUseCase.remove() - try { - val conflictingEvents = - saveEventsUseCase.remoteProtocols3AreConflicting(remoteProtocols) - - (conflictingEventsResult as MutableLiveData).postValue(Event(conflictingEvents)) - } catch (e: Exception) { - (yourEventsResult as MutableLiveData).value = Event( - DatabaseSyncerResult.Failed.Error(AppErrorResult(HolderStep.StoringEvents, e)) - ) - } finally { - loading.value = Event(false) - } - } - } - - override fun saveRemoteProtocolEvents( - flow: Flow, - remoteProtocols: Map, - removePreviousEvents: Boolean - ) { - (loading as MutableLiveData).value = Event(true) - viewModelScope.launch { - try { - // Save the events in the database - val result = saveEventsUseCase.saveRemoteProtocols3( - remoteProtocols = remoteProtocols, - removePreviousEvents = removePreviousEvents, - flow = flow - ) - - when (result) { - is SaveEventsUseCaseImpl.SaveEventResult.Success -> { - // Send all events to database and create green cards, origins and credentials - val databaseSyncerResult = holderDatabaseSyncer.sync( - flow = flow, - newEvents = remoteProtocols.keys.flatMap { it.events ?: listOf() } - ) - - (yourEventsResult as MutableLiveData).value = Event( - databaseSyncerResult - ) - } - is SaveEventsUseCaseImpl.SaveEventResult.Failed -> { - (yourEventsResult as MutableLiveData).value = - Event(DatabaseSyncerResult.Failed.Error(result.errorResult)) - } - } - } catch (e: Exception) { - (yourEventsResult as MutableLiveData).value = Event( - DatabaseSyncerResult.Failed.Error(AppErrorResult(HolderStep.StoringEvents, e)) - ) - } finally { - loading.value = Event(false) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/ConflictingEventResult.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/ConflictingEventResult.kt deleted file mode 100644 index b528eba12..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/ConflictingEventResult.kt +++ /dev/null @@ -1,7 +0,0 @@ -package nl.rijksoverheid.ctr.holder.your_events.models - -sealed class ConflictingEventResult { - object Holder : ConflictingEventResult() - object Existing : ConflictingEventResult() - object None : ConflictingEventResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemoteGreenCards.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemoteGreenCards.kt deleted file mode 100644 index 06482f5aa..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemoteGreenCards.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.models - -import com.squareup.moshi.JsonClass -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class RemoteGreenCards( - val euGreencards: List?, - val blobExpireDates: List?, - val context: Context? = null, - val hints: List? = listOf() -) { - - data class EuGreenCard( - val origins: List, - val credential: String - ) - - data class Origin( - val type: OriginType, - val eventTime: OffsetDateTime, - val expirationTime: OffsetDateTime, - val validFrom: OffsetDateTime, - val doseNumber: Int?, - val hints: List? = listOf() - ) - - data class BlobExpiry( - val id: Int, - val expiry: OffsetDateTime, - val reason: String = "" - ) - - data class Context(val matchingBlobIds: List>) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemoteNonce.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemoteNonce.kt deleted file mode 100644 index 47b912acc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemoteNonce.kt +++ /dev/null @@ -1,17 +0,0 @@ -package nl.rijksoverheid.ctr.holder.your_events.models - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@JsonClass(generateAdapter = true) -data class RemoteNonce( - val nonce: String, - @Json(name = "stoken") val sToken: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemotePrepareIssue.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemotePrepareIssue.kt deleted file mode 100644 index 7b652dab5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/RemotePrepareIssue.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.models - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class RemotePrepareIssue( - val stoken: String, - val prepareIssueMessage: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as RemotePrepareIssue - - if (stoken != other.stoken) return false - if (!prepareIssueMessage.contentEquals(other.prepareIssueMessage)) return false - - return true - } - - override fun hashCode(): Int { - var result = stoken.hashCode() - result = 31 * result + prepareIssueMessage.contentHashCode() - return result - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/YourEventsEndState.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/YourEventsEndState.kt deleted file mode 100644 index 417c118c1..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/models/YourEventsEndState.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.models - -import androidx.annotation.StringRes -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.shared.models.WeCouldntCreateCertificateException - -sealed class YourEventsEndState { - object None : YourEventsEndState() - object BlockedEvent : YourEventsEndState() - data class Hints(val localisedHints: List) : YourEventsEndState() - // endstate showing an error code - data class WeCouldntMakeACertificateError(val exception: WeCouldntCreateCertificateException) : YourEventsEndState() -} - -// additional endstates with custom title and description which cannot be generated by the hints system yet -sealed class YourEventsEndStateWithCustomTitle(@StringRes val title: Int, @StringRes val description: Int) : YourEventsEndState() { - object InternationalQROnly : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateInternationalQROnly_title, - R.string.holder_listRemoteEvents_endStateInternationalQROnly_message) - object VaccinationsAndRecovery : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateVaccinationsAndRecovery_title, - R.string.holder_listRemoteEvents_endStateVaccinationsAndRecovery_message) - object InternationalVaccinationAndRecovery : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateInternationalVaccinationAndRecovery_title, - R.string.holder_listRemoteEvents_endStateInternationalVaccinationAndRecovery_message) - object RecoveryOnly : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateRecoveryOnly_title, - R.string.holder_listRemoteEvents_endStateRecoveryOnly_message) - object NoRecoveryButDosisCorrection : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateNoRecoveryButDosisCorrection_title, - R.string.holder_listRemoteEvents_endStateNoRecoveryButDosisCorrection_message) - object RecoveryTooOld : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateRecoveryTooOld_title, - R.string.holder_listRemoteEvents_endStateRecoveryTooOld_message) - object RecoveryAndDosisCorrection : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateRecoveryAndDosisCorrection_title, - R.string.holder_listRemoteEvents_endStateRecoveryAndDosisCorrection_message) - object WeCouldntMakeACertificate : YourEventsEndStateWithCustomTitle( - R.string.holder_listRemoteEvents_endStateCantCreateCertificate_title, - R.string.holder_listRemoteEvents_endStateCantCreateCertificate_message) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/usecases/SaveEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/usecases/SaveEventsUseCase.kt deleted file mode 100644 index 49f0fbf92..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/usecases/SaveEventsUseCase.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.usecases - -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.get_events.utils.ScopeUtil -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.your_events.models.ConflictingEventResult -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventHolderUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventUtil -import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteProtocol3Util -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.Flow - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface SaveEventsUseCase { - - suspend fun saveRemoteProtocols3( - remoteProtocols: Map, - removePreviousEvents: Boolean, - flow: Flow - ): SaveEventsUseCaseImpl.SaveEventResult - - suspend fun remoteProtocols3AreConflicting(remoteProtocols: Map): ConflictingEventResult -} - -class SaveEventsUseCaseImpl( - private val holderDatabase: HolderDatabase, - private val remoteEventHolderUtil: RemoteEventHolderUtil, - private val scopeUtil: ScopeUtil, - private val remoteEventUtil: RemoteEventUtil, - private val remoteProtocol3Util: RemoteProtocol3Util -) : SaveEventsUseCase { - - private suspend fun remoteEventExistsAlready(remoteEvents: List): Boolean { - val remoteEventUniques = remoteEvents.mapNotNull { it.unique } - val storedEventIdentifiers = holderDatabase.eventGroupDao().getAll().map { it.providerIdentifier } - return storedEventIdentifiers.any { identifier -> - remoteEventUniques.any { remoteEventUtil.isDccEvent(identifier) && identifier.contains(it) } - } - } - - override suspend fun remoteProtocols3AreConflicting(remoteProtocols: Map): ConflictingEventResult { - if (remoteEventExistsAlready(remoteProtocols.map { it.key }.flatMap { it.events ?: emptyList() })) { - return ConflictingEventResult.Existing - } - - val storedEventHolders = holderDatabase.eventGroupDao().getAll() - .mapNotNull { remoteEventHolderUtil.holder(it.jsonData, it.providerIdentifier) } - .distinct() - val incomingEventHolders = remoteProtocols.mapNotNull { it.key.holder }.distinct() - - return if (remoteEventHolderUtil.conflicting(storedEventHolders, incomingEventHolders)) { - ConflictingEventResult.Holder - } else { - ConflictingEventResult.None - } - } - - override suspend fun saveRemoteProtocols3( - remoteProtocols: Map, - removePreviousEvents: Boolean, - flow: Flow - ): SaveEventResult { - try { - if (removePreviousEvents) { - holderDatabase.eventGroupDao().deleteAll() - } - - val entities = remoteProtocols.map { - val remoteProtocol = it.key - val remoteEvents = remoteProtocol.events ?: listOf() - val originType = remoteEventUtil.getOriginType(remoteEvents.first()) - EventGroupEntity( - walletId = 1, - providerIdentifier = remoteProtocol3Util.getProviderIdentifier(remoteProtocol), - type = originType, - jsonData = it.value, - scope = scopeUtil.getScopeForOriginType( - originType = originType, - getPositiveTestWithVaccination = flow == HolderFlow.VaccinationAndPositiveTest - ), - expiryDate = null, - draft = true - ) - } - - // Save entity in database - holderDatabase.eventGroupDao().insertAll(entities) - } catch (e: Exception) { - return SaveEventResult.Failed( - errorResult = AppErrorResult( - step = HolderStep.StoringEvents, - e = e - ) - ) - } - return SaveEventResult.Success - } - - sealed class SaveEventResult { - object Success : SaveEventResult() - data class Failed(val errorResult: ErrorResult) : SaveEventResult() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/CreateInfoLineUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/CreateInfoLineUtil.kt deleted file mode 100644 index 70c7283ff..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/CreateInfoLineUtil.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.holder.your_events.utils - -import androidx.core.text.HtmlCompat - -abstract class CreateInfoLineUtil { - fun createdLine( - name: String, - nameAnswer: String, - isOptional: Boolean = false - ): String { - val sanitizedName = HtmlCompat.fromHtml(name, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() - val sanitizedAnswer = - HtmlCompat.fromHtml(nameAnswer, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() - return if (isOptional && nameAnswer.isEmpty()) "" else "$sanitizedName $sanitizedAnswer
" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/EventGroupEntityUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/EventGroupEntityUtil.kt deleted file mode 100644 index b8811c3b4..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/EventGroupEntityUtil.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase - -interface EventGroupEntityUtil { - fun getProviderName(providerIdentifier: String): String -} - -class EventGroupEntityUtilImpl( - private val cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : EventGroupEntityUtil { - - override fun getProviderName(providerIdentifier: String): String { - // Some provider identifiers have a unique appended to it, the first part is the actual provider identifier - val providerIdentifierWithoutUnique = providerIdentifier.split("_").first() - - return cachedAppConfigUseCase.getCachedAppConfig().providerIdentifiers - .firstOrNull { it.code == providerIdentifierWithoutUnique } - ?.name - ?: providerIdentifier - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/InfoScreenUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/InfoScreenUtil.kt deleted file mode 100644 index fe4d6d3ae..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/InfoScreenUtil.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination - -interface InfoScreenUtil { - - fun getForNegativeTest( - event: RemoteEventNegativeTest, - fullName: String, - testDate: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean = true - ): InfoScreen - - fun getForVaccination( - event: RemoteEventVaccination, - fullName: String, - birthDate: String, - providerIdentifier: String, - europeanCredential: ByteArray?, - addExplanation: Boolean = true - ): InfoScreen - - fun getForPositiveTest( - event: RemoteEventPositiveTest, - testDate: String, - fullName: String, - birthDate: String - ): InfoScreen - - fun getForRecovery( - event: RemoteEventRecovery, - testDate: String, - fullName: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean = true - ): InfoScreen -} - -class InfoScreenUtilImpl( - private val vaccinationInfoScreenUtil: VaccinationInfoScreenUtil, - private val testInfoScreenUtil: TestInfoScreenUtil, - private val recoveryInfoScreenUtil: RecoveryInfoScreenUtil -) : InfoScreenUtil { - - override fun getForNegativeTest( - event: RemoteEventNegativeTest, - fullName: String, - testDate: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean - ) = testInfoScreenUtil.getForNegativeTest(event, fullName, testDate, birthDate, europeanCredential, addExplanation) - - override fun getForVaccination( - event: RemoteEventVaccination, - fullName: String, - birthDate: String, - providerIdentifier: String, - europeanCredential: ByteArray?, - addExplanation: Boolean - ) = vaccinationInfoScreenUtil.getForVaccination(event, fullName, birthDate, providerIdentifier, europeanCredential, addExplanation) - - override fun getForPositiveTest( - event: RemoteEventPositiveTest, - testDate: String, - fullName: String, - birthDate: String - ) = testInfoScreenUtil.getForPositiveTest(event, testDate, fullName, birthDate) - - override fun getForRecovery( - event: RemoteEventRecovery, - testDate: String, - fullName: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean - ) = recoveryInfoScreenUtil.getForRecovery(event, testDate, fullName, birthDate, europeanCredential, addExplanation) -} - -@Parcelize -data class InfoScreen( - val title: String, - val description: String -) : Parcelable diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RecoveryInfoScreenUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RecoveryInfoScreenUtil.kt deleted file mode 100644 index 0f8ef8d11..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RecoveryInfoScreenUtil.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.content.res.Resources -import android.text.TextUtils -import java.util.Locale -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtil -import nl.rijksoverheid.ctr.holder.utils.CountryUtil - -interface RecoveryInfoScreenUtil { - - fun getForRecovery( - event: RemoteEventRecovery, - testDate: String, - fullName: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean = true - ): InfoScreen -} - -class RecoveryInfoScreenUtilImpl( - val resources: Resources, - private val paperProofUtil: PaperProofUtil, - private val countryUtil: CountryUtil -) : CreateInfoLineUtil(), RecoveryInfoScreenUtil { - - override fun getForRecovery( - event: RemoteEventRecovery, - testDate: String, - fullName: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean - ): InfoScreen { - - val validFromDate = event.recovery?.validFrom?.formatDayMonthYear() ?: "" - val validUntilDate = event.recovery?.validUntil?.formatDayMonthYear() ?: "" - - val isPaperCertificate = europeanCredential != null - - val title = - if (europeanCredential != null) resources.getString(R.string.your_vaccination_explanation_toolbar_title) else resources.getString( - R.string.your_test_result_explanation_toolbar_title - ) - val header = if (europeanCredential != null) { - resources.getString(R.string.paper_proof_event_explanation_header) - } else { - resources.getString(R.string.recovery_explanation_description_header) - } - - val countryValue = event.recovery?.country - val country = when { - countryValue.isNullOrEmpty() -> "NL" - else -> countryValue - } - - val description = (TextUtils.concat( - header, - "

", - createdLine( - resources.getString(R.string.recovery_explanation_description_name), - fullName - ), - createdLine( - resources.getString(R.string.recovery_explanation_description_birth_date), - birthDate, - isOptional = true - ), - "
", - createdLine( - resources.getString(R.string.recovery_explanation_description_test_date), - testDate - ), - createdLine( - resources.getString(R.string.holder_event_about_test_countrytestedin), - countryUtil.getCountryForInfoScreen(Locale.getDefault().language, country), - isOptional = true - ), - if (europeanCredential != null) { - val issuerAnswer = paperProofUtil.getIssuer(europeanCredential) - createdLine( - resources.getString(R.string.holder_dcc_issuer), - if (issuerAnswer == "Ministry of Health Welfare and Sport") { - resources.getString(R.string.qr_explanation_certificate_issuer) - } else { - issuerAnswer - }, - isOptional = true - ) - } else { - "" - }, - "
", - createdLine( - resources.getString(R.string.recovery_explanation_description_valid_from), - validFromDate - ), - createdLine( - resources.getString(R.string.recovery_explanation_description_valid_until), - validUntilDate - ), - "
", - createdLine( - resources.getString( - if (isPaperCertificate) { - R.string.holder_dcc_test_identifier - } else { - R.string.your_test_result_explanation_description_unique_identifier - } - ), - event.unique - ), - if (europeanCredential != null && addExplanation) { - paperProofUtil.getInfoScreenFooterText(europeanCredential) - } else { - "" - } - ) as String) - - return InfoScreen( - title = title, - description = description - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventHolderUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventHolderUtil.kt deleted file mode 100644 index dd0284c0f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventHolderUtil.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.util.Base64 -import com.squareup.moshi.JsonClass -import com.squareup.moshi.Moshi -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.paper_proof.usecases.GetEventsFromPaperProofQrUseCase -import nl.rijksoverheid.ctr.shared.models.JSON -import org.json.JSONObject - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface RemoteEventHolderUtil { - fun holder(data: ByteArray, providerIdentifier: String): RemoteProtocol.Holder? - fun conflicting( - storedEventHolders: List, - incomingEventHolders: List - ): Boolean -} - -class RemoteEventHolderUtilImpl( - private val moshi: Moshi, - private val getEventsFromPaperProofQrUseCase: GetEventsFromPaperProofQrUseCase, - private val remoteEventUtil: RemoteEventUtil, - private val yourEventsFragmentUtil: YourEventsFragmentUtil -) : RemoteEventHolderUtil { - override fun holder(data: ByteArray, providerIdentifier: String): RemoteProtocol.Holder? { - val remoteEvent = - if (remoteEventUtil.isDccEvent(providerIdentifier)) { - val qr = JSONObject(String(data)).optString("credential") - getEventsFromPaperProofQrUseCase.get(qr) - } else { - val payload = - moshi.adapter(SignedResponse::class.java).fromJson(String(data))!!.payload - val decodedPayload = String(Base64.decode(payload, Base64.DEFAULT)) - moshi.adapter(RemoteProtocol::class.java).fromJson(decodedPayload)!! - } - return remoteEvent.holder - } - - /** - * Compare the holder of the currently stored events with the holder of the new importing events - * If the birth date or month are different, then the holders are conflicting and - * we should keep only one of them. - */ - override fun conflicting( - storedEventHolders: List, - incomingEventHolders: List - ): Boolean { - storedEventHolders.forEach { storedEventHolder -> - val storedBirthdate = yourEventsFragmentUtil.getBirthDate(storedEventHolder) - incomingEventHolders.forEach { incomingEventHolder -> - val incomingBirthdate = yourEventsFragmentUtil.getBirthDate(incomingEventHolder) - return birthDateIsNotMatching(storedBirthdate, incomingBirthdate) - } - } - return false - } - - private fun birthDateIsNotMatching(stored: String, incoming: String): Boolean { - if (stored == incoming) { - return false - } - - val storedBirthdayParts = stored.split(" ") - val incomingBirthdayParts = incoming.split(" ") - - val storedBirthDay: String? = storedBirthdayParts.getOrNull(0) - val storedBirthMonth: String? = storedBirthdayParts.getOrNull(1) - val incomingBirthDay: String? = incomingBirthdayParts.getOrNull(0) - val incomingBirthMonth: String? = incomingBirthdayParts.getOrNull(1) - - return storedBirthDay != incomingBirthDay || storedBirthMonth != incomingBirthMonth - } -} - -@JsonClass(generateAdapter = true) -data class SignedResponse(val signature: String, val payload: String) : JSON() diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventStringUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventStringUtil.kt deleted file mode 100644 index 65adfc81a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventStringUtil.kt +++ /dev/null @@ -1,34 +0,0 @@ -package nl.rijksoverheid.ctr.holder.your_events.utils - -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.shared.ext.capitalize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -interface RemoteEventStringUtil { - fun remoteEventTitle(remoteEventClass: Class): String -} - -class RemoteEventStringUtilImpl( - private val getString: (Int) -> String -) : RemoteEventStringUtil { - override fun remoteEventTitle(remoteEventClass: Class): String { - return when (remoteEventClass) { - RemoteEventVaccination::class.java -> getString(R.string.general_vaccination) - RemoteEventNegativeTest::class.java -> getString(R.string.general_negativeTest) - RemoteEventPositiveTest::class.java -> getString(R.string.general_positiveTest) - RemoteEventRecovery::class.java -> getString(R.string.general_recoverycertificate) - else -> "" - }.capitalize() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventUtil.kt deleted file mode 100644 index d46967518..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteEventUtil.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.util.Base64 -import com.squareup.moshi.Moshi -import java.time.LocalDate -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteConfigProviders -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventRecovery -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.ext.getStringOrNull -import org.json.JSONException -import org.json.JSONObject - -interface RemoteEventUtil { - fun isDccEvent(providerIdentifier: String): Boolean - fun getHolderFromDcc(dcc: JSONObject): RemoteProtocol.Holder - fun removeDuplicateEvents(remoteEvents: List): List - fun getRemoteEventFromDcc(dcc: JSONObject): RemoteEvent - fun getRemoteVaccinationFromDcc(dcc: JSONObject): RemoteEventVaccination? - fun getRemoteRecoveryFromDcc(dcc: JSONObject): RemoteEventRecovery? - fun getRemoteTestFromDcc(dcc: JSONObject): RemoteEventNegativeTest? - fun getRemoteProtocol3FromNonDcc(eventGroupEntity: EventGroupEntity): RemoteProtocol? - fun getOriginType(remoteEvent: RemoteEvent): OriginType -} - -class RemoteEventUtilImpl( - private val moshi: Moshi -) : RemoteEventUtil { - - /** - * Only remove duplicate events for vaccination events - */ - override fun removeDuplicateEvents(remoteEvents: List): List { - return if (remoteEvents.all { it is RemoteEventVaccination }) { - return remoteEvents.filterIsInstance().distinct() - } else { - remoteEvents - } - } - - override fun isDccEvent(providerIdentifier: String): Boolean { - return providerIdentifier.startsWith(RemoteConfigProviders.EventProvider.PROVIDER_IDENTIFIER_DCC) - } - - @Throws(NullPointerException::class) - override fun getHolderFromDcc(dcc: JSONObject): RemoteProtocol.Holder { - val fullName = dcc.optJSONObject("nam") ?: throw NullPointerException("can't parse name") - return RemoteProtocol.Holder( - infix = "", - firstName = fullName.getStringOrNull("gn"), - lastName = fullName.getStringOrNull("fn"), - birthDate = dcc.getStringOrNull("dob") - ) - } - - @Throws(JSONException::class) - override fun getRemoteEventFromDcc(dcc: JSONObject): RemoteEvent { - return getRemoteVaccinationFromDcc(dcc) ?: getRemoteRecoveryFromDcc(dcc) ?: getRemoteTestFromDcc(dcc) - ?: throw JSONException("can't parse event type") - } - - override fun getRemoteVaccinationFromDcc(dcc: JSONObject): RemoteEventVaccination? { - return getEventByType(dcc, "v")?.let { - RemoteEventVaccination( - type = "vaccination", - unique = it.getStringOrNull("ci"), - vaccination = RemoteEventVaccination.Vaccination( - doseNumber = it.getStringOrNull("dn"), - totalDoses = it.getStringOrNull("sd"), - date = try { LocalDate.parse(it.getStringOrNull("dt")?.take(10)) } catch (e: Exception) { null }, - country = it.getStringOrNull("co"), - type = it.getStringOrNull("vp"), - brand = it.getStringOrNull("mp"), - manufacturer = it.getStringOrNull("ma"), - completedByMedicalStatement = null, - hpkCode = null, - completedByPersonalStatement = null, - completionReason = null - ) - ) - } - } - - override fun getRemoteRecoveryFromDcc(dcc: JSONObject): RemoteEventRecovery? { - return getEventByType(dcc, "r")?.let { - RemoteEventRecovery( - type = "recovery", - unique = it.getStringOrNull("ci") ?: "", - isSpecimen = false, - recovery = RemoteEventRecovery.Recovery( - sampleDate = try { LocalDate.parse(it.getStringOrNull("fr")?.take(10)) } catch (e: Exception) { null }, - validFrom = try { LocalDate.parse(it.getStringOrNull("df")?.take(10)) } catch (e: Exception) { null }, - validUntil = try { LocalDate.parse(it.getStringOrNull("du")?.take(10)) } catch (e: Exception) { null }, - country = it.getStringOrNull("co") - ) - ) - } - } - - override fun getRemoteTestFromDcc(dcc: JSONObject): RemoteEventNegativeTest? { - return getEventByType(dcc, "t")?.let { jsonObject -> - RemoteEventNegativeTest( - type = "test", - unique = jsonObject.getStringOrNull("ci"), - isSpecimen = false, - negativeTest = RemoteEventNegativeTest.NegativeTest( - sampleDate = OffsetDateTime.parse(jsonObject.getStringOrNull("sc")), - negativeResult = jsonObject.getStringOrNull("tr") == "260415000", - facility = jsonObject.getStringOrNull("tc"), - type = jsonObject.getStringOrNull("tt"), - name = jsonObject.getStringOrNull("nm") - .takeIf { it?.isNotEmpty() ?: false }, - country = jsonObject.getStringOrNull("co") - .takeIf { it?.isNotEmpty() ?: false }, - manufacturer = jsonObject.getStringOrNull("ma") - .takeIf { it?.isNotEmpty() ?: false } - ) - ) - } - } - - override fun getRemoteProtocol3FromNonDcc(eventGroupEntity: EventGroupEntity): RemoteProtocol? { - val payload = moshi.adapter(SignedResponse::class.java) - .fromJson(String(eventGroupEntity.jsonData))?.payload - val decodedPayload = String(Base64.decode(payload, Base64.DEFAULT)) - return moshi.adapter(RemoteProtocol::class.java).fromJson(decodedPayload) - } - - private fun getEventByType(dcc: JSONObject, key: String) = try { - dcc.getJSONArray(key).optJSONObject(0) - } catch (exception: JSONException) { - null - } - - override fun getOriginType(remoteEvent: RemoteEvent): OriginType { - return when (remoteEvent) { - is RemoteEventVaccination -> OriginType.Vaccination - is RemoteEventRecovery -> OriginType.Recovery - is RemoteEventPositiveTest -> OriginType.Recovery - is RemoteEventNegativeTest -> OriginType.Test - else -> error("remote event not supported as origin type") - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteProtocol3Util.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteProtocol3Util.kt deleted file mode 100644 index 71d5e61a2..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/RemoteProtocol3Util.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import java.lang.StringBuilder -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.your_events.RemoteEventInformation - -interface RemoteProtocol3Util { - fun areGGDEvents(providerIdentifier: String): Boolean - fun areRIVMEvents(providerIdentifier: String): Boolean - fun getProviderIdentifier(remoteProtocol: RemoteProtocol): String - fun groupEvents(remoteEvents: List): Map> -} - -class RemoteProtocol3UtilImpl : RemoteProtocol3Util { - - override fun getProviderIdentifier(remoteProtocol: RemoteProtocol): String { - return if (!areGGDEvents(remoteProtocol.providerIdentifier) && !areRIVMEvents(remoteProtocol.providerIdentifier)) { - val providerIdentifierBuilder = StringBuilder() - providerIdentifierBuilder.append(remoteProtocol.providerIdentifier) - providerIdentifierBuilder.append("_") - remoteProtocol.events?.forEach { remoteEvent -> - run { - providerIdentifierBuilder.append(remoteEvent.unique) - } - } - providerIdentifierBuilder.toString() - } else { - remoteProtocol.providerIdentifier - } - } - - /** - * Group all events that have the same: - * - the same date - * AND - * hpkcodes are not null and match - * OR - * manifacturer are not null and match - * It's possible that your vaccination is known at both the GGD or RIVM - * so this merges the two - */ - override fun groupEvents(remoteEvents: List): Map> { - val sameEventsGrouped = mutableMapOf>() - - remoteEvents.sortedBy { it.providerIdentifier }.forEach { - val provider = it.providerIdentifier - val holder = it.holder - it.events?.forEach { remoteEvent -> - if (sameEventsGrouped.contains(remoteEvent)) { - sameEventsGrouped[remoteEvent]?.add(RemoteEventInformation(provider, holder, remoteEvent)) - } else { - sameEventsGrouped[remoteEvent] = mutableListOf(RemoteEventInformation(provider, holder, remoteEvent)) - } - } - } - - // Sort events descending by date - return sameEventsGrouped.entries - .sortedByDescending { it.key.getDate() } - .associate { it.key to it.value } - } - - override fun areGGDEvents(providerIdentifier: String): Boolean { - return providerIdentifier.lowercase() == "ggd" - } - - override fun areRIVMEvents(providerIdentifier: String): Boolean { - return providerIdentifier.lowercase() == "rivm" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/TestInfoScreenUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/TestInfoScreenUtil.kt deleted file mode 100644 index 71ebdca1f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/TestInfoScreenUtil.kt +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.content.res.Resources -import android.text.TextUtils -import java.util.Locale -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventNegativeTest -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventPositiveTest -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtil -import nl.rijksoverheid.ctr.holder.utils.CountryUtil -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase - -interface TestInfoScreenUtil { - - fun getForNegativeTest( - event: RemoteEventNegativeTest, - fullName: String, - testDate: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean = true - ): InfoScreen - - fun getForPositiveTest( - event: RemoteEventPositiveTest, - testDate: String, - fullName: String, - birthDate: String - ): InfoScreen -} - -class TestInfoScreenUtilImpl( - private val resources: Resources, - private val paperProofUtil: PaperProofUtil, - private val countryUtil: CountryUtil, - cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : CreateInfoLineUtil(), TestInfoScreenUtil { - - private val holderConfig = cachedAppConfigUseCase.getCachedAppConfig() - - override fun getForNegativeTest( - event: RemoteEventNegativeTest, - fullName: String, - testDate: String, - birthDate: String, - europeanCredential: ByteArray?, - addExplanation: Boolean - ): InfoScreen { - val testType = holderConfig.euTestTypes.firstOrNull { - it.code == event.negativeTest?.type - }?.name ?: event.negativeTest?.type ?: "" - - val isRat = event.negativeTest?.type == "LP217198-3" - - val testName = if (isRat) { - holderConfig.euTestNames.firstOrNull { - it.code == event.negativeTest?.manufacturer - }?.name ?: "" - } else { - event.negativeTest?.name ?: "" - } - - val testLocation = event.negativeTest?.facility ?: "" - - val testManufacturer = - holderConfig.euTestManufacturers.firstOrNull { - it.code == event.negativeTest?.manufacturer - }?.name ?: event.negativeTest?.manufacturer ?: "" - - val unique = event.unique ?: "" - - val country = getCountry(event.negativeTest?.country) - - val isPaperCertificate = europeanCredential != null - - val title = - if (europeanCredential != null) resources.getString(R.string.your_vaccination_explanation_toolbar_title) else resources.getString( - R.string.your_test_result_explanation_toolbar_title - ) - val header = if (isPaperCertificate) { - resources.getString(R.string.paper_proof_event_explanation_header) - } else { - resources.getString(R.string.your_test_result_explanation_description_header) - } - - val description = (TextUtils.concat( - header, - "

", - createdLine( - resources.getString(R.string.your_test_result_explanation_description_name), - fullName - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_date_of_birth), - birthDate, - isOptional = true - ), - "
", - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_type), - testType, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_name), - testName, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_date), - testDate, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_result), - resources.getString(R.string.your_test_result_explanation_negative_test_result), - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_manufacturer), - testManufacturer, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_location), - testLocation, - isOptional = true - ), - createdLine( - resources.getString(R.string.holder_event_about_test_countrytestedin), - countryUtil.getCountryForInfoScreen(Locale.getDefault().language, country), - isOptional = true - ), - if (europeanCredential != null) { - val issuerAnswer = paperProofUtil.getIssuer(europeanCredential) - createdLine( - resources.getString(R.string.holder_dcc_issuer), - if (issuerAnswer == "Ministry of Health Welfare and Sport") { - resources.getString(R.string.qr_explanation_certificate_issuer) - } else { - issuerAnswer - }, - isOptional = true - ) - } else { - "" - }, - "
", - createdLine( - resources.getString( - if (isPaperCertificate) { - R.string.holder_dcc_test_identifier - } else { - R.string.your_test_result_explanation_description_unique_identifier - } - ), - unique - ), - if (europeanCredential != null && addExplanation) { - paperProofUtil.getInfoScreenFooterText(europeanCredential) - } else { - "" - } - ) as String) - - return InfoScreen( - title = title, - description = description - ) - } - - private fun getCountry(country: String?) = when { - country.isNullOrEmpty() -> "NL" - else -> country - } - - override fun getForPositiveTest( - event: RemoteEventPositiveTest, - testDate: String, - fullName: String, - birthDate: String - ): InfoScreen { - - val testType = holderConfig.euTestTypes.firstOrNull { - it.code == event.positiveTest?.type - }?.name ?: event.positiveTest?.type ?: "" - - val testName = event.positiveTest?.name ?: "" - - val testLocation = event.positiveTest?.facility ?: "" - - val testManufacturer = - holderConfig.euTestManufacturers.firstOrNull { - it.code == event.positiveTest?.manufacturer - }?.name ?: event.positiveTest?.manufacturer ?: "" - - val unique = event.unique ?: "" - - val country = getCountry(event.positiveTest?.country) - - val title = resources.getString(R.string.your_test_result_explanation_toolbar_title) - val description = (TextUtils.concat( - resources.getString(R.string.your_test_result_explanation_description_header), - "

", - createdLine( - resources.getString(R.string.your_test_result_explanation_description_name), - fullName - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_date_of_birth), - birthDate, - isOptional = true - ), - "
", - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_type), - testType, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_name), - testName, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_date), - testDate, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_result), - resources.getString(R.string.your_test_result_explanation_positive_test_result), - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_manufacturer), - testManufacturer, - isOptional = true - ), - createdLine( - resources.getString(R.string.your_test_result_explanation_description_test_location), - testLocation, - isOptional = true - ), - createdLine( - resources.getString(R.string.holder_event_about_test_countrytestedin), - countryUtil.getCountryForInfoScreen(Locale.getDefault().language, country), - isOptional = true - ), - "
", - createdLine( - resources.getString(R.string.your_test_result_explanation_description_unique_identifier), - unique - ) - ) as String) - - return InfoScreen( - title = title, - description = description - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/VaccinationInfoScreenUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/VaccinationInfoScreenUtil.kt deleted file mode 100644 index e01b7c37d..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/VaccinationInfoScreenUtil.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (c) 2023 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.content.res.Resources -import android.text.TextUtils -import java.util.Locale -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination -import nl.rijksoverheid.ctr.holder.paper_proof.utils.PaperProofUtil -import nl.rijksoverheid.ctr.holder.qrcodes.utils.LastVaccinationDoseUtil -import nl.rijksoverheid.ctr.holder.utils.CountryUtil -import nl.rijksoverheid.ctr.persistence.HolderCachedAppConfigUseCase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface VaccinationInfoScreenUtil { - - fun getForVaccination( - event: RemoteEventVaccination, - fullName: String, - birthDate: String, - providerIdentifier: String, - europeanCredential: ByteArray?, - addExplanation: Boolean = true - ): InfoScreen -} - -class VaccinationInfoScreenUtilImpl( - private val lastVaccinationDoseUtil: LastVaccinationDoseUtil, - private val resources: Resources, - private val countryUtil: CountryUtil, - private val paperProofUtil: PaperProofUtil, - cachedAppConfigUseCase: HolderCachedAppConfigUseCase -) : CreateInfoLineUtil(), VaccinationInfoScreenUtil { - - private val holderConfig = cachedAppConfigUseCase.getCachedAppConfig() - - override fun getForVaccination( - event: RemoteEventVaccination, - fullName: String, - birthDate: String, - providerIdentifier: String, - europeanCredential: ByteArray?, - addExplanation: Boolean - ): InfoScreen { - val title = - if (europeanCredential != null) resources.getString(R.string.your_vaccination_explanation_toolbar_title) else resources.getString( - R.string.your_test_result_explanation_toolbar_title - ) - - val name = resources.getString(R.string.your_vaccination_explanation_name) - - val birthDateQuestion = - resources.getString(R.string.your_vaccination_explanation_birthday) - - val disease = resources.getString(R.string.your_vaccination_explanation_covid_19) - val diseaseAnswer = - resources.getString(R.string.your_vaccination_explanation_covid_19_answer) - - val hpkCode = holderConfig.hpkCodes.firstOrNull { it.code == event.vaccination?.hpkCode } - - val vaccine = resources.getString(R.string.your_vaccination_explanation_vaccine) - val vaccineAnswer = getVaccineAnswer(hpkCode, event) - - val vaccineDisplayName = - resources.getString(R.string.holder_event_aboutVaccination_productName) - val vaccineDisplayNameAnswer = hpkCode?.displayName ?: "" - - val vaccineType = resources.getString(R.string.your_vaccination_explanation_vaccine_type) - val vaccineTypeAnswer = getVaccineTypeAnswer(hpkCode, event) - - val producer = resources.getString(R.string.your_vaccination_explanation_producer) - val producerAnswer = getProducerAnswer(hpkCode, event) - - val doses = resources.getString(R.string.your_vaccination_explanation_doses) - val dosesAnswer = getDosesAnswer(event, event.vaccination) - - val lastDose = resources.getString(R.string.your_vaccination_explanation_last_dose) - val lastDoseAnswer = lastVaccinationDoseUtil.getIsLastDoseAnswer(event) - - val vaccinationDate = - resources.getString(R.string.your_vaccination_explanation_vaccination_date) - val vaccinationDateAnswer = event.vaccination?.date?.formatDayMonthYear() ?: "" - - val fullCountryName = if (event.vaccination?.country != null) { - countryUtil.getCountryForInfoScreen( - Locale.getDefault().language, - event.vaccination.country - ) - } else { - "" - } - - val vaccinationCountry = - resources.getString(R.string.your_vaccination_explanation_vaccination_country) - - val uniqueCode = resources.getString(R.string.your_vaccination_explanation_unique_code) - val uniqueCodeAnswer = event.unique ?: "" - - val header = if (europeanCredential != null || providerIdentifier.contains("dcc")) { - resources.getString(R.string.paper_proof_event_explanation_header) - } else { - resources.getString(R.string.your_vaccination_explanation_header, providerIdentifier) - } - - return InfoScreen( - title = title, - description = (TextUtils.concat( - header, - "

", - createdLine(name, fullName), - createdLine(birthDateQuestion, birthDate, isOptional = true), - "
", - createdLine(disease, diseaseAnswer), - createdLine(vaccine, vaccineAnswer), - createdLine(vaccineDisplayName, vaccineDisplayNameAnswer, isOptional = true), - createdLine(vaccineType, vaccineTypeAnswer, isOptional = true), - createdLine(producer, producerAnswer, isOptional = true), - createdLine(doses, dosesAnswer, isOptional = true), - createdLine(lastDose, lastDoseAnswer, isOptional = true), - createdLine(vaccinationDate, vaccinationDateAnswer, isOptional = true), - createdLine(vaccinationCountry, fullCountryName, isOptional = true), - if (europeanCredential != null) { - val issuerAnswer = paperProofUtil.getIssuer(europeanCredential) - createdLine( - resources.getString(R.string.holder_dcc_issuer), - if (issuerAnswer == "Ministry of Health Welfare and Sport") { - resources.getString(R.string.qr_explanation_certificate_issuer) - } else { - issuerAnswer - }, - isOptional = true - ) - } else { - "" - }, - "
", - createdLine(uniqueCode, uniqueCodeAnswer), - if (europeanCredential != null && addExplanation) { - paperProofUtil.getInfoScreenFooterText(europeanCredential) - } else { - "" - } - ) as String) - ) - } - - private fun getDosesAnswer( - event: RemoteEventVaccination, - vaccination: RemoteEventVaccination.Vaccination? - ) = if (event.vaccination?.doseNumber != null && vaccination?.totalDoses != null) { - resources.getString( - R.string.your_vaccination_explanation_doses_answer, - vaccination.doseNumber, - vaccination.totalDoses - ) - } else "" - - private fun getProducerAnswer( - hpkCode: AppConfig.HpkCode?, - event: RemoteEventVaccination - ) = - (holderConfig.euManufacturers.firstOrNull { it.code == event.vaccination?.manufacturer }?.name - ?: holderConfig.euManufacturers.firstOrNull { it.code == hpkCode?.ma }?.name - ?: event.vaccination?.manufacturer - ?: "") - - private fun getVaccineTypeAnswer( - hpkCode: AppConfig.HpkCode?, - event: RemoteEventVaccination - ) = (holderConfig.euVaccinations.firstOrNull { it.code == event.vaccination?.type }?.name - ?: holderConfig.euVaccinations.firstOrNull { it.code == hpkCode?.vp }?.name - ?: event.vaccination?.type - ?: "") - - private fun getVaccineAnswer( - hpkCode: AppConfig.HpkCode?, - event: RemoteEventVaccination - ): String { - val hpkCodeName = hpkCode?.name ?: "" - val brand = - holderConfig.euBrands.firstOrNull { it.code == event.vaccination?.brand }?.name - ?: holderConfig.euBrands.firstOrNull { it.code == hpkCode?.mp }?.name - ?: event.vaccination?.brand - ?: "" - return when { - hpkCodeName.isNotEmpty() -> hpkCodeName - brand.isNotEmpty() -> brand - else -> "" - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/YourEventsEndStateUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/YourEventsEndStateUtil.kt deleted file mode 100644 index 8d1fda39a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/YourEventsEndStateUtil.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import android.content.Context -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.utils.StringUtil -import nl.rijksoverheid.ctr.holder.your_events.models.YourEventsEndState -import nl.rijksoverheid.ctr.holder.your_events.models.YourEventsEndStateWithCustomTitle -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.models.WeCouldntCreateCertificateException - -interface YourEventsEndStateUtil { - fun getEndState(context: Context, newEvents: List = listOf(), hints: List, blockedEvents: List = listOf()): YourEventsEndState - fun getErrorStateSubstring(context: Context, flow: Flow): String -} - -class YourEventsEndStateUtilImpl( - private val stringUtil: StringUtil -) : YourEventsEndStateUtil { - override fun getEndState(context: Context, newEvents: List, hints: List, blockedEvents: List): YourEventsEndState { - val newEventsContainBlockedEvent = newEvents.any { blockedEvents.contains(it) } - return if (newEventsContainBlockedEvent) { - YourEventsEndState.BlockedEvent - } else { - val endStateFromHints = hintsToEndState(hints) - if (endStateFromHints != YourEventsEndState.None) { - endStateFromHints - } else { - val localisedHints = - hints.map { stringUtil.getStringFromResourceName(it) }.filterNot { it.isEmpty() } - if (localisedHints.isEmpty()) { - YourEventsEndState.None - } else { - YourEventsEndState.Hints(localisedHints) - } - } - } - } - - private fun hintsToEndState(hints: List): YourEventsEndState { - val anyRecoveryCreated = - hints.contains("domestic_recovery_created") || hints.contains("international_recovery_created") - val allRecoveriesCreated = - hints.contains("domestic_recovery_created") && hints.contains("international_recovery_created") - val anyRecoveryRejected = - hints.contains("domestic_recovery_rejected") || hints.contains("international_recovery_rejected") - val anyVaccinationCreated = - hints.contains("domestic_vaccination_created") || hints.contains("international_vaccination_created") - val allVaccinationsCreated = - hints.contains("domestic_vaccination_created") && hints.contains("international_vaccination_created") - val anyVaccinationRejected = - hints.contains("domestic_vaccination_rejected") || hints.contains("international_vaccination_rejected") - val anyNegativeTestCreated = - hints.contains("domestic_negativetest_created") || hints.contains("international_negativetest_created") - val anyNegativeTestRejected = - hints.contains("domestic_negativetest_rejected") || hints.contains("international_negativetest_rejected") - - if (allRecoveriesCreated && hints.contains("vaccination_dose_correction_applied")) { - return if (allVaccinationsCreated) { - YourEventsEndStateWithCustomTitle.VaccinationsAndRecovery - } else { - YourEventsEndStateWithCustomTitle.RecoveryAndDosisCorrection - } - } - - if (!anyVaccinationCreated && !anyVaccinationRejected) { - - if (anyNegativeTestCreated) { - return YourEventsEndState.None - } else if (anyNegativeTestRejected) { - return YourEventsEndState.WeCouldntMakeACertificateError( - WeCouldntCreateCertificateException("0512") - ) - } - - return if (anyRecoveryCreated) { - YourEventsEndState.None - } else if (anyRecoveryRejected && hints.contains("vaccination_dose_correction_applied")) { - YourEventsEndStateWithCustomTitle.NoRecoveryButDosisCorrection - } else if (anyRecoveryRejected && - (hints.contains("vaccination_dose_correction_not_applied") || - hints.contains("international_recovery_too_old")) - ) { - YourEventsEndStateWithCustomTitle.RecoveryTooOld - } else if (anyRecoveryRejected) { - YourEventsEndState.WeCouldntMakeACertificateError( - WeCouldntCreateCertificateException("0511") - ) - } else { - YourEventsEndState.None - } - } - - if (!anyVaccinationRejected && !anyRecoveryCreated) { - return YourEventsEndState.None - } - - if (anyRecoveryCreated) { - return if (anyVaccinationCreated) { - if (hints.contains("domestic_vaccination_created")) { - YourEventsEndStateWithCustomTitle.VaccinationsAndRecovery - } else { - YourEventsEndStateWithCustomTitle.InternationalVaccinationAndRecovery - } - } else { - YourEventsEndStateWithCustomTitle.RecoveryOnly - } - } - - if (hints.contains("domestic_vaccination_rejected") && hints.contains("international_vaccination_rejected")) { - return YourEventsEndState.WeCouldntMakeACertificateError( - WeCouldntCreateCertificateException("059") - ) - } - - if (hints.contains("domestic_vaccination_rejected")) { - return YourEventsEndStateWithCustomTitle.InternationalQROnly - } - - if (hints.containsAll( - listOf( - "domestic_recovery_rejected", - "international_recovery_rejected", - "domestic_vaccination_rejected", - "international_vaccination_rejected", - "vaccination_dose_correction_not_applied" - ) - ) - ) { - return YourEventsEndState.WeCouldntMakeACertificateError( - WeCouldntCreateCertificateException("0510") - ) - } - - return YourEventsEndState.None - } - - override fun getErrorStateSubstring(context: Context, flow: Flow): String { - return context.getString( - when (flow) { - HolderFlow.CommercialTest -> R.string.general_negativeTest - HolderFlow.DigidTest -> R.string.general_negativeTest - HolderFlow.Recovery -> R.string.general_positiveTest - HolderFlow.Vaccination -> R.string.general_vaccination - else -> R.string.general_retrievedDetails - } - ).lowercase() - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/YourEventsFragmentUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/YourEventsFragmentUtil.kt deleted file mode 100644 index fc35bedc0..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/utils/YourEventsFragmentUtil.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.utils - -import androidx.core.text.HtmlCompat -import java.time.LocalDate -import java.time.format.DateTimeFormatter -import java.time.format.DateTimeParseException -import nl.rijksoverheid.ctr.appconfig.api.model.AppConfig -import nl.rijksoverheid.ctr.design.ext.formatDayMonthYear -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteProtocol -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.your_events.YourEventsFragmentType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.shared.models.Flow - -interface YourEventsFragmentUtil { - fun getHeaderCopy(type: YourEventsFragmentType, flow: Flow): Int - fun getNoOriginTypeCopy(type: YourEventsFragmentType, flow: Flow): Int - fun getProviderName(providers: List, providerIdentifier: String): String - fun getCancelDialogDescription(type: YourEventsFragmentType): Int - fun getFullName(holder: RemoteProtocol.Holder?): String - fun getBirthDate(holder: RemoteProtocol.Holder?): String - fun getVaccinationDate(date: LocalDate?): String -} - -class YourEventsFragmentUtilImpl( - private val remoteEventUtil: RemoteEventUtil -) : YourEventsFragmentUtil { - override fun getHeaderCopy(type: YourEventsFragmentType, flow: Flow): Int { - return when { - flow == HolderFlow.Migration -> { - R.string.holder_migrationFlow_scannedDetailsOverview_message - } - type is YourEventsFragmentType.DCC -> { - R.string.holder_listRemoteEvents_paperflow_message - } - isRecovery(type) -> { - R.string.holder_listRemoteEvents_recovery_message - } - isTest(type) -> { - R.string.holder_listRemoteEvents_negativeTest_message - } - else -> { - R.string.holder_listRemoteEvents_vaccination_message - } - } - } - - private fun isRecovery(yourEventsFragmentType: YourEventsFragmentType): Boolean { - val type = - yourEventsFragmentType as? YourEventsFragmentType.RemoteProtocol3Type ?: return false - val remoteEvent = type.remoteEvents.keys.firstOrNull()?.events?.first() ?: return false - return remoteEventUtil.getOriginType(remoteEvent) == OriginType.Recovery - } - - private fun isTest(yourEventsFragmentType: YourEventsFragmentType): Boolean { - val type = - yourEventsFragmentType as? YourEventsFragmentType.RemoteProtocol3Type ?: return false - val remoteEvent = type.remoteEvents.keys.firstOrNull()?.events?.first() ?: return false - return remoteEventUtil.getOriginType(remoteEvent) == OriginType.Test - } - - override fun getNoOriginTypeCopy(type: YourEventsFragmentType, flow: Flow): Int { - return when (type) { - is YourEventsFragmentType.DCC -> { - R.string.rule_engine_no_test_origin_description_scanned_qr_code - } - is YourEventsFragmentType.RemoteProtocol3Type -> { - return when (remoteEventUtil.getOriginType(type.remoteEvents.keys.first().events!!.first())) { - is OriginType.Test -> { - R.string.rule_engine_no_test_origin_description_negative_test - } - is OriginType.Recovery -> { - R.string.rule_engine_no_test_origin_description_positive_test - } - is OriginType.Vaccination -> { - if (flow is HolderFlow.VaccinationAndPositiveTest) { - R.string.general_retrievedDetails - } else { - R.string.rule_engine_no_test_origin_description_vaccination - } - } - } - } - } - } - - override fun getProviderName( - providers: List, - providerIdentifier: String - ): String { - return providers.firstOrNull { it.code == providerIdentifier } - ?.name - ?: providerIdentifier - } - - override fun getCancelDialogDescription(type: YourEventsFragmentType): Int { - return when (type) { - is YourEventsFragmentType.DCC -> R.string.holder_dcc_alert_message - is YourEventsFragmentType.RemoteProtocol3Type -> { - when (remoteEventUtil.getOriginType(type.remoteEvents.keys.first().events!!.first())) { - is OriginType.Test -> R.string.holder_test_alert_message - is OriginType.Recovery -> R.string.holder_recovery_alert_message - is OriginType.Vaccination -> R.string.holder_vaccination_alert_message - } - } - } - } - - override fun getFullName(holder: RemoteProtocol.Holder?): String { - return holder?.let { - return HtmlCompat.fromHtml( - if (it.infix.isNullOrEmpty()) { - "${it.lastName}, ${it.firstName}" - } else { - "${it.infix} ${it.lastName}, ${it.firstName}" - }, HtmlCompat.FROM_HTML_MODE_LEGACY - ).toString() - } ?: "" - } - - override fun getBirthDate(holder: RemoteProtocol.Holder?): String { - return holder?.birthDate?.let { - val birthDate = HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() - try { - LocalDate.parse(birthDate, DateTimeFormatter.ISO_DATE).formatDayMonthYear() - } catch (e: DateTimeParseException) { - // Check if date has removed content, if so return string directly - if (birthDate.contains("XX")) { - birthDate - } else "" - } catch (e: Exception) { - "" - } - } ?: "" - } - - override fun getVaccinationDate(date: LocalDate?): String { - return date?.let { vaccinationDate -> - try { - vaccinationDate.formatDayMonthYear() - } catch (e: Exception) { - "" - } - } ?: "" - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/widgets/YourEventWidget.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/widgets/YourEventWidget.kt deleted file mode 100644 index 0cce39d9b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/widgets/YourEventWidget.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -package nl.rijksoverheid.ctr.holder.your_events.widgets - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.content.ContextCompat -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.databinding.ItemYourEventBinding -import nl.rijksoverheid.ctr.shared.utils.Accessibility.addAccessibilityAction -import nl.rijksoverheid.ctr.shared.utils.Accessibility.setAccessibilityLabel -import nl.rijksoverheid.ctr.shared.utils.Accessibility.setAsAccessibilityButton - -class YourEventWidget @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0, - defStyleRes: Int = 0 -) : ConstraintLayout(context, attrs, defStyle, defStyleRes) { - - val binding = - ItemYourEventBinding.inflate(LayoutInflater.from(context), this, true) - - fun setContent(title: String, subtitle: String, infoClickListener: () -> Unit) { - binding.rowTitle.text = title - binding.rowSubtitle.setHtmlText(subtitle, htmlTextColor = ContextCompat.getColor(context, R.color.html_secondary_text)) - binding.detailsButton.setOnClickListener { infoClickListener.invoke() } - binding.detailsButton.contentDescription = "${binding.detailsButton.text}, $title" - - with(binding.testResultsGroup) { - setOnClickListener { infoClickListener.invoke() } - - setAccessibilityLabel(String.format("%s. %s.", - binding.rowTitle.text, - binding.rowSubtitle.spannable - )) - setAsAccessibilityButton(true) - addAccessibilityAction(AccessibilityNodeInfoCompat.ACTION_CLICK, binding.detailsButton.text) - } - } - - fun setButtonsEnabled(enabled: Boolean) { - binding.detailsButton.isEnabled = enabled - binding.testResultsGroup.isClickable = enabled - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/widgets/YourEventWidgetUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/widgets/YourEventWidgetUtil.kt deleted file mode 100644 index bcf719c8c..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/your_events/widgets/YourEventWidgetUtil.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.holder.your_events.widgets - -import android.content.Context -import nl.rijksoverheid.ctr.holder.R -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination - -interface YourEventWidgetUtil { - fun getVaccinationEventTitle( - context: Context, - isDccEvent: Boolean, - currentEvent: RemoteEventVaccination - ): String - - fun getVaccinationEventSubtitle( - context: Context, - isDccEvent: Boolean, - providerIdentifiers: String, - vaccinationDate: String, - fullName: String, - birthDate: String - ): String -} - -class YourEventWidgetUtilImpl : YourEventWidgetUtil { - override fun getVaccinationEventTitle( - context: Context, - isDccEvent: Boolean, - currentEvent: RemoteEventVaccination - ): String { - return if (isDccEvent) { - context.getString(R.string.retrieved_vaccination_dcc_title, currentEvent.vaccination?.doseNumber ?: "", currentEvent.vaccination?.totalDoses ?: "") - } else { - context.getString( - R.string.retrieved_vaccination_title - ) - } - } - - override fun getVaccinationEventSubtitle( - context: Context, - isDccEvent: Boolean, - providerIdentifiers: String, - vaccinationDate: String, - fullName: String, - birthDate: String - ): String { - return if (isDccEvent) { - context.getString( - R.string.your_vaccination_dcc_row_subtitle, - vaccinationDate, - fullName, - birthDate) - } else { - context.getString( - R.string.your_vaccination_row_subtitle, - vaccinationDate, - fullName, - birthDate, - providerIdentifiers - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/HolderCachedConfigUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/HolderCachedConfigUseCase.kt deleted file mode 100644 index d60f5ebfe..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/HolderCachedConfigUseCase.kt +++ /dev/null @@ -1,31 +0,0 @@ -package nl.rijksoverheid.ctr.persistence - -import nl.rijksoverheid.ctr.appconfig.api.model.HolderConfig -import nl.rijksoverheid.ctr.appconfig.usecases.CachedAppConfigUseCase - -interface HolderCachedAppConfigUseCase { - fun getCachedAppConfig(): HolderConfig - fun getCachedAppConfigOrNull(): HolderConfig? -} - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderCachedAppConfigUseCaseImpl constructor( - private val cachedAppConfigUseCase: CachedAppConfigUseCase -) : HolderCachedAppConfigUseCase { - - private val defaultConfig = HolderConfig.default() - - override fun getCachedAppConfig(): HolderConfig { - return getCachedAppConfigOrNull() ?: defaultConfig - } - - override fun getCachedAppConfigOrNull(): HolderConfig? { - return cachedAppConfigUseCase.getCachedAppConfigOrNull() as? HolderConfig - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabase.kt index b4e270740..be0ad9a62 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabase.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabase.kt @@ -1,40 +1,8 @@ package nl.rijksoverheid.ctr.persistence.database -import android.content.ContentValues import android.content.Context -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.RenameTable -import androidx.room.Room import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import androidx.room.migration.AutoMigrationSpec -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase import java.io.File -import net.sqlcipher.database.SQLiteDatabase -import net.sqlcipher.database.SQLiteException -import net.sqlcipher.database.SupportFactory -import nl.rijksoverheid.ctr.persistence.PersistenceManager -import nl.rijksoverheid.ctr.persistence.database.converters.HolderDatabaseConverter -import nl.rijksoverheid.ctr.persistence.database.dao.CredentialDao -import nl.rijksoverheid.ctr.persistence.database.dao.EventGroupDao -import nl.rijksoverheid.ctr.persistence.database.dao.GreenCardDao -import nl.rijksoverheid.ctr.persistence.database.dao.OriginDao -import nl.rijksoverheid.ctr.persistence.database.dao.OriginHintDao -import nl.rijksoverheid.ctr.persistence.database.dao.RemovedEventDao -import nl.rijksoverheid.ctr.persistence.database.dao.SecretKeyDao -import nl.rijksoverheid.ctr.persistence.database.dao.WalletDao -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginHintEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.persistence.database.entities.SecretKeyEntity -import nl.rijksoverheid.ctr.persistence.database.entities.WalletEntity -import nl.rijksoverheid.ctr.shared.models.Environment -import nl.rijksoverheid.ctr.shared.utils.AndroidUtil /* * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. @@ -44,193 +12,18 @@ import nl.rijksoverheid.ctr.shared.utils.AndroidUtil * */ -val MIGRATION_1_2 = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE origin ADD COLUMN doseNumber INTEGER") - } -} - -val MIGRATION_2_3 = object : Migration(2, 3) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE credential ADD COLUMN category VARCHAR") - } -} - -val MIGRATION_3_4 = object : Migration(3, 4) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("ALTER TABLE event_group ADD COLUMN scope TEXT") - database.execSQL("DROP INDEX index_event_group_provider_identifier_type") - database.execSQL("CREATE UNIQUE INDEX index_event_group_provider_identifier_type_scope ON event_group(provider_identifier, type, scope)") - } -} - -val MIGRATION_4_5 = object : Migration(4, 5) { - - /** - * Fix for possible duplicate events inserted into to the database. - * Scope parameter was added to event group with unique constraint. - * However scope was set to nullable which breaks the unique constraint. - * This migration removes duplicate entries and updates null scopes to empty strings. - * Scope is set to non nullable in table. - */ - override fun migrate(database: SupportSQLiteDatabase) { - // create new table for event group with scope not null - database.execSQL("CREATE TABLE IF NOT EXISTS event_group_temp (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, wallet_id INTEGER NOT NULL, provider_identifier TEXT NOT NULL, type TEXT NOT NULL, scope TEXT NOT NULL, maxIssuedAt INTEGER NOT NULL, jsonData BLOB NOT NULL, FOREIGN KEY(wallet_id) REFERENCES wallet(id) ON UPDATE NO ACTION ON DELETE CASCADE )") - - // remove duplicate events with null scope. Update null scope to empty string - database.execSQL( - "DELETE FROM event_group WHERE EXISTS " + - "(SELECT 1 FROM event_group e2 " + - "WHERE event_group.provider_identifier = e2.provider_identifier " + - "AND event_group.type = e2.type " + - "AND scope IS NULL " + - "AND event_group.rowid < e2.rowid)" - ) - database.execSQL("UPDATE event_group SET scope = '' WHERE scope IS NULL") - - // copy data from old event group table to new one - database.execSQL("INSERT INTO event_group_temp (id, wallet_id, provider_identifier, type, scope, maxIssuedAt, jsonData) SELECT id, wallet_id, provider_identifier, type, scope, maxIssuedAt, jsonData FROM event_group") - - // delete old event group table and index - database.execSQL("DROP TABLE IF EXISTS event_group") - database.execSQL("DROP INDEX IF EXISTS index_event_group_wallet_id") - database.execSQL("DROP INDEX IF EXISTS index_event_group_provider_identifier_type_scope") - - // rename new table and set index - database.execSQL("ALTER TABLE event_group_temp RENAME TO event_group") - database.execSQL("CREATE INDEX index_event_group_wallet_id ON event_group(wallet_id)") - database.execSQL("CREATE UNIQUE INDEX index_event_group_provider_identifier_type_scope ON event_group(provider_identifier, type, scope)") - } -} - -/** - * Remove [EventGroupEntity.maxIssuedAt] and replace it with [EventGroupEntity.expiryDate] - */ -val MIGRATION_5_6 = object : Migration(5, 6) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE TABLE IF NOT EXISTS event_group_temp (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, wallet_id INTEGER NOT NULL, provider_identifier TEXT NOT NULL, type TEXT NOT NULL, scope TEXT NOT NULL, expiryDate INTEGER, jsonData BLOB NOT NULL, FOREIGN KEY(wallet_id) REFERENCES wallet(id) ON UPDATE NO ACTION ON DELETE CASCADE )") - database.execSQL("INSERT INTO event_group_temp (id, wallet_id, provider_identifier, type, scope, expiryDate, jsonData) SELECT id, wallet_id, provider_identifier, type, scope, null, jsonData FROM event_group") - database.execSQL("DROP TABLE IF EXISTS event_group") - database.execSQL("DROP INDEX IF EXISTS index_event_group_wallet_id") - database.execSQL("DROP INDEX IF EXISTS index_event_group_provider_identifier_type_scope") - database.execSQL("ALTER TABLE event_group_temp RENAME TO event_group") - database.execSQL("CREATE INDEX index_event_group_wallet_id ON event_group(wallet_id)") - database.execSQL("CREATE UNIQUE INDEX index_event_group_provider_identifier_type_scope ON event_group(provider_identifier, type, scope)") - } -} - -@Suppress("FunctionName") -fun MIGRATION_6_7(persistenceManager: PersistenceManager) = object : Migration(6, 7) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE TABLE IF NOT EXISTS secret_key (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, green_card_id INTEGER NOT NULL, secret TEXT NOT NULL, FOREIGN KEY(green_card_id) REFERENCES green_card(id) ON UPDATE NO ACTION ON DELETE CASCADE )") - val domesticGreenCardCursor = - database.query("SELECT * FROM green_card WHERE type = 'domestic'") - - // If we have a domestic green card migrate old secret key - if (domesticGreenCardCursor.count == 1 && persistenceManager.getDatabasePassPhrase() != null) { - domesticGreenCardCursor.moveToFirst() - val greenCardIdIndex = domesticGreenCardCursor.getColumnIndex("id") - val domesticGreenCardId = domesticGreenCardCursor.getInt(greenCardIdIndex) - val insertValues = ContentValues() - insertValues.put("green_card_id", domesticGreenCardId) - insertValues.put( - "secret", - persistenceManager.getDatabasePassPhrase() - ) // The old database pass phrase is the new secret key - database.insert("secret_key", 0, insertValues) - } - } -} - -val MIGRATION_7_8 = object : Migration(7, 8) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE TABLE IF NOT EXISTS blocked_event (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, wallet_id INTEGER NOT NULL, type TEXT NOT NULL, event_time INTEGER, FOREIGN KEY(wallet_id) REFERENCES wallet(id) ON UPDATE NO ACTION ON DELETE CASCADE )") - database.execSQL("CREATE INDEX index_blocked_event_wallet_id ON blocked_event(wallet_id)") - database.execSQL("CREATE TABLE IF NOT EXISTS origin_hint (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, origin_id INTEGER NOT NULL, hint TEXT NOT NULL, FOREIGN KEY(origin_id) REFERENCES origin(id) ON UPDATE NO ACTION ON DELETE CASCADE )") - database.execSQL("CREATE INDEX index_origin_hint_hint ON origin_hint(hint)") - } -} - -@RenameTable(fromTableName = "blocked_event", toTableName = "removed_event") -class RemovedEventMigration : AutoMigrationSpec - -@Database( - entities = [WalletEntity::class, EventGroupEntity::class, GreenCardEntity::class, CredentialEntity::class, OriginEntity::class, SecretKeyEntity::class, RemovedEventEntity::class, OriginHintEntity::class], - autoMigrations = [AutoMigration( - from = 8, - to = 9, - spec = RemovedEventMigration::class - ), AutoMigration(from = 9, to = 10)], - version = 10 -) -@TypeConverters(HolderDatabaseConverter::class) abstract class HolderDatabase : RoomDatabase() { - abstract fun walletDao(): WalletDao - abstract fun greenCardDao(): GreenCardDao - abstract fun credentialDao(): CredentialDao - abstract fun eventGroupDao(): EventGroupDao - abstract fun originDao(): OriginDao - abstract fun secretKeyDao(): SecretKeyDao - abstract fun removedEventDao(): RemovedEventDao - abstract fun originHintDao(): OriginHintDao companion object { - fun createInstance( - context: Context, - persistenceManager: PersistenceManager, - androidUtil: AndroidUtil, - environment: Environment - ): HolderDatabase { - if (persistenceManager.getDatabasePassPhrase() == null) { - persistenceManager.saveDatabasePassPhrase(androidUtil.generateRandomKey()) - } - - // From db migration 6 to 7 there was a database encryption key migration issue. - // This code checks if we can open the database, and if not delete the old database. - if (environment is Environment.Prod && persistenceManager.getCheckCanOpenDatabase()) { - try { - val file = File(context.filesDir.parentFile, "databases/holder-database") - try { - SQLiteDatabase.loadLibs(context) - SQLiteDatabase.openDatabase( - file.absolutePath, - persistenceManager.getDatabasePassPhrase(), - null, - SQLiteDatabase.OPEN_READONLY - ) - } catch (e: SQLiteException) { - file.delete() - } finally { - persistenceManager.setCheckCanOpenDatabase(false) - } - } catch (e: Exception) { - // Make sure this hack never crashes - } finally { - persistenceManager.setCheckCanOpenDatabase(false) - } + fun deleteDatabase( + context: Context + ) { + try { + val file = File(context.filesDir.parentFile, "databases/holder-database") + file.delete() + } catch (e: Exception) { + // no op } - - return Room - .databaseBuilder(context, HolderDatabase::class.java, "holder-database") - .addMigrations( - MIGRATION_1_2, - MIGRATION_2_3, - MIGRATION_3_4, - MIGRATION_4_5, - MIGRATION_5_6, - MIGRATION_6_7(persistenceManager), - MIGRATION_7_8 - ) - .apply { - if (environment !is Environment.InstrumentationTests) { - val supportFactory = SupportFactory( - SQLiteDatabase.getBytes( - persistenceManager.getDatabasePassPhrase()?.toCharArray() - ) - ) - openHelperFactory(supportFactory) - } - }.build() } } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabaseSyncer.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabaseSyncer.kt deleted file mode 100644 index e77dbf32e..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/HolderDatabaseSyncer.kt +++ /dev/null @@ -1,206 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database - -import java.time.OffsetDateTime -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import nl.rijksoverheid.ctr.holder.dashboard.util.GreenCardUtil -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.usecases.PersistBlockedEventsUseCase -import nl.rijksoverheid.ctr.holder.models.HolderFlow -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.holder.workers.WorkerManagerUtil -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason -import nl.rijksoverheid.ctr.persistence.database.usecases.DraftEventUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.GetRemoteGreenCardsUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoteGreenCardsResult -import nl.rijksoverheid.ctr.persistence.database.usecases.RemoveExpiredEventsUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.SyncRemoteGreenCardsResult -import nl.rijksoverheid.ctr.persistence.database.usecases.SyncRemoteGreenCardsUseCase -import nl.rijksoverheid.ctr.persistence.database.usecases.UpdateEventExpirationUseCase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface HolderDatabaseSyncer { - - /** - * Synchronized the database. Does cleanup in the database based on expiration dates and can resync with remote - * @param flow the [HolderFlow] we are in - * @param syncWithRemote If true and the data call to resync succeeds, clear all green cards in the database and re-add them - * @param previousSyncResult The previous result outputted by this [sync] if known - */ - suspend fun sync( - flow: Flow = HolderFlow.Startup, - syncWithRemote: Boolean = true, - previousSyncResult: DatabaseSyncerResult? = null, - newEvents: List = listOf() - ): DatabaseSyncerResult -} - -class HolderDatabaseSyncerImpl( - private val mobileCoreWrapper: MobileCoreWrapper, - private val holderDatabase: HolderDatabase, - private val greenCardUtil: GreenCardUtil, - private val workerManagerUtil: WorkerManagerUtil, - private val getRemoteGreenCardsUseCase: GetRemoteGreenCardsUseCase, - private val syncRemoteGreenCardsUseCase: SyncRemoteGreenCardsUseCase, - private val removeExpiredEventsUseCase: RemoveExpiredEventsUseCase, - private val updateEventExpirationUseCase: UpdateEventExpirationUseCase, - private val draftEventUseCase: DraftEventUseCase, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val persistBlockedEventsUseCase: PersistBlockedEventsUseCase -) : HolderDatabaseSyncer { - - private val mutex = Mutex() - - override suspend fun sync( - flow: Flow, - syncWithRemote: Boolean, - previousSyncResult: DatabaseSyncerResult?, - newEvents: List - ): DatabaseSyncerResult { - return withContext(Dispatchers.IO) { - mutex.withLock { - val events = holderDatabase.eventGroupDao().getAll() - - if (!featureFlagUseCase.isInArchiveMode() && syncWithRemote) { - if (events.isEmpty()) { - // Remote does not handle empty events, so we decide that empty events == no green cards - holderDatabase.greenCardDao().deleteAll() - return@withContext DatabaseSyncerResult.Success(listOf()) - } - - // Generate a new secret key - val secretKey = mobileCoreWrapper.generateHolderSk() - - // Sync with remote - val remoteGreenCardsResult = getRemoteGreenCardsUseCase.get( - events = events, - secretKey = secretKey, - flow = flow - ) - - when (remoteGreenCardsResult) { - is RemoteGreenCardsResult.Success -> { - // Update event expire dates - updateEventExpirationUseCase.update( - blobExpireDates = remoteGreenCardsResult.remoteGreenCards.blobExpireDates ?: listOf() - ) - - // Persist blocked events for communication to the user on the dashboard - persistBlockedEventsUseCase.persist( - newEvents = newEvents, - removedEvents = remoteGreenCardsResult.blockedEvents, - reason = RemovedEventReason.Blocked - ) - - val remoteGreenCards = remoteGreenCardsResult.remoteGreenCards - - // Insert green cards in database - val result = syncRemoteGreenCardsUseCase.execute( - remoteGreenCards = remoteGreenCards, - secretKey = secretKey - ) - - // Clean up expired events in the database - removeExpiredEventsUseCase.execute( - events = holderDatabase.eventGroupDao().getAll() - ) - - when (result) { - is SyncRemoteGreenCardsResult.Success -> { - draftEventUseCase.finalise() - workerManagerUtil.scheduleRefreshCredentialsJob() - return@withContext DatabaseSyncerResult.Success( - hints = remoteGreenCards.hints ?: listOf(), - blockedEvents = remoteGreenCardsResult.blockedEvents - ) - } - is SyncRemoteGreenCardsResult.Failed -> { - draftEventUseCase.remove() - return@withContext DatabaseSyncerResult.Failed.Error(result.errorResult) - } - } - } - is RemoteGreenCardsResult.FuzzyMatchingError -> { - DatabaseSyncerResult.FuzzyMatchingError( - matchingBlobIds = remoteGreenCardsResult.matchingBlobIds - ) - } - is RemoteGreenCardsResult.Error -> { - draftEventUseCase.remove() - val greenCards = holderDatabase.greenCardDao().getAll() - - when (remoteGreenCardsResult.errorResult) { - is NetworkRequestResult.Failed.ClientNetworkError, is NetworkRequestResult.Failed.ServerNetworkError -> { - DatabaseSyncerResult.Failed.NetworkError( - errorResult = remoteGreenCardsResult.errorResult, - hasGreenCardsWithoutCredentials = greenCards - .any { greenCardUtil.hasNoActiveCredentials(it) } - ) - } - is NetworkRequestResult.Failed.CoronaCheckHttpError -> { - if (previousSyncResult == null) { - DatabaseSyncerResult.Failed.ServerError.FirstTime( - errorResult = remoteGreenCardsResult.errorResult - ) - } else { - DatabaseSyncerResult.Failed.ServerError.MultipleTimes( - errorResult = remoteGreenCardsResult.errorResult - ) - } - } - else -> { - DatabaseSyncerResult.Failed.Error( - errorResult = remoteGreenCardsResult.errorResult - ) - } - } - } - } - } else { - previousSyncResult ?: DatabaseSyncerResult.Success(listOf()) - } - } - } - } -} - -sealed class DatabaseSyncerResult { - data class Success( - val hints: List = listOf(), - val blockedEvents: List = listOf() - ) : DatabaseSyncerResult() - - data class FuzzyMatchingError( - val matchingBlobIds: List> - ) : DatabaseSyncerResult() - - sealed class Failed(open val errorResult: ErrorResult, open val failedAt: OffsetDateTime) : - DatabaseSyncerResult() { - data class NetworkError( - override val errorResult: ErrorResult, - val hasGreenCardsWithoutCredentials: Boolean - ) : Failed(errorResult, OffsetDateTime.now()) - - sealed class ServerError(override val errorResult: ErrorResult) : - Failed(errorResult, OffsetDateTime.now()) { - data class FirstTime(override val errorResult: ErrorResult) : ServerError(errorResult) - data class MultipleTimes(override val errorResult: ErrorResult) : - ServerError(errorResult) - } - - data class Error(override val errorResult: ErrorResult) : - Failed(errorResult, OffsetDateTime.now()) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/converters/HolderDatabaseConverter.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/converters/HolderDatabaseConverter.kt deleted file mode 100644 index 6bd8026d5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/converters/HolderDatabaseConverter.kt +++ /dev/null @@ -1,74 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.converters - -import androidx.room.TypeConverter -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -class HolderDatabaseConverter { - @TypeConverter - fun fromTimestampToOffsetDateTime(value: Long?): OffsetDateTime? { - return value?.let { OffsetDateTime.ofInstant(Instant.ofEpochSecond(it), ZoneOffset.UTC) } - } - - @TypeConverter - fun offsetDateTimeToTimestamp(date: OffsetDateTime?): Long? { - return date?.toEpochSecond() - } - - @TypeConverter - fun fromGreenCardType(value: String?): GreenCardType { - return GreenCardType.Eu - } - - @TypeConverter - fun greenCardTypeToString(type: GreenCardType?): String { - return GreenCardType.TYPE_EU - } - - @TypeConverter - fun fromOriginType(value: String?): OriginType? { - return when (value) { - OriginType.TYPE_RECOVERY -> OriginType.Recovery - OriginType.TYPE_TEST -> OriginType.Test - OriginType.TYPE_VACCINATION -> OriginType.Vaccination - else -> null - } - } - - @TypeConverter - fun originTypeToString(type: OriginType?): String? { - return when (type) { - OriginType.Recovery -> OriginType.TYPE_RECOVERY - OriginType.Test -> OriginType.TYPE_TEST - OriginType.Vaccination -> OriginType.TYPE_VACCINATION - else -> null - } - } - - @TypeConverter - fun fromRemovedEventReason(value: String): RemovedEventReason { - return when (value) { - RemovedEventReason.FUZZY_MATCHED -> RemovedEventReason.FuzzyMatched - else -> RemovedEventReason.Blocked - } - } - - @TypeConverter - fun removedEventReasonToString(type: RemovedEventReason): String { - return when (type) { - RemovedEventReason.Blocked -> RemovedEventReason.BLOCKED - RemovedEventReason.FuzzyMatched -> RemovedEventReason.FUZZY_MATCHED - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/CredentialDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/CredentialDao.kt deleted file mode 100644 index 461a88dcb..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/CredentialDao.kt +++ /dev/null @@ -1,37 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Update -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Dao -interface CredentialDao { - - @Query("SELECT * FROM credential") - suspend fun getAll(): List - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: CredentialEntity) - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(entity: List) - - @Update - suspend fun update(entity: CredentialEntity) - - @Query("DELETE FROM credential WHERE id = :id") - suspend fun delete(id: Int) - - @Query("DELETE FROM credential") - suspend fun deleteAll() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/EventGroupDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/EventGroupDao.kt deleted file mode 100644 index 11b6b86cf..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/EventGroupDao.kt +++ /dev/null @@ -1,57 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Dao -interface EventGroupDao { - - @Query("SELECT * FROM event_group") - suspend fun getAll(): List - - @Query("SELECT * FROM event_group WHERE id IN (:ids)") - suspend fun getAllOfIds(ids: List): List - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(entity: List) - - @Query("UPDATE event_group SET expiryDate = :expiryDate WHERE id = :eventGroupId") - suspend fun updateExpiryDate(eventGroupId: Int, expiryDate: OffsetDateTime) - - @Query("UPDATE event_group SET draft = :draft") - suspend fun updateDraft(draft: Boolean) - - @Query("UPDATE event_group SET draft = :draft WHERE id IN (:ids)") - suspend fun updateDraft(ids: List, draft: Boolean) - - @Delete - suspend fun delete(entity: EventGroupEntity) - - @Query("DELETE FROM event_group") - suspend fun deleteAll() - - @Query("DELETE FROM event_group WHERE type = :originType") - suspend fun deleteAllOfType(originType: OriginType) - - @Query("DELETE FROM event_group WHERE type NOT IN (:originTypes)") - suspend fun deleteAllOfNotTypes(originTypes: List) - - @Query("DELETE FROM event_group WHERE id IN (:ids)") - suspend fun deleteAllOfIds(ids: List) - - @Query("DELETE FROM event_group WHERE draft = 1") - suspend fun deleteDraftEvents() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/GreenCardDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/GreenCardDao.kt deleted file mode 100644 index 6eab24399..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/GreenCardDao.kt +++ /dev/null @@ -1,49 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction -import androidx.room.Update -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.models.GreenCard - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Dao -interface GreenCardDao { - - @Transaction - @Query("SELECT * FROM green_card") - suspend fun getAll(): List - - @Transaction - @Query("SELECT * FROM green_card WHERE type = :type AND wallet_id = :walletId") - suspend fun getAll(type: GreenCardType, walletId: Int): List - - @Query("SELECT * FROM green_card WHERE id = :greenCardId") - suspend fun get(greenCardId: Int): GreenCardEntity? - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: GreenCardEntity): Long - - @Update - suspend fun update(entity: GreenCardEntity) - - @Delete - suspend fun delete(entity: GreenCardEntity) - - @Query("DELETE FROM green_card") - suspend fun deleteAll() - - @Query("DELETE FROM green_card WHERE type NOT IN (:types)") - suspend fun deleteAllOfNotTypes(types: List) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/OriginDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/OriginDao.kt deleted file mode 100644 index 49b832324..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/OriginDao.kt +++ /dev/null @@ -1,34 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Dao -interface OriginDao { - - @Query("SELECT * FROM origin") - suspend fun getAll(): List - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertAll(entity: List) - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: OriginEntity): Long - - @Delete - suspend fun delete(entity: OriginEntity) - - @Query("DELETE FROM origin") - suspend fun deleteAll() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/OriginHintDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/OriginHintDao.kt deleted file mode 100644 index 26e43d862..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/OriginHintDao.kt +++ /dev/null @@ -1,25 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.dao -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import nl.rijksoverheid.ctr.persistence.database.entities.OriginHintEntity - -@Dao -interface OriginHintDao { - @Query("SELECT * FROM origin_hint WHERE hint = :hint") - suspend fun get(hint: String): List - - @Query("DELETE FROM origin_hint") - suspend fun deleteAll() - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: OriginHintEntity) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/RemovedEventDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/RemovedEventDao.kt deleted file mode 100644 index 1fc51fde5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/RemovedEventDao.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventEntity -import nl.rijksoverheid.ctr.persistence.database.entities.RemovedEventReason - -@Dao -interface RemovedEventDao { - @Transaction - @Query("SELECT * FROM removed_event WHERE reason = :reason") - suspend fun getAll(reason: RemovedEventReason): List - - @Query("DELETE FROM removed_event WHERE reason = :reason") - suspend fun deleteAll(reason: RemovedEventReason) - - @Query("DELETE FROM removed_event") - suspend fun deleteAll() - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: RemovedEventEntity) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/SecretKeyDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/SecretKeyDao.kt deleted file mode 100644 index 29eee95fd..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/SecretKeyDao.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import nl.rijksoverheid.ctr.persistence.database.entities.SecretKeyEntity - -@Dao -interface SecretKeyDao { - @Query("SELECT * FROM secret_key WHERE green_card_id = :greenCardId") - suspend fun get(greenCardId: Int): SecretKeyEntity - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insert(entity: SecretKeyEntity) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/WalletDao.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/WalletDao.kt deleted file mode 100644 index cbb1f1f92..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/dao/WalletDao.kt +++ /dev/null @@ -1,33 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.dao - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.Query -import androidx.room.Transaction -import kotlinx.coroutines.flow.Flow -import nl.rijksoverheid.ctr.persistence.database.entities.WalletEntity -import nl.rijksoverheid.ctr.persistence.database.models.Wallet - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Dao -interface WalletDao { - - @Transaction - @Query("SELECT * FROM wallet") - fun get(): Flow> - - @Query("SELECT * FROM wallet") - suspend fun getAll(): List - - @Insert - suspend fun insert(entity: WalletEntity) - - @Query("DELETE FROM wallet WHERE id = :id") - suspend fun delete(id: Int) -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/CredentialEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/CredentialEntity.kt deleted file mode 100644 index 9413dd48a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/CredentialEntity.kt +++ /dev/null @@ -1,60 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey -import java.time.OffsetDateTime - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Entity( - tableName = "credential", - foreignKeys = [ForeignKey( - entity = GreenCardEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("green_card_id"), - onDelete = ForeignKey.CASCADE - )] -) - -data class CredentialEntity( - @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo(name = "green_card_id", index = true) val greenCardId: Long, - @ColumnInfo(typeAffinity = ColumnInfo.BLOB) val data: ByteArray, - val credentialVersion: Int, - val validFrom: OffsetDateTime, - val expirationTime: OffsetDateTime, - val category: String? = null -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as CredentialEntity - - if (id != other.id) return false - if (greenCardId != other.greenCardId) return false - if (!data.contentEquals(other.data)) return false - if (credentialVersion != other.credentialVersion) return false - if (validFrom != other.validFrom) return false - if (expirationTime != other.expirationTime) return false - - return true - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + greenCardId.hashCode() - result = 31 * result + data.contentHashCode() - result = 31 * result + credentialVersion - result = 31 * result + validFrom.hashCode() - result = 31 * result + expirationTime.hashCode() - return result - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/EventGroupEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/EventGroupEntity.kt deleted file mode 100644 index e7bfcac77..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/EventGroupEntity.kt +++ /dev/null @@ -1,62 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index -import androidx.room.PrimaryKey -import java.time.OffsetDateTime - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Entity( - indices = [Index(value = ["provider_identifier", "type", "scope"], unique = true)], - tableName = "event_group", - foreignKeys = [ForeignKey( - entity = WalletEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("wallet_id"), - onDelete = ForeignKey.CASCADE - )] -) -data class EventGroupEntity( - @PrimaryKey(autoGenerate = true) val id: Int = 0, - @ColumnInfo(name = "wallet_id", index = true) val walletId: Int, - @ColumnInfo(name = "provider_identifier") val providerIdentifier: String, - val type: OriginType, - val scope: String, - val expiryDate: OffsetDateTime?, - @ColumnInfo(defaultValue = "0") val draft: Boolean, - @ColumnInfo(typeAffinity = ColumnInfo.BLOB) val jsonData: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as EventGroupEntity - - if (id != other.id) return false - if (walletId != other.walletId) return false - if (providerIdentifier != other.providerIdentifier) return false - if (type != other.type) return false - if (scope != other.scope) return false - if (!jsonData.contentEquals(other.jsonData)) return false - - return true - } - - override fun hashCode(): Int { - var result = id - result = 31 * result + walletId - result = 31 * result + providerIdentifier.hashCode() - result = 31 * result + type.hashCode() - result = 31 * result + (scope.hashCode()) - result = 31 * result + jsonData.contentHashCode() - return result - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/GreenCardEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/GreenCardEntity.kt deleted file mode 100644 index 24d2db6f3..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/GreenCardEntity.kt +++ /dev/null @@ -1,31 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import android.os.Parcelable -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Entity( - tableName = "green_card", - foreignKeys = [ForeignKey( - entity = WalletEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("wallet_id"), - onDelete = ForeignKey.CASCADE - )] -) -@Parcelize -data class GreenCardEntity( - @PrimaryKey(autoGenerate = true) val id: Int = 0, - @ColumnInfo(name = "wallet_id", index = true) val walletId: Int, - val type: GreenCardType -) : Parcelable diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/GreenCardType.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/GreenCardType.kt deleted file mode 100644 index a2c574f0b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/GreenCardType.kt +++ /dev/null @@ -1,21 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -sealed class GreenCardType : Parcelable { - - companion object { - const val TYPE_EU = "eu" - } - - @Parcelize - object Eu : GreenCardType(), Parcelable -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginEntity.kt deleted file mode 100644 index d6e43fa22..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginEntity.kt +++ /dev/null @@ -1,33 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey -import java.time.OffsetDateTime - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Entity( - tableName = "origin", - foreignKeys = [ForeignKey( - entity = GreenCardEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("green_card_id"), - onDelete = ForeignKey.CASCADE - )] -) -data class OriginEntity( - @PrimaryKey(autoGenerate = true) val id: Int = 0, - @ColumnInfo(name = "green_card_id", index = true) val greenCardId: Long, - val type: OriginType, - val eventTime: OffsetDateTime, - val expirationTime: OffsetDateTime, - val validFrom: OffsetDateTime, - val doseNumber: Int? = null // Only exists for type Vaccination and GreenCardType Domestic -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginHintEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginHintEntity.kt deleted file mode 100644 index ec458ba62..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginHintEntity.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey - -@Entity( - tableName = "origin_hint", - foreignKeys = [ForeignKey( - entity = OriginEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("origin_id"), - onDelete = ForeignKey.CASCADE - )] -) -data class OriginHintEntity( - @PrimaryKey(autoGenerate = true) val id: Int = 0, - @ColumnInfo(name = "origin_id", index = true) val originId: Long, - @ColumnInfo(index = true) val hint: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginType.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginType.kt deleted file mode 100644 index 36e0dc9c2..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/OriginType.kt +++ /dev/null @@ -1,50 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ - -/** - * Types of origins that can be returned by the backend or the database - */ -sealed class OriginType(val order: Int) : Parcelable { - - companion object { - const val TYPE_VACCINATION = "vaccination" - const val TYPE_RECOVERY = "recovery" - const val TYPE_TEST = "test" - - fun fromTypeString(typeString: String): OriginType { - return when (typeString) { - TYPE_VACCINATION -> Vaccination - TYPE_RECOVERY -> Recovery - TYPE_TEST -> Test - else -> throw IllegalStateException("Type not known") - } - } - } - - @Parcelize - object Vaccination : OriginType(1) - - @Parcelize - object Recovery : OriginType(2) - - @Parcelize - object Test : OriginType(4) - - fun getTypeString(): String { - return when (this) { - is Vaccination -> TYPE_VACCINATION - is Recovery -> TYPE_RECOVERY - is Test -> TYPE_TEST - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/RemovedEventEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/RemovedEventEntity.kt deleted file mode 100644 index 4d84d6303..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/RemovedEventEntity.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey -import java.time.OffsetDateTime - -@Entity( - tableName = "removed_event", - foreignKeys = [ForeignKey( - entity = WalletEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("wallet_id"), - onDelete = ForeignKey.CASCADE - )] -) -data class RemovedEventEntity( - @PrimaryKey(autoGenerate = true) val id: Long = 0, - @ColumnInfo(name = "wallet_id", index = true) val walletId: Int, - val type: String, - @ColumnInfo(name = "event_time") val eventTime: OffsetDateTime?, - @ColumnInfo(defaultValue = "blocked") val reason: RemovedEventReason -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/RemovedEventReason.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/RemovedEventReason.kt deleted file mode 100644 index 150254e4f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/RemovedEventReason.kt +++ /dev/null @@ -1,19 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -sealed class RemovedEventReason { - - companion object { - const val BLOCKED = "blocked" - const val FUZZY_MATCHED = "fuzzy_matched" - } - - object Blocked : RemovedEventReason() - object FuzzyMatched : RemovedEventReason() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/SecretKeyEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/SecretKeyEntity.kt deleted file mode 100644 index 85f093559..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/SecretKeyEntity.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.PrimaryKey - -@Entity( - tableName = "secret_key", - foreignKeys = [ForeignKey( - entity = GreenCardEntity::class, - parentColumns = arrayOf("id"), - childColumns = arrayOf("green_card_id"), - onDelete = ForeignKey.CASCADE - )] -) -data class SecretKeyEntity( - @PrimaryKey(autoGenerate = true) val id: Int = 0, - @ColumnInfo(name = "green_card_id", index = true) val greenCardId: Int, - @ColumnInfo(name = "secret") val secretKey: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/WalletEntity.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/WalletEntity.kt deleted file mode 100644 index 23ea9a164..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/entities/WalletEntity.kt +++ /dev/null @@ -1,17 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.entities - -import androidx.room.Entity -import androidx.room.PrimaryKey - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -@Entity(tableName = "wallet") -data class WalletEntity( - @PrimaryKey val id: Int, - val label: String -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/models/GreenCard.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/models/GreenCard.kt deleted file mode 100644 index b03eb6e70..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/models/GreenCard.kt +++ /dev/null @@ -1,30 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.models - -import androidx.room.Embedded -import androidx.room.Relation -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class GreenCard( - @Embedded val greenCardEntity: GreenCardEntity, - - @Relation( - parentColumn = "id", - entityColumn = "green_card_id" - ) - val origins: List, - - @Relation( - parentColumn = "id", - entityColumn = "green_card_id" - ) - val credentialEntities: List -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/models/Wallet.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/models/Wallet.kt deleted file mode 100644 index 105ed707b..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/models/Wallet.kt +++ /dev/null @@ -1,32 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.models - -import androidx.room.Embedded -import androidx.room.Relation -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.WalletEntity - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -data class Wallet( - @Embedded val walletEntity: WalletEntity, - - @Relation( - parentColumn = "id", - entityColumn = "wallet_id", - entity = EventGroupEntity::class - ) - val eventEntities: List = listOf(), - - @Relation( - parentColumn = "id", - entityColumn = "wallet_id", - entity = GreenCardEntity::class - ) - val greenCards: List = listOf() -) diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/CreateEuGreenCardsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/CreateEuGreenCardsUseCase.kt deleted file mode 100644 index d18a87bdb..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/CreateEuGreenCardsUseCase.kt +++ /dev/null @@ -1,75 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.usecases - -import java.time.Instant -import java.time.OffsetDateTime -import java.time.ZoneOffset -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.CredentialEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardEntity -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginEntity -import nl.rijksoverheid.ctr.persistence.database.entities.OriginHintEntity -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper - -interface CreateEuGreenCardUseCase { - suspend fun create(greenCard: RemoteGreenCards.EuGreenCard) -} - -class CreateEuGreenCardUseCaseImpl( - private val holderDatabase: HolderDatabase, - private val mobileCoreWrapper: MobileCoreWrapper -) : CreateEuGreenCardUseCase { - override suspend fun create(greenCard: RemoteGreenCards.EuGreenCard) { - // Create green card - val localEuropeanGreenCardId = holderDatabase.greenCardDao().insert( - GreenCardEntity( - walletId = 1, - type = GreenCardType.Eu - ) - ) - - // Create origins for european green card - greenCard.origins.map { remoteOrigin -> - val localOriginId = holderDatabase.originDao().insert( - OriginEntity( - greenCardId = localEuropeanGreenCardId, - type = remoteOrigin.type, - eventTime = remoteOrigin.eventTime, - expirationTime = remoteOrigin.expirationTime, - validFrom = remoteOrigin.validFrom - ) - ) - - remoteOrigin.hints?.forEach { hint -> - holderDatabase.originHintDao().insert( - OriginHintEntity( - originId = localOriginId, - hint = hint - ) - ) - } - } - - // Create credential - val europeanCredential = mobileCoreWrapper.readEuropeanCredential( - credential = greenCard.credential.toByteArray() - ) - - val entity = CredentialEntity( - greenCardId = localEuropeanGreenCardId, - data = greenCard.credential.toByteArray(), - credentialVersion = europeanCredential.getInt("credentialVersion"), - validFrom = OffsetDateTime.ofInstant( - Instant.ofEpochSecond(europeanCredential.getLong("issuedAt")), - ZoneOffset.UTC - ), - expirationTime = OffsetDateTime.ofInstant( - Instant.ofEpochSecond(europeanCredential.getLong("expirationTime")), - ZoneOffset.UTC - ) - ) - - holderDatabase.credentialDao().insert(entity) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/DraftEventUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/DraftEventUseCase.kt deleted file mode 100644 index 52c01047f..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/DraftEventUseCase.kt +++ /dev/null @@ -1,30 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.usecases - -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase - -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - * - */ -interface DraftEventUseCase { - suspend fun remove() - suspend fun finalise() -} - -class DraftEventUseCaseImpl( - private val holderDatabase: HolderDatabase -) : DraftEventUseCase { - - private val eventGroupDao = holderDatabase.eventGroupDao() - - override suspend fun remove() { - eventGroupDao.deleteDraftEvents() - } - - override suspend fun finalise() { - eventGroupDao.updateDraft(false) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/GetRemoteGreenCardsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/GetRemoteGreenCardsUseCase.kt deleted file mode 100644 index eeae4c243..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/GetRemoteGreenCardsUseCase.kt +++ /dev/null @@ -1,124 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.usecases - -import nl.rijksoverheid.ctr.holder.api.repositories.CoronaCheckRepository -import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent -import nl.rijksoverheid.ctr.holder.get_events.usecases.GetRemoteProtocolFromEventGroupUseCase -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.ErrorResult -import nl.rijksoverheid.ctr.shared.models.Flow -import nl.rijksoverheid.ctr.shared.models.NetworkRequestResult -import org.json.JSONObject - -/** - * Get green cards from remote - */ -interface GetRemoteGreenCardsUseCase { - suspend fun get( - events: List, - secretKey: String, - flow: Flow - ): RemoteGreenCardsResult -} - -class GetRemoteGreenCardsUseCaseImpl( - private val coronaCheckRepository: CoronaCheckRepository, - private val mobileCoreWrapper: MobileCoreWrapper, - private val getRemoteProtocolFromEventGroupUseCase: GetRemoteProtocolFromEventGroupUseCase -) : GetRemoteGreenCardsUseCase { - - override suspend fun get( - events: List, - secretKey: String, - flow: Flow - ): RemoteGreenCardsResult { - return try { - val prepareIssue = - when (val prepareIssueResult = coronaCheckRepository.getPrepareIssue()) { - is NetworkRequestResult.Success -> { - prepareIssueResult.response - } - is NetworkRequestResult.Failed -> { - return RemoteGreenCardsResult.Error(prepareIssueResult) - } - } - - val commitmentMessage = try { - mobileCoreWrapper.createCommitmentMessage( - secretKey = secretKey.toByteArray(), - prepareIssueMessage = prepareIssue.prepareIssueMessage - ) - } catch (e: Exception) { - return RemoteGreenCardsResult.Error( - AppErrorResult( - step = HolderStep.PrepareIssueNetworkRequest, - e = e - ) - ) - } - - val remoteGreenCardsResult = coronaCheckRepository.getGreenCards( - stoken = prepareIssue.stoken, - events = events.map { - val jsonObject = JSONObject(it.jsonData.decodeToString()) - jsonObject.put("id", it.id.toString()) - jsonObject.toString() - }, - issueCommitmentMessage = commitmentMessage, - flow = flow - ) - - when (remoteGreenCardsResult) { - is NetworkRequestResult.Success -> { - val matchingBlobIds = remoteGreenCardsResult.response.context?.matchingBlobIds - if (matchingBlobIds?.isNotEmpty() == true) { - val fuzzyMatchedEvents = matchingBlobIds.flatten().toSet().mapNotNull { fuzzyMatchedEventId -> - val eventGroup = events.firstOrNull() { event -> event.id == fuzzyMatchedEventId } - val remoteProtocol = - eventGroup?.let { getRemoteProtocolFromEventGroupUseCase.get(it) } - remoteProtocol?.events?.mapNotNull { remoteEvent -> - remoteEvent - } - }.flatten() - return RemoteGreenCardsResult.FuzzyMatchingError(matchingBlobIds, fuzzyMatchedEvents) - } - - val blockedEventIds = - remoteGreenCardsResult.response.blobExpireDates?.filter { it.reason == "event_blocked" } - ?: listOf() - val blockedEvents = blockedEventIds.mapNotNull { blobExpiry -> - val eventGroup = events.firstOrNull { event -> event.id == blobExpiry.id } - val remoteProtocol = - eventGroup?.let { getRemoteProtocolFromEventGroupUseCase.get(it) } - remoteProtocol?.events?.mapNotNull { remoteEvent -> - remoteEvent - } - }.flatten() - RemoteGreenCardsResult.Success(remoteGreenCardsResult.response, blockedEvents) - } - is NetworkRequestResult.Failed -> { - RemoteGreenCardsResult.Error(remoteGreenCardsResult) - } - } - } catch (e: Exception) { - RemoteGreenCardsResult.Error(AppErrorResult(HolderStep.GetCredentialsNetworkRequest, e)) - } - } -} - -sealed class RemoteGreenCardsResult { - data class Success( - val remoteGreenCards: RemoteGreenCards, - val blockedEvents: List = listOf() - ) : RemoteGreenCardsResult() - - data class FuzzyMatchingError( - val matchingBlobIds: List>, - val fuzzyMatchedEvents: List - ) : RemoteGreenCardsResult() - - data class Error(val errorResult: ErrorResult) : RemoteGreenCardsResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/RemoveCTBUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/RemoveCTBUseCase.kt deleted file mode 100644 index 7705af2dc..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/RemoveCTBUseCase.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.usecases - -import android.util.Base64 -import com.squareup.moshi.Moshi -import nl.rijksoverheid.ctr.holder.your_events.utils.SignedResponse -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.GreenCardType -import nl.rijksoverheid.ctr.persistence.database.entities.OriginType - -interface RemoveCTBUseCase { - suspend fun execute() -} - -class RemoveCTBUseCaseImpl( - private val moshi: Moshi, - private val holderDatabase: HolderDatabase -) : RemoveCTBUseCase { - override suspend fun execute() { - holderDatabase.eventGroupDao().deleteAllOfNotTypes( - listOf( - OriginType.Vaccination, - OriginType.Recovery, - OriginType.Test - ) - ) - val remainingEventGroups = holderDatabase.eventGroupDao().getAll() - val eventGroupsWithVaccinationAssessmentEvents = remainingEventGroups.filter { - try { - val payload = moshi.adapter(SignedResponse::class.java) - .fromJson(String(it.jsonData))?.payload - String(Base64.decode(payload, Base64.DEFAULT)).contains("vaccinationassessment") - } catch (exception: Exception) { - false - } - } - if (eventGroupsWithVaccinationAssessmentEvents.isNotEmpty()) { - holderDatabase.eventGroupDao().deleteAllOfIds(eventGroupsWithVaccinationAssessmentEvents.map { it.id }) - } - holderDatabase.greenCardDao().deleteAllOfNotTypes( - listOf( - GreenCardType.Eu - ) - ) - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/RemoveExpiredEventsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/RemoveExpiredEventsUseCase.kt deleted file mode 100644 index bf20e9209..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/RemoveExpiredEventsUseCase.kt +++ /dev/null @@ -1,30 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.usecases - -import java.time.Clock -import java.time.OffsetDateTime -import nl.rijksoverheid.ctr.holder.usecases.HolderFeatureFlagUseCase -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity - -interface RemoveExpiredEventsUseCase { - suspend fun execute(events: List) -} - -class RemoveExpiredEventsUseCaseImpl( - private val clock: Clock, - private val featureFlagUseCase: HolderFeatureFlagUseCase, - private val holderDatabase: HolderDatabase -) : RemoveExpiredEventsUseCase { - - override suspend fun execute(events: List) { - if (featureFlagUseCase.isInArchiveMode()) { - return - } - - events.forEach { - if (it.expiryDate != null && it.expiryDate <= OffsetDateTime.now(clock)) { - holderDatabase.eventGroupDao().delete(it) - } - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/SyncRemoteGreenCardsUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/SyncRemoteGreenCardsUseCase.kt deleted file mode 100644 index 9509ba18a..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/SyncRemoteGreenCardsUseCase.kt +++ /dev/null @@ -1,53 +0,0 @@ -package nl.rijksoverheid.ctr.persistence.database.usecases - -import nl.rijksoverheid.ctr.holder.models.HolderStep -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase -import nl.rijksoverheid.ctr.shared.MobileCoreWrapper -import nl.rijksoverheid.ctr.shared.models.AppErrorResult -import nl.rijksoverheid.ctr.shared.models.ErrorResult - -/** - * Inserts the green cards fetched from remote into the database - */ -interface SyncRemoteGreenCardsUseCase { - suspend fun execute(remoteGreenCards: RemoteGreenCards, secretKey: String): SyncRemoteGreenCardsResult -} - -class SyncRemoteGreenCardsUseCaseImpl( - private val holderDatabase: HolderDatabase, - private val createEuGreenCardsUseCase: CreateEuGreenCardUseCase, - private val mobileCoreWrapper: MobileCoreWrapper -) : SyncRemoteGreenCardsUseCase { - - override suspend fun execute(remoteGreenCards: RemoteGreenCards, secretKey: String): SyncRemoteGreenCardsResult { - try { - // Clear everything from the database - holderDatabase.greenCardDao().deleteAll() - holderDatabase.originDao().deleteAll() - holderDatabase.credentialDao().deleteAll() - holderDatabase.originHintDao().deleteAll() - - remoteGreenCards.euGreencards?.let { - it.forEach { greenCard -> - createEuGreenCardsUseCase.create( - greenCard = greenCard - ) - } - } - return SyncRemoteGreenCardsResult.Success - } catch (e: Exception) { - return SyncRemoteGreenCardsResult.Failed( - AppErrorResult( - step = HolderStep.StoringCredentials, - e = e - ) - ) - } - } -} - -sealed class SyncRemoteGreenCardsResult { - object Success : SyncRemoteGreenCardsResult() - data class Failed(val errorResult: ErrorResult) : SyncRemoteGreenCardsResult() -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/UpdateEventExpirationUseCase.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/UpdateEventExpirationUseCase.kt deleted file mode 100644 index 6c9e55ac5..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/usecases/UpdateEventExpirationUseCase.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2021 De Staat der Nederlanden, Ministerie van Volksgezondheid, Welzijn en Sport. - * Licensed under the EUROPEAN UNION PUBLIC LICENCE v. 1.2 - * - * SPDX-License-Identifier: EUPL-1.2 - */ - -package nl.rijksoverheid.ctr.persistence.database.usecases - -import nl.rijksoverheid.ctr.holder.your_events.models.RemoteGreenCards -import nl.rijksoverheid.ctr.persistence.database.HolderDatabase - -interface UpdateEventExpirationUseCase { - suspend fun update(blobExpireDates: List) -} - -class UpdateEventExpirationUseCaseImpl( - private val holderDatabase: HolderDatabase -) : UpdateEventExpirationUseCase { - - override suspend fun update(blobExpireDates: List) { - blobExpireDates.forEach { - holderDatabase.eventGroupDao().updateExpiryDate( - eventGroupId = it.id, - expiryDate = it.expiry - ) - } - } -} diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/util/YourEventFragmentEndStateUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/util/YourEventFragmentEndStateUtil.kt deleted file mode 100644 index 8b1378917..000000000 --- a/holder/src/main/java/nl/rijksoverheid/ctr/persistence/database/util/YourEventFragmentEndStateUtil.kt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/holder/src/main/res/layout/adapter_item_dashboard_add_qr.xml b/holder/src/main/res/layout/adapter_item_dashboard_add_qr.xml deleted file mode 100644 index a4049ad48..000000000 --- a/holder/src/main/res/layout/adapter_item_dashboard_add_qr.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/holder/src/main/res/layout/adapter_item_dashboard_green_card.xml b/holder/src/main/res/layout/adapter_item_dashboard_green_card.xml deleted file mode 100644 index f50b6d6f0..000000000 --- a/holder/src/main/res/layout/adapter_item_dashboard_green_card.xml +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/holder/src/main/res/layout/adapter_item_dashboard_green_card_placeholder.xml b/holder/src/main/res/layout/adapter_item_dashboard_green_card_placeholder.xml deleted file mode 100644 index b0c189671..000000000 --- a/holder/src/main/res/layout/adapter_item_dashboard_green_card_placeholder.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - diff --git a/holder/src/main/res/layout/adapter_item_dashboard_header.xml b/holder/src/main/res/layout/adapter_item_dashboard_header.xml deleted file mode 100644 index ad9400156..000000000 --- a/holder/src/main/res/layout/adapter_item_dashboard_header.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/holder/src/main/res/layout/adapter_item_dashboard_info_card.xml b/holder/src/main/res/layout/adapter_item_dashboard_info_card.xml deleted file mode 100644 index 8e347194c..000000000 --- a/holder/src/main/res/layout/adapter_item_dashboard_info_card.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - -