diff --git a/.github/workflows/build-release-candidate.yml b/.github/workflows/build-release-candidate.yml index df7609ec4a..6eb35c8764 100644 --- a/.github/workflows/build-release-candidate.yml +++ b/.github/workflows/build-release-candidate.yml @@ -62,4 +62,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: ${{ env.repository_name }} - Android APK - path: ${{ env.main_project_module }}/build/outputs/apk/dhis/release/dhis2-v${{ steps.read-version.outputs.vName }}-dhis-release.apk + path: ${{ env.main_project_module }}/build/outputs/apk/dhis/release/dhis2-v${{ steps.read-version.outputs.vName }}.apk diff --git a/.github/workflows/continuous-delivery.yml b/.github/workflows/continuous-delivery.yml new file mode 100644 index 0000000000..c3d7c89978 --- /dev/null +++ b/.github/workflows/continuous-delivery.yml @@ -0,0 +1,54 @@ +name: Continuous Delivery + +env: + + main_project_module: app + +on: + workflow_dispatch: + push: + branches: + - main + - develop + - release/* + +jobs: + deployment_job: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Set Current Date + - name: Set current date + id: date + run: echo "date=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" + + # Set Repository Name As Env Variable + - name: Set repository name as env variable + run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV + + - name: Set Up JDK + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + cache: 'gradle' + + - name: Change wrapper permissions + run: chmod +x ./gradlew + + # Create APK Debug + - name: Build apk debug project (APK) - ${{ env.main_project_module }} module + run: ./gradlew assembleDhisDebug + + - name: Read version name from file + working-directory: ./gradle + id: read-version + run: echo "vName=$(grep 'vName' libs.versions.toml | awk -F' = ' '{print $2}' | tr -d '"')" >> "$GITHUB_OUTPUT" + + # Upload Artifact Build + - name: Upload Android artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.repository_name }} - Android APK - ${{ steps.date.outputs.date }} + path: ${{ env.main_project_module }}/build/outputs/apk/dhis/debug/dhis2-v${{ steps.read-version.outputs.vName }}-training.apk diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6a520473a5..0b7aa91c4e 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -90,4 +90,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: ${{ env.repository_name }} - Android APK - path: ${{ env.main_project_module }}/build/outputs/apk/dhis/debug/dhis2-v${{ steps.read-version.outputs.vName }}-dhis-debug.apk + path: ${{ env.main_project_module }}/build/outputs/apk/dhis/debug/dhis2-v${{ steps.read-version.outputs.vName }}-training.apk diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml new file mode 100644 index 0000000000..77b5f194c5 --- /dev/null +++ b/.github/workflows/deploy-release.yml @@ -0,0 +1,92 @@ +name: Deploy Release + +env: + main_project_module: app + +on: + workflow_dispatch: + inputs: + github_release: + description: 'enable github release' + required: true + type: boolean + google_play_release: + description: 'enable google play release' + required: true + type: boolean + release_tag_name: + description: 'tag' + required: true + type: string + is_patch: + description: 'Is the new version a patch' + required: true + type: boolean + default: false + +jobs: + Build-Apk: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set repository name as env variable + run: echo "repository_name=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}')" >> $GITHUB_ENV + - name: Read version name from file + working-directory: ./gradle + id: read-version + run: echo "::set-output name=vName::$(grep 'vName' libs.versions.toml | awk -F' = ' '{print $2}' | tr -d '"')" + - name: Set Up JDK + uses: actions/setup-java@v3 + with: + distribution: 'zulu' # See 'Supported distributions' for available options + java-version: '17' + cache: 'gradle' + - name: Change wrapper permissions + run: chmod +x ./gradlew + - name: Decode Keystore + id: decode_keystore + uses: timheuer/base64-to-file@v1 + with: + fileName: 'dhis_keystore.jks' + encodedString: ${{ secrets.KEYSTORE }} + - name: Build release apk + run: ./gradlew app:assembleRelease app:assembleDhisDebug + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SIGNING_KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + SIGNING_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + SIGNING_STORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + SIGNING_KEYSTORE_PATH: ${{ steps.decode_keystore.outputs.filePath }} + + - name: Upload to Play Store + if: ${{ inputs.google_play_release }} + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: com.dhis2 + releaseFiles: ${{ env.main_project_module }}/build/outputs/apk/dhisPlayServices/release/dhis2-v${{ steps.read-version.outputs.vName }}-googlePlay.apk + track: alpha + whatsNewDirectory: whatsnew + + + - name: Github Patch Release + if: ${{ (inputs.github_release == true) && (inputs.is_patch == true) }} + uses: ncipollo/release-action@v1 + with: + allowUpdates: true + draft: true + generateReleaseNotes: true + name: "Android Capture App for DHIS 2 (v${{ inputs.release_tag_name }}) - Patch version" + tag: ${{ inputs.release_tag_name }} + artifacts: ${{ env.main_project_module }}/build/outputs/apk/dhis/release/dhis2-v${{ steps.read-version.outputs.vName }}.apk,${{ env.main_project_module }}/build/outputs/apk/dhisPlayServices/release/dhis2-v${{ steps.read-version.outputs.vName }}-googlePlay.apk,${{ env.main_project_module }}/build/outputs/apk/dhis/debug/dhis2-v${{ steps.read-version.outputs.vName }}-training.apk + + - name: Github New Release + if: ${{ (inputs.github_release == true) && (inputs.is_patch == false) }} + uses: ncipollo/release-action@v1 + with: + allowUpdates: true + draft: true + bodyFile: "RELEASE.md" + name: "Android Capture App for DHIS 2 (v${{ inputs.release_tag_name }})" + tag: ${{ inputs.release_tag_name }} + artifacts: ${{ env.main_project_module }}/build/outputs/apk/dhis/release/dhis2-v${{ steps.read-version.outputs.vName }}.apk,${{ env.main_project_module }}/build/outputs/apk/dhisPlayServices/release/dhis2-v${{ steps.read-version.outputs.vName }}-googlePlay.apk,${{ env.main_project_module }}/build/outputs/apk/dhis/debug/dhis2-v${{ steps.read-version.outputs.vName }}-training.apk diff --git a/.tx/config b/.tx/config index 5529965f6d..d17aec75f4 100644 --- a/.tx/config +++ b/.tx/config @@ -44,3 +44,16 @@ source_lang = en type = ANDROID minimum_perc = 0 +[o:hisp-uio:p:dhis2-android-capture-app:r:ui-strings-xml] +file_filter = ui-components/src/main/res/values-/strings.xml +source_file = ui-components/src/main/res/values/strings.xml +source_lang = en +type = ANDROID +minimum_perc = 0 + +[o:hisp-uio:p:dhis2-android-capture-app:r:table-strings-xml] +file_filter = compose-table/src/main/res/values-/strings.xml +source_file = compose-table/src/main/res/values/strings.xml +source_lang = en +type = ANDROID +minimum_perc = 0 diff --git a/RELEASE.md b/RELEASE.md index 10b51397d1..19cb83d747 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,69 +1,9 @@ -Android Capture App for DHIS 2 (v2.9.1) - Patch version - - - - - - - - -
- - -This is a patch version of the DHIS2 Android App It builds upon the last version including bug fixes that couldn't wait to the next version. -It includes no functional improvements neither changes in the User Interface. It means that yours users can update without experiencing any change in the UI. -
+# Release notes - Android App for DHIS2 - 3.0.0.1 -## Bugs fixed -* [ANDROAPP-5895](https://dhis2.atlassian.net/browse/ANDROAPP-5895) Correct misalignment when entering text for inputShell -* [ANDROAPP-5885](https://dhis2.atlassian.net/browse/ANDROAPP-5885) [Data set] indicators don't update until the user moves to a different cell -* [ANDROAPP-5881](https://dhis2.atlassian.net/browse/ANDROAPP-5881) Input with virtual keyboard not working correctly -* [ANDROAPP-5872](https://dhis2.atlassian.net/browse/ANDROAPP-5872) [LMIS] Search in stock management is not updating the list -* [ANDROAPP-5871](https://dhis2.atlassian.net/browse/ANDROAPP-5871) App Not Responding when local db is encrypted -* [ANDROAPP-5856](https://dhis2.atlassian.net/browse/ANDROAPP-5856) ANR ReadableStoreImpl.addObjectsToCollection in DataSetDetailRepositoryImpl -* [ANDROAPP-5825](https://dhis2.atlassian.net/browse/ANDROAPP-5825) Input date value changes on click in schedule new event screen -* [ANDROAPP-5821](https://dhis2.atlassian.net/browse/ANDROAPP-5821) NoSuchElementException: List is empty. -* [ANDROAPP-5807](https://dhis2.atlassian.net/browse/ANDROAPP-5807) Crash when parsing value to input in InputDateTime -* [ANDROAPP-5804](https://dhis2.atlassian.net/browse/ANDROAPP-5804) Incorrect label on bar codes, QR codes and GS1 -* [ANDROAPP-5803](https://dhis2.atlassian.net/browse/ANDROAPP-5803) On schedule event due date incorrect when last previous event does not have a report date -* [ANDROAPP-5788](https://dhis2.atlassian.net/browse/ANDROAPP-5788) Keyboard hides helper text if the selected field is near the bottom of the screen -* [ANDROAPP-5773](https://dhis2.atlassian.net/browse/ANDROAPP-5773) Analytics are not being displayed as tables. -* [ANDROAPP-5770](https://dhis2.atlassian.net/browse/ANDROAPP-5770) IllegalStateException: Expected BringIntoViewRequester to not be used before parents are placed. -* [ANDROAPP-5769](https://dhis2.atlassian.net/browse/ANDROAPP-5769) ApplicationNotResponding: ANR for at least 5000 ms. -* [ANDROAPP-5767](https://dhis2.atlassian.net/browse/ANDROAPP-5767) RuntimeException in teidashboardActivity Sentry issue -* [ANDROAPP-5764](https://dhis2.atlassian.net/browse/ANDROAPP-5764) BottomSheetDialog shows barcode expanded with old form -* [ANDROAPP-5749](https://dhis2.atlassian.net/browse/ANDROAPP-5749) Incorrect behavior when tapping on Next on sections that are too long -* [ANDROAPP-5746](https://dhis2.atlassian.net/browse/ANDROAPP-5746) Exception when trying to add a file from downloads directory -* [ANDROAPP-5743](https://dhis2.atlassian.net/browse/ANDROAPP-5743) [Bug?] Calculated variables save integer values with ".0" -* [ANDROAPP-5742](https://dhis2.atlassian.net/browse/ANDROAPP-5742) [Local Analytics] App isn't plotting all the points (per event), only the first one. -* [ANDROAPP-5741](https://dhis2.atlassian.net/browse/ANDROAPP-5741) [Local Analytics] App crashes if charts are empty -* [ANDROAPP-5740](https://dhis2.atlassian.net/browse/ANDROAPP-5740) Display error correctly when date or time is incomplete -* [ANDROAPP-5726](https://dhis2.atlassian.net/browse/ANDROAPP-5726) RTS workflow needs to allow for translating the 3 transaction types -* [ANDROAPP-5716](https://dhis2.atlassian.net/browse/ANDROAPP-5716) Filters not responsive to rapid changes when there are many programs -* [ANDROAPP-5710](https://dhis2.atlassian.net/browse/ANDROAPP-5710) Keyboard not showing for certain fields -* [ANDROAPP-5704](https://dhis2.atlassian.net/browse/ANDROAPP-5704) Overdue date in patient line list follows inconsistent format -* [ANDROAPP-5700](https://dhis2.atlassian.net/browse/ANDROAPP-5700) User can select out-of-scope OUs on the enrollment form -* [ANDROAPP-5698](https://dhis2.atlassian.net/browse/ANDROAPP-5698) Incorrect list of points in a polygon -* [ANDROAPP-5663](https://dhis2.atlassian.net/browse/ANDROAPP-5663) Tei dashboard event list scrolling -* [ANDROAPP-5662](https://dhis2.atlassian.net/browse/ANDROAPP-5662) Search button is behind the nav bar -* [ANDROAPP-5630](https://dhis2.atlassian.net/browse/ANDROAPP-5630) Due date in Tracker program does not follow standard interval days -* [ANDROAPP-5606](https://dhis2.atlassian.net/browse/ANDROAPP-5606) Active filter counter mismatched with workinglist's filters -* [ANDROAPP-5604](https://dhis2.atlassian.net/browse/ANDROAPP-5604) App crashes when one attempts to synchronise TEI and events imported via QR code -* [ANDROAPP-5570](https://dhis2.atlassian.net/browse/ANDROAPP-5570) Changes to enrollment date not respected by program rules -* [ANDROAPP-5567](https://dhis2.atlassian.net/browse/ANDROAPP-5567) DHIS2-RTS Capture app limited to 60 TEIs -* [ANDROAPP-5484](https://dhis2.atlassian.net/browse/ANDROAPP-5484) Images block creating relationships (Capture Android) -* [ANDROAPP-5294](https://dhis2.atlassian.net/browse/ANDROAPP-5294) Filter by ACCESSIBLE org units -* [ANDROAPP-5261](https://dhis2.atlassian.net/browse/ANDROAPP-5261) The animation of the input bottom bar is not smooth. -* [ANDROAPP-5249](https://dhis2.atlassian.net/browse/ANDROAPP-5249) Resizing for all columns difficult use -* [ANDROAPP-5130](https://dhis2.atlassian.net/browse/ANDROAPP-5130) Follow-up clicks can be skipped by the app when entering data into tables. -* This patch release updates the [Android SDK](https://github.com/dhis2/dhis2-android-sdk) to version 1.9.1-20240109.100903-15. - -You can find in Jira details on the [bugs fixed](https://dhis2.atlassian.net/issues/?filter=10554) in this version. +### Bug -Remember to check the [documentation](https://www.dhis2.org/android-documentation) for detailed -information of the features included in the App and how to configure DHIS2 to use it. +[ANDROAPP-6194](https://dhis2.atlassian.net/browse/ANDROAPP-6194) Unable to search outside the program -Please create a [Jira](https://dhis2.atlassian.net) Issue if you find a bug or -you want to propose a new functionality. [Project: Android App for DHIS2 | Component: -AndroidApp]. -
\ No newline at end of file +[ANDROAPP-6195](https://dhis2.atlassian.net/browse/ANDROAPP-6195) Missing terms in transifex + +[ANDROAPP-6210](https://dhis2.atlassian.net/browse/ANDROAPP-6210) UninitializedPropertyAccessException on breaking the glass \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dfaa4d221b..51d068ff1f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +import com.android.build.api.variant.impl.VariantOutputImpl +import com.android.build.gradle.internal.scope.ProjectInfo.Companion.getBaseName import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.Date @@ -36,10 +38,10 @@ android { } signingConfigs { - create("release"){ + create("release") { keyAlias = System.getenv("SIGNING_KEY_ALIAS") keyPassword = System.getenv("SIGNING_KEY_PASSWORD") - System.getenv("SIGNING_KEYSTORE_PATH")?.let {path-> + System.getenv("SIGNING_KEYSTORE_PATH")?.let { path -> storeFile = file(path) } storePassword = System.getenv("SIGNING_STORE_PASSWORD") @@ -227,6 +229,25 @@ android { abortOnError = false checkReleaseBuilds = false } + + androidComponents { + onVariants { variant -> + val buildType = variant.buildType + val flavorName = variant.flavorName + variant.outputs.forEach { output -> + if (output is VariantOutputImpl) { + val suffix = when { + buildType == "debug" && flavorName == "dhis" -> "-training" + buildType == "release" && flavorName == "dhisPlayServices" -> "-googlePlay" + else -> "" + } + + output.outputFileName = "dhis2-v${libs.versions.vName.get()}$suffix.apk" + } + } + + } + } } dependencies { diff --git a/app/src/androidTest/java/org/dhis2/common/mockwebserver/MockWebServerRobot.kt b/app/src/androidTest/java/org/dhis2/common/mockwebserver/MockWebServerRobot.kt index dc76fafbb7..eb2c53c7af 100644 --- a/app/src/androidTest/java/org/dhis2/common/mockwebserver/MockWebServerRobot.kt +++ b/app/src/androidTest/java/org/dhis2/common/mockwebserver/MockWebServerRobot.kt @@ -15,4 +15,11 @@ class MockWebServerRobot(private val dhis2MockServer: Dhis2MockServer) { fun addResponse(method: String, path: String, sdkResource: String, responseCode: Int = 200) { dhis2MockServer.addResponse(method, path, sdkResource, responseCode) } + + companion object { + const val API_OLD_TRACKED_ENTITY_PATH = "/api/trackedEntityInstances/query?.*" + const val API_OLD_TRACKED_ENTITY_RESPONSE = + "mocks/teilist/old_tracked_entity_empty_response.json" + + } } diff --git a/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt b/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt index 5f17dd4292..f94746d2c9 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/BaseTest.kt @@ -169,12 +169,13 @@ open class BaseTest { } private fun disableComposeForms() { + preferencesRobot.saveValue(SET_FROM_TESTING, true) preferencesRobot.saveValue(Feature.COMPOSE_FORMS.name, false) } fun enableComposeForms() { - preferencesRobot.saveValue("SET_FROM_DEVELOPMENT", true) + preferencesRobot.saveValue(SET_FROM_TESTING, true) preferencesRobot.saveValue(Feature.COMPOSE_FORMS.name, true) } @@ -184,5 +185,6 @@ open class BaseTest { val disableAnimationsTestRule = DisableAnimations() const val MOCK_SERVER_URL = "http://127.0.0.1:8080" const val API = "api" + const val SET_FROM_TESTING = "SET_FROM_TESTING" } } diff --git a/app/src/androidTest/java/org/dhis2/usescases/flow/searchFlow/SearchFlowTest.kt b/app/src/androidTest/java/org/dhis2/usescases/flow/searchFlow/SearchFlowTest.kt index 2807eded53..cf42db37d6 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/flow/searchFlow/SearchFlowTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/flow/searchFlow/SearchFlowTest.kt @@ -7,11 +7,14 @@ import androidx.compose.ui.text.intl.Locale import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule import org.dhis2.R +import org.dhis2.common.mockwebserver.MockWebServerRobot.Companion.API_OLD_TRACKED_ENTITY_PATH +import org.dhis2.common.mockwebserver.MockWebServerRobot.Companion.API_OLD_TRACKED_ENTITY_RESPONSE import org.dhis2.usescases.BaseTest import org.dhis2.usescases.flow.teiFlow.entity.DateRegistrationUIModel import org.dhis2.usescases.flow.teiFlow.entity.RegisterTEIUIModel import org.dhis2.usescases.flow.teiFlow.teiFlowRobot import org.dhis2.usescases.searchTrackEntity.SearchTEActivity +import org.hisp.dhis.android.core.mockwebserver.ResponseController import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -28,8 +31,19 @@ class SearchFlowTest : BaseTest() { private val dateRegistration = createFirstSpecificDate() private val dateEnrollment = createEnrollmentDate() + override fun setUp() { + super.setUp() + setupMockServer() + } + @Test fun shouldCreateTEIAndFilterByEnrollment() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + setDatePicker() val registerTEIDetails = createRegisterTEI() val enrollmentStatus = context.getString(R.string.filters_title_enrollment_status) diff --git a/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt b/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt index 4f350bb9b5..29bb15176f 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt @@ -1,6 +1,8 @@ package org.dhis2.usescases.flow.syncFlow import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.lifecycle.MutableLiveData import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -51,7 +53,7 @@ class SyncFlowTest : BaseTest() { ApplicationProvider.getApplicationContext().mutableWorkInfoStatuses } - @Ignore("failing by a bug - ANDROAPP-6154") + @Ignore("Flaky test, will be fixed in next release") @Test fun shouldShowErrorWhenTEISyncFails() { mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) @@ -81,8 +83,12 @@ class SyncFlowTest : BaseTest() { clickOnCompleteButton() } + teiDashboardRobot(composeTestRule) { + composeTestRule.onNodeWithText("Sync").performClick() + } + syncFlowRobot(composeTestRule) { - clickOnEventToSync() + waitToDebounce(500) clickOnSyncButton() workInfoStatusLiveData.postValue(arrayListOf(mockedGranularWorkInfo(WorkInfo.State.RUNNING))) workInfoStatusLiveData.postValue(arrayListOf(mockedGranularWorkInfo(WorkInfo.State.FAILED))) @@ -91,6 +97,7 @@ class SyncFlowTest : BaseTest() { cleanLocalDatabase() } + @Ignore("Flaky test, will be addressed in issue #ANDROAPP-6155") @Test fun shouldSuccessfullySyncSavedEvent() { mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) @@ -117,6 +124,7 @@ class SyncFlowTest : BaseTest() { } @Test + @Ignore("Flaky test, will be addressed in issue #ANDROAPP-6139") fun shouldShowErrorWhenSyncEventFails() { mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) @@ -180,6 +188,7 @@ class SyncFlowTest : BaseTest() { cleanLocalDatabase() } + @Ignore("Flaky test, will be addressed in next release") @Test fun shouldShowErrorWhenSyncDataSetFails() { prepareFacilityDataSetIntentAndLaunchActivity(ruleDataSet) diff --git a/app/src/androidTest/java/org/dhis2/usescases/flow/teiFlow/TeiFlowTest.kt b/app/src/androidTest/java/org/dhis2/usescases/flow/teiFlow/TeiFlowTest.kt index 5b8cf533e8..7fad72127c 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/flow/teiFlow/TeiFlowTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/flow/teiFlow/TeiFlowTest.kt @@ -4,12 +4,15 @@ import android.content.Intent import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule +import org.dhis2.common.mockwebserver.MockWebServerRobot.Companion.API_OLD_TRACKED_ENTITY_PATH +import org.dhis2.common.mockwebserver.MockWebServerRobot.Companion.API_OLD_TRACKED_ENTITY_RESPONSE import org.dhis2.usescases.BaseTest import org.dhis2.usescases.flow.teiFlow.entity.DateRegistrationUIModel import org.dhis2.usescases.flow.teiFlow.entity.EnrollmentListUIModel import org.dhis2.usescases.flow.teiFlow.entity.RegisterTEIUIModel import org.dhis2.usescases.searchTrackEntity.SearchTEActivity import org.dhis2.usescases.teiDashboard.TeiDashboardMobileActivity +import org.hisp.dhis.android.core.mockwebserver.ResponseController import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -33,8 +36,19 @@ class TeiFlowTest : BaseTest() { private val dateEnrollment = createEnrollmentDate() private val currentDate = getCurrentDate() + override fun setUp() { + super.setUp() + setupMockServer() + } + @Test fun shouldEnrollToSameProgramAfterClosingIt() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val totalEventsPerEnrollment = 3 val enrollmentListDetails = createEnrollmentList() val registerTeiDetails = createRegisterTEI() @@ -105,6 +119,5 @@ class TeiFlowTest : BaseTest() { const val DATE_FORMAT = "dd/M/yyyy" const val DATE_PICKER_FORMAT = ", d MMMM" - } } \ No newline at end of file diff --git a/app/src/androidTest/java/org/dhis2/usescases/searchte/SearchTETest.kt b/app/src/androidTest/java/org/dhis2/usescases/searchte/SearchTETest.kt index eadaad1c7b..0ee0ad8421 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/searchte/SearchTETest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/searchte/SearchTETest.kt @@ -1,9 +1,7 @@ package org.dhis2.usescases.searchte -import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.intl.Locale @@ -19,6 +17,8 @@ import dispatch.android.espresso.IdlingDispatcherProviderRule import org.dhis2.R import org.dhis2.bindings.app import org.dhis2.common.idlingresources.MapIdlingResource +import org.dhis2.common.mockwebserver.MockWebServerRobot.Companion.API_OLD_TRACKED_ENTITY_PATH +import org.dhis2.common.mockwebserver.MockWebServerRobot.Companion.API_OLD_TRACKED_ENTITY_RESPONSE import org.dhis2.commons.date.DateUtils.SIMPLE_DATE_FORMAT import org.dhis2.lazyActivityScenarioRule import org.dhis2.ui.dialogs.bottomsheet.SECONDARY_BUTTON_TAG @@ -32,6 +32,7 @@ import org.dhis2.usescases.searchte.entity.DisplayListFieldsUIModel import org.dhis2.usescases.searchte.robot.filterRobot import org.dhis2.usescases.searchte.robot.searchTeiRobot import org.dhis2.usescases.teidashboard.robot.teiDashboardRobot +import org.hisp.dhis.android.core.mockwebserver.ResponseController import org.junit.After import org.junit.Ignore import org.junit.Rule @@ -61,8 +62,19 @@ class SearchTETest : BaseTest() { @get:Rule val composeTestRule = createComposeRule() + override fun setUp() { + super.setUp() + setupMockServer() + } + @Test fun shouldSuccessfullySearchByName() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val firstName = "Tim" val lastName = "Johnson" @@ -82,6 +94,12 @@ class SearchTETest : BaseTest() { @Test fun shouldShowErrorWhenCanNotFindSearchResult() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val firstName = "asdssds" prepareTestProgramRulesProgrammeIntentAndLaunchActivity(rule) @@ -97,6 +115,12 @@ class SearchTETest : BaseTest() { @Test fun shouldSuccessfullySearchUsingMoreThanOneField() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val firstName = "Anna" val lastName = "Jones" @@ -133,6 +157,12 @@ class SearchTETest : BaseTest() { @Test fun shouldCheckDisplayInList() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val displayInListData = createDisplayListFields() prepareTestAdultWomanProgrammeIntentAndLaunchActivity(rule) @@ -286,6 +316,12 @@ class SearchTETest : BaseTest() { @Test fun shouldSuccessfullyFilterBySync() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val teiName = "Frank" val teiLastName = "Fjordsen" val syncFilter = context.getString(R.string.action_sync) @@ -321,6 +357,12 @@ class SearchTETest : BaseTest() { @Test fun shouldSuccessfullySearchAndFilter() { + mockWebServerRobot.addResponse( + ResponseController.GET, + API_OLD_TRACKED_ENTITY_PATH, + API_OLD_TRACKED_ENTITY_RESPONSE, + ) + val name = "Anna" val lastName = "Jones" val enrollmentStatus = context.getString(R.string.filters_title_enrollment_status) diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt index 4557f174bf..b6dc9db96f 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt @@ -125,6 +125,7 @@ class TeiDashboardTest : BaseTest() { } } + @Ignore("next button is sometimes not reached. Review feature.") @Test fun shouldShowQRWhenClickOnShare() { prepareTeiCompletedProgrammeAndLaunchActivity(rule) diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt index dc50165dbe..8a6c68c2b8 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/TeiDashboardRobot.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition @@ -395,7 +397,15 @@ class TeiDashboardRobot(val composeTestRule: ComposeTestRule) : BaseRobot() { } fun clickOnTimelineEvents() { - onView(withText(R.string.show_events_timeline)).perform(click()) + try { + onView(withText(R.string.show_events_timeline)).perform(click()) + }catch (e: NoMatchingViewException){ + checkIfGroupedEventsIsVisible() + } + } + + private fun checkIfGroupedEventsIsVisible(){ + onView(withText(R.string.group_events_by_stage)).check(matches(isDisplayed())) } fun checkEventWasScheduled(eventName: String, position: Int) { diff --git a/app/src/dhisPlayServices/java/org/dhis2/utils/granularsync/GranularSyncModule.kt b/app/src/dhisPlayServices/java/org/dhis2/utils/granularsync/GranularSyncModule.kt index 44ec6a4b02..d40e2a78ec 100644 --- a/app/src/dhisPlayServices/java/org/dhis2/utils/granularsync/GranularSyncModule.kt +++ b/app/src/dhisPlayServices/java/org/dhis2/utils/granularsync/GranularSyncModule.kt @@ -73,6 +73,15 @@ class GranularSyncModule( ) } + @Provides + fun provideDispatchers() = object : DispatcherProvider { + override fun io() = Dispatchers.IO + + override fun computation() = Dispatchers.Default + + override fun ui() = Dispatchers.Main + } + @Provides fun granularSyncRepository( d2: D2, @@ -87,6 +96,7 @@ class GranularSyncModule( dhisProgramUtils, periodUtils, resourceManager, + provideDispatchers(), ) @Provides diff --git a/app/src/dhisUITesting/assets/mocks/teilist/old_tracked_entity_empty_response.json b/app/src/dhisUITesting/assets/mocks/teilist/old_tracked_entity_empty_response.json new file mode 100644 index 0000000000..e5104f914d --- /dev/null +++ b/app/src/dhisUITesting/assets/mocks/teilist/old_tracked_entity_empty_response.json @@ -0,0 +1,62 @@ +{ + "headers": [ + { + "name": "instance", + "column": "Instance", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "created", + "column": "Created", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "lastupdated", + "column": "Last updated", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "ou", + "column": "Organisation unit", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "ouname", + "column": "Organisation unit name", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "te", + "column": "Tracked entity type", + "type": "java.lang.String", + "hidden": false, + "meta": false + }, + { + "name": "inactive", + "column": "Inactive", + "type": "java.lang.String", + "hidden": false, + "meta": false + } + ], + "metaData": { + "names": { + "nEenWmSyUEp": "Person" + } + }, + "width": 9, + "height": 0, + "rows": [ + ] +} \ No newline at end of file diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt index d853902964..62a220e632 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt @@ -11,8 +11,6 @@ import android.widget.PopupMenu import androidx.databinding.DataBindingUtil import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModelProvider -import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar @@ -24,7 +22,6 @@ import org.dhis2.commons.dialogs.CustomDialog import org.dhis2.commons.dialogs.DialogClickListener import org.dhis2.commons.popupmenu.AppMenuHelper import org.dhis2.commons.resources.ResourceManager -import org.dhis2.commons.sync.OnDismissListener import org.dhis2.commons.sync.SyncContext import org.dhis2.databinding.ActivityEventCaptureBinding import org.dhis2.form.model.EventMode @@ -34,7 +31,6 @@ import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialog import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialogUiModel import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle.DiscardButton import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle.MainButton -import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventCaptureFragment.EventCaptureFormFragment import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventCaptureFragment.OnEditionListener import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCompletionDialog import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.EventDetailsComponent @@ -42,10 +38,7 @@ import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.Even import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.EventDetailsModule import org.dhis2.usescases.eventsWithoutRegistration.eventInitial.EventInitialActivity import org.dhis2.usescases.general.ActivityGlobalAbstract -import org.dhis2.usescases.teiDashboard.DashboardViewModel import org.dhis2.usescases.teiDashboard.dashboardfragments.relationships.MapButtonObservable -import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataActivityContract -import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataFragment.Companion.newInstance import org.dhis2.utils.analytics.CLICK import org.dhis2.utils.analytics.DELETE_EVENT import org.dhis2.utils.analytics.SHOW_HELP @@ -55,16 +48,13 @@ import org.dhis2.utils.customviews.navigationbar.NavigationPageConfigurator import org.dhis2.utils.granularsync.OPEN_ERROR_LOCATION import org.dhis2.utils.granularsync.SyncStatusDialog import org.dhis2.utils.granularsync.shouldLaunchSyncDialog -import org.dhis2.utils.isLandscape -import org.dhis2.utils.isPortrait import javax.inject.Inject class EventCaptureActivity : ActivityGlobalAbstract(), EventCaptureContract.View, MapButtonObservable, - EventDetailsComponentProvider, - TEIDataActivityContract { + EventDetailsComponentProvider { private lateinit var binding: ActivityEventCaptureBinding @Inject @@ -87,53 +77,37 @@ class EventCaptureActivity : var eventCaptureComponent: EventCaptureComponent? = null var programUid: String? = null var eventUid: String? = null - private var teiUid: String? = null - private var enrollmentUid: String? = null private val relationshipMapButton: LiveData = MutableLiveData(false) private var onEditionListener: OnEditionListener? = null private var adapter: EventCapturePagerAdapter? = null - private var eventViewPager: ViewPager2? = null - private var dashboardViewModel: DashboardViewModel? = null override fun onCreate(savedInstanceState: Bundle?) { eventUid = intent.getStringExtra(Constants.EVENT_UID) - programUid = intent.getStringExtra(Constants.PROGRAM_UID) - setUpEventCaptureComponent(eventUid) - teiUid = presenter.getTeiUid() - enrollmentUid = presenter.getEnrollmentUid() + eventCaptureComponent = this.app().userComponent()!!.plus( + EventCaptureModule( + this, + eventUid, + ), + ) + eventCaptureComponent!!.inject(this) themeManager!!.setProgramTheme(intent.getStringExtra(Constants.PROGRAM_UID)!!) super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_event_capture) binding.presenter = presenter - eventViewPager = when { - this.isLandscape() -> binding.eventViewLandPager - else -> binding.eventViewPager - } eventMode = intent.getSerializableExtra(Constants.EVENT_MODE) as EventMode? setUpViewPagerAdapter() setUpNavigationBar() - setUpEventCaptureFormLandscape(eventUid ?: "") - if (this.isLandscape() && areTeiUidAndEnrollmentUidNotNull()) { - val viewModelFactory = this.app().dashboardComponent()?.dashboardViewModelFactory() - - viewModelFactory?.let { - dashboardViewModel = - ViewModelProvider(this, viewModelFactory)[DashboardViewModel::class.java] - supportFragmentManager.beginTransaction().replace(R.id.tei_column, newInstance(programUid, teiUid, enrollmentUid)).commit() - dashboardViewModel?.updateSelectedEventUid(eventUid) - } - } showProgress() presenter.initNoteCounter() presenter.init() - binding.syncButton.setOnClickListener { showSyncDialog(EVENT_SYNC) } + binding.syncButton.setOnClickListener { showSyncDialog() } if (intent.shouldLaunchSyncDialog()) { - showSyncDialog(EVENT_SYNC) + showSyncDialog() } } private fun setUpViewPagerAdapter() { - eventViewPager?.isUserInputEnabled = false + binding.eventViewPager?.isUserInputEnabled = false adapter = EventCapturePagerAdapter( this, intent.getStringExtra(Constants.PROGRAM_UID), @@ -143,8 +117,8 @@ class EventCaptureActivity : intent.getBooleanExtra(OPEN_ERROR_LOCATION, false), eventMode, ) - eventViewPager?.adapter = adapter - eventViewPager?.registerOnPageChangeCallback(object : OnPageChangeCallback() { + binding.eventViewPager?.adapter = adapter + binding.eventViewPager?.registerOnPageChangeCallback(object : OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) if (position == 0 && eventMode !== EventMode.NEW) { @@ -161,56 +135,12 @@ class EventCaptureActivity : private fun setUpNavigationBar() { binding.navigationBar.pageConfiguration(pageConfigurator!!) - eventViewPager?.registerOnPageChangeCallback( - object : OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - binding.navigationBar.selectItemAt(position) - } - }, - ) binding.navigationBar.setOnItemSelectedListener { item: MenuItem -> - eventViewPager?.currentItem = adapter!!.getDynamicTabIndex(item.itemId) + binding.eventViewPager?.currentItem = adapter!!.getDynamicTabIndex(item.itemId) true } } - private fun setUpEventCaptureFormLandscape(eventUid: String) { - if (this.isLandscape()) { - supportFragmentManager.beginTransaction() - .replace(R.id.event_form, EventCaptureFormFragment.newInstance(eventUid, false, eventMode)) - .commit() - } - } - - private fun setUpEventCaptureComponent(eventUid: String?) { - eventCaptureComponent = app().userComponent()!!.plus( - EventCaptureModule( - this, - eventUid, - this.isPortrait(), - ), - ) - eventCaptureComponent!!.inject(this) - } - - private fun updateLandscapeViewsOnEventChange(newEventUid: String) { - if (newEventUid != this.eventUid) { - this.eventUid = newEventUid - setUpEventCaptureComponent(newEventUid) - setUpViewPagerAdapter() - setUpNavigationBar() - setUpEventCaptureFormLandscape(newEventUid) - showProgress() - presenter!!.initNoteCounter() - presenter!!.init() - } - } - - private fun areTeiUidAndEnrollmentUidNotNull(): Boolean { - return teiUid != null && enrollmentUid != null - } - fun openDetails() { binding.navigationBar.selectItemAt(0) } @@ -227,9 +157,6 @@ class EventCaptureActivity : override fun onResume() { super.onResume() presenter.refreshTabCounters() - with(dashboardViewModel) { - this?.selectedEventUid()?.observe(this@EventCaptureActivity, ::updateLandscapeViewsOnEventChange) - } } override fun onDestroy() { @@ -282,11 +209,7 @@ class EventCaptureActivity : } private fun isFormScreen(): Boolean { - return if (this.isPortrait()) { - adapter?.isFormScreenShown(binding.eventViewPager?.currentItem) == true - } else { - true - } + return adapter?.isFormScreenShown(binding.eventViewPager?.currentItem) == true } override fun updatePercentage(primaryValue: Float) { @@ -297,28 +220,25 @@ class EventCaptureActivity : } override fun showCompleteActions(eventCompletionDialog: EventCompletionDialog) { - val dialog = BottomSheetDialog( - bottomSheetDialogUiModel = eventCompletionDialog.bottomSheetDialogUiModel, - onMainButtonClicked = { - setAction(eventCompletionDialog.mainButtonAction) - }, - onSecondaryButtonClicked = { - eventCompletionDialog.secondaryButtonAction?.let { setAction(it) } - }, - content = if (eventCompletionDialog.fieldsWithIssues.isNotEmpty()) { - { bottomSheetDialog -> - ErrorFieldList(eventCompletionDialog.fieldsWithIssues) { - bottomSheetDialog.dismiss() - } - } - } else { - null - }, - ) if (binding.navigationBar.selectedItemId == R.id.navigation_data_entry) { - dialog.show(supportFragmentManager, SHOW_OPTIONS) - } - if (this.isLandscape()) { + val dialog = BottomSheetDialog( + bottomSheetDialogUiModel = eventCompletionDialog.bottomSheetDialogUiModel, + onMainButtonClicked = { + setAction(eventCompletionDialog.mainButtonAction) + }, + onSecondaryButtonClicked = { + eventCompletionDialog.secondaryButtonAction?.let { setAction(it) } + }, + content = if (eventCompletionDialog.fieldsWithIssues.isNotEmpty()) { + { bottomSheetDialog -> + ErrorFieldList(eventCompletionDialog.fieldsWithIssues) { + bottomSheetDialog.dismiss() + } + } + } else { + null + }, + ) dialog.show(supportFragmentManager, SHOW_OPTIONS) } } @@ -513,66 +433,23 @@ class EventCaptureActivity : return eventCaptureComponent!!.plus(module) } - private fun showSyncDialog(syncType: String) { - val syncContext = when (syncType) { - TEI_SYNC -> enrollmentUid?.let { SyncContext.Enrollment(it) } - EVENT_SYNC -> SyncContext.Event(eventUid!!) - else -> null - } - - syncContext?.let { - SyncStatusDialog.Builder() - .withContext(this) - .withSyncContext(it) - .onDismissListener(object : OnDismissListener { - override fun onDismiss(hasChanged: Boolean) { - if (hasChanged && syncType == TEI_SYNC) { - dashboardViewModel?.updateDashboard() - } - } - }) - .onNoConnectionListener { - val contextView = findViewById(R.id.navigationBar) - Snackbar.make( - contextView, - R.string.sync_offline_check_connection, - Snackbar.LENGTH_SHORT, - ).show() - } - .show(syncType) - } - } - - override fun openSyncDialog() { - showSyncDialog(TEI_SYNC) - } - - override fun finishActivity() { - finish() - } - - override fun restoreAdapter(programUid: String, teiUid: String, enrollmentUid: String) { - // we do not restore adapter in events - } - - override fun executeOnUIThread() { - activity.runOnUiThread { - showDescription(getString(R.string.error_applying_rule_effects)) - } - } - - override fun getContext(): Context { - return this - } - - override fun activityTeiUid(): String? { - return teiUid + private fun showSyncDialog() { + SyncStatusDialog.Builder() + .withContext(this) + .withSyncContext(SyncContext.Event(eventUid!!)) + .onNoConnectionListener { + val contextView = findViewById(R.id.navigationBar) + Snackbar.make( + contextView, + R.string.sync_offline_check_connection, + Snackbar.LENGTH_SHORT, + ).show() + } + .show("EVENT_SYNC") } companion object { private const val SHOW_OPTIONS = "SHOW_OPTIONS" - private const val TEI_SYNC = "SYNC_TEI" - private const val EVENT_SYNC = "EVENT_SYNC" @JvmStatic fun getActivityBundle(eventUid: String, programUid: String, eventMode: EventMode): Bundle { diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java index cfc80799f2..6cf913c99f 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java @@ -42,12 +42,9 @@ public class EventCaptureModule { private final String eventUid; private final EventCaptureContract.View view; - private final boolean isPortrait; - - public EventCaptureModule(EventCaptureContract.View view, String eventUid, boolean isPortrait) { + public EventCaptureModule(EventCaptureContract.View view, String eventUid) { this.view = view; this.eventUid = eventUid; - this.isPortrait = isPortrait; } @Provides @@ -141,7 +138,7 @@ FlowableProcessor getProcessor() { NavigationPageConfigurator pageConfigurator( EventCaptureContract.EventCaptureRepository repository ) { - return new EventPageConfigurator(repository, isPortrait); + return new EventPageConfigurator(repository); } @Provides @@ -159,4 +156,4 @@ EventCaptureResourcesProvider provideEventCaptureResourcesProvider( ) { return new EventCaptureResourcesProvider(resourceManager); } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt index 2f4a9dcdc8..ed3d15822f 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt @@ -4,14 +4,13 @@ import org.dhis2.utils.customviews.navigationbar.NavigationPageConfigurator class EventPageConfigurator( private val eventCaptureRepository: EventCaptureContract.EventCaptureRepository, - val isPortrait: Boolean, ) : NavigationPageConfigurator { override fun displayDetails(): Boolean { return true } override fun displayDataEntry(): Boolean { - return isPortrait + return true } override fun displayAnalytics(): Boolean { diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepository.java b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepository.java index ae624c091c..8691eea1fd 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepository.java +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepository.java @@ -6,6 +6,7 @@ import org.dhis2.commons.data.EventViewModel; import org.dhis2.commons.data.SearchTeiModel; import org.dhis2.commons.data.tuples.Pair; +import org.dhis2.commons.filters.FilterManager; import org.dhis2.commons.filters.sorting.SortingItem; import org.dhis2.data.search.SearchParametersModel; import org.hisp.dhis.android.core.arch.call.D2Progress; @@ -17,14 +18,16 @@ import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItem; import org.jetbrains.annotations.NotNull; -import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import io.reactivex.Flowable; import io.reactivex.Observable; +import kotlin.Deprecated; +@Deprecated(message = "Use SearchRepositoryKt instead") public interface SearchRepository { Observable> programsWithRegistration(String programTypeId); @@ -82,4 +85,10 @@ public interface SearchRepository { List trackedEntityTypeFields(); boolean filtersApplyOnGlobalSearch(); + + @NotNull HashSet getFetchedTeiUIDs(); + + SearchParametersModel getSavedSearchParameters(); + + FilterManager getSavedFilters(); } diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java index ff0f8f0769..070bb93b60 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java @@ -24,8 +24,8 @@ import org.dhis2.commons.filters.sorting.SortingItem; import org.dhis2.commons.network.NetworkUtils; import org.dhis2.commons.reporting.CrashReportController; -import org.dhis2.commons.resources.MetadataIconProvider; import org.dhis2.commons.resources.DhisPeriodUtils; +import org.dhis2.commons.resources.MetadataIconProvider; import org.dhis2.commons.resources.ResourceManager; import org.dhis2.data.dhislogic.DhisEnrollmentUtils; import org.dhis2.data.forms.dataentry.SearchTEIRepository; @@ -43,7 +43,6 @@ import org.dhis2.utils.ValueUtils; import org.hisp.dhis.android.core.D2; import org.hisp.dhis.android.core.arch.call.D2Progress; -import org.hisp.dhis.android.core.arch.helpers.Result; import org.hisp.dhis.android.core.arch.helpers.UidsHelper; import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope; import org.hisp.dhis.android.core.common.FeatureType; @@ -57,7 +56,6 @@ import org.hisp.dhis.android.core.event.Event; import org.hisp.dhis.android.core.event.EventCollectionRepository; import org.hisp.dhis.android.core.event.EventStatus; -import org.hisp.dhis.android.core.maintenance.D2Error; import org.hisp.dhis.android.core.organisationunit.OrganisationUnit; import org.hisp.dhis.android.core.program.Program; import org.hisp.dhis.android.core.program.ProgramStage; @@ -79,7 +77,6 @@ import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItem; import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItemAttribute; import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItemHelper; -import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -606,6 +603,21 @@ public boolean filtersApplyOnGlobalSearch() { !FilterManager.getInstance().getStateFilters().isEmpty(); } + @Override + public @NotNull HashSet getFetchedTeiUIDs() { + return fetchedTeiUids; + } + + @Override + public SearchParametersModel getSavedSearchParameters() { + return savedSearchParameters; + } + + @Override + public FilterManager getSavedFilters() { + return savedFilters; + } + @Override public Observable getTrackedEntityType(String trackedEntityUid) { return d2.trackedEntityModule().trackedEntityTypes().uid(trackedEntityUid).get().toObservable(); @@ -697,17 +709,6 @@ public TeiDownloadResult download(String teiUid, @Nullable String enrollmentUid, return teiDownloader.download(teiUid, enrollmentUid, reason); } - public SearchTeiModel transformResult(Result result, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem) { - try { - return transform(result.getOrThrow(), selectedProgram, offlineOnly, sortingItem); - } catch (Exception e) { - SearchTeiModel errorModel = new SearchTeiModel(); - errorModel.onlineErrorMessage = resources.parseD2Error(e); - errorModel.onlineErrorCode = ((D2Error) e).errorCode(); - return errorModel; - } - } - @Override public SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable Program selectedProgram, boolean offlineOnly, SortingItem sortingItem) { if (!fetchedTeiUids.contains(searchItem.uid())) { diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt index 20e0b4b7c5..dc1a1470c7 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImplKt.kt @@ -30,14 +30,8 @@ class SearchRepositoryImplKt( private val metadataIconProvider: MetadataIconProvider, ) : SearchRepositoryKt { - private lateinit var savedSearchParamenters: SearchParametersModel - - private lateinit var savedFilters: FilterManager - private lateinit var trackedEntityInstanceQuery: TrackedEntitySearchCollectionRepository - private val fetchedTeiUids = HashSet() - override fun searchTrackedEntities( searchParametersModel: SearchParametersModel, isOnline: Boolean, @@ -51,26 +45,23 @@ class SearchRepositoryImplKt( isOnline: Boolean, ): TrackedEntitySearchCollectionRepository { var allowCache = false - savedSearchParamenters = searchParametersModel.copy() - savedFilters = FilterManager.getInstance().copy() - if (searchParametersModel != savedSearchParamenters || !FilterManager.getInstance() - .sameFilters(savedFilters) + if (searchParametersModel != searchRepositoryJava.savedSearchParameters || !FilterManager.getInstance() + .sameFilters(searchRepositoryJava.savedFilters) ) { trackedEntityInstanceQuery = searchRepositoryJava.getFilteredRepository(searchParametersModel) } else { - trackedEntityInstanceQuery = - searchRepositoryJava.getFilteredRepository(searchParametersModel) + searchRepositoryJava.getFilteredRepository(searchParametersModel) allowCache = true } - if (fetchedTeiUids.isNotEmpty() && searchParametersModel.selectedProgram == null) { + if (searchRepositoryJava.fetchedTeiUIDs.isNotEmpty() && searchParametersModel.selectedProgram == null) { trackedEntityInstanceQuery = - trackedEntityInstanceQuery.excludeUids().`in`(fetchedTeiUids.toList()) + trackedEntityInstanceQuery.excludeUids().`in`(searchRepositoryJava.fetchedTeiUIDs.toList()) } - val pagerFlow = if (isOnline && FilterManager.getInstance().stateFilters.isNotEmpty()) { + val pagerFlow = if (isOnline && FilterManager.getInstance().stateFilters.isEmpty()) { trackedEntityInstanceQuery.allowOnlineCache().eq(allowCache).offlineFirst() } else { trackedEntityInstanceQuery.allowOnlineCache().eq(allowCache).offlineOnly() @@ -131,7 +122,8 @@ class SearchRepositoryImplKt( options.associate { it.uid() to metadataIconProvider( it.style(), - program?.style()?.color()?.toColor() ?: SurfaceColor.Primary, + program?.style()?.color()?.toColor() + ?: SurfaceColor.Primary, ) } @@ -177,7 +169,12 @@ class SearchRepositoryImplKt( .blockingGet() val metadataIconMap = - options.associate { it.uid() to metadataIconProvider(it.style(), SurfaceColor.Primary) } + options.associate { + it.uid() to metadataIconProvider( + it.style(), + SurfaceColor.Primary, + ) + } OptionSetConfiguration.OptionConfigData( options = options, diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEIViewModel.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEIViewModel.kt index 38441502cd..78b7e72282 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEIViewModel.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEIViewModel.kt @@ -373,7 +373,7 @@ class SearchTEIViewModel( suspend fun fetchGlobalResults() = withContext(dispatchers.io()) { val searchParametersModel = SearchParametersModel( - selectedProgram = searchRepository.getProgram(initialProgramUid), + selectedProgram = null, queryData = queryData, ) val getPagingData = searchRepositoryKt.searchTrackedEntities( @@ -927,6 +927,7 @@ class SearchTEIViewModel( ValueType.ORGANISATION_UNIT, ValueType.MULTI_TEXT -> { map[item.uid] = (item.displayName ?: "") } + ValueType.DATE, ValueType.AGE -> { item.value?.let { if (it.isNotEmpty()) { @@ -941,6 +942,7 @@ class SearchTEIViewModel( } } } + ValueType.DATETIME -> { item.value?.let { if (it.isNotEmpty()) { @@ -955,9 +957,11 @@ class SearchTEIViewModel( } } } + ValueType.BOOLEAN -> { map[item.uid] = "${item.label}: ${item.value}" } + ValueType.TRUE_ONLY -> { item.value?.let { if (it == "true") { @@ -965,9 +969,11 @@ class SearchTEIViewModel( } } } + ValueType.PERCENTAGE -> { map[item.uid] = "${item.value}%" } + else -> { map[item.uid] = (item.value ?: "") } diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt index a468985923..7523decc33 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt @@ -27,8 +27,6 @@ class DashboardViewModel( private val eventUid = MutableLiveData() - private val selectedEventUid = MutableLiveData() - val showStatusErrorMessages = MutableLiveData(StatusChangeResultCode.CHANGED) private var _showFollowUpBar = MutableStateFlow(false) @@ -171,14 +169,4 @@ class DashboardViewModel( } } } - - fun selectedEventUid(): LiveData { - return selectedEventUid - } - - fun updateSelectedEventUid(uid: String?) { - if (selectedEventUid.value != uid) { - this.selectedEventUid.value = uid - } - } } diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt index c16f884432..42573604ac 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt @@ -461,10 +461,10 @@ class TeiDashboardMobileActivity : if (this.isLandscape()) { if (binding.teiTablePager?.adapter == null) { setViewpagerAdapter() + supportFragmentManager.beginTransaction() + .replace(R.id.tei_main_view, newInstance(programUid, teiUid, enrollmentUid)) + .commitAllowingStateLoss() } - supportFragmentManager.beginTransaction() - .replace(R.id.tei_main_view, newInstance(programUid, teiUid, enrollmentUid)) - .commitAllowingStateLoss() } else { if (binding.teiPager?.adapter == null) { setViewpagerAdapter() diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt index b817c42e0d..102d1c164b 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt @@ -96,8 +96,9 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { private var programStageFromEvent: ProgramStage? = null private var eventCatComboOptionSelector: EventCatComboOptionSelector? = null private val dashboardViewModel: DashboardViewModel by activityViewModels() - private val dashboardActivity: TEIDataActivityContract by lazy { context as TEIDataActivityContract } + private val dashboardActivity: TeiDashboardMobileActivity by lazy { context as TeiDashboardMobileActivity } + private var showAllEnrollment = false private var programUid: String? = null override fun onAttach(context: Context) { @@ -135,6 +136,7 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { eventUid().observe(viewLifecycleOwner, ::displayGenerateEvent) noEnrollmentSelected.observe(viewLifecycleOwner) { noEnrollmentSelected -> if (noEnrollmentSelected) { + showAllEnrollment = true showLegacyCard(dashboardModel.value as DashboardTEIModel) } else { showDetailCard() @@ -219,8 +221,8 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { programsCallback = { startActivity( TeiDashboardMobileActivity.intent( - dashboardActivity.getContext(), - dashboardActivity.activityTeiUid(), + dashboardActivity.context, + dashboardActivity.teiUid, null, null, ), @@ -291,6 +293,9 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { override fun onResume() { super.onResume() presenter.init() + if (!showAllEnrollment) { + dashboardViewModel.updateDashboard() + } } override fun onPause() { @@ -329,7 +334,6 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { currentProgram, colorUtils, cardMapper, - initialSelectedEventUid = dashboardViewModel.selectedEventUid().value, ) binding.teiRecycler.adapter = eventAdapter } @@ -449,8 +453,15 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { } override fun restoreAdapter(programUid: String, teiUid: String, enrollmentUid: String) { - dashboardActivity.restoreAdapter(programUid, teiUid, enrollmentUid) - dashboardActivity.finishActivity() + dashboardActivity.startActivity( + TeiDashboardMobileActivity.intent( + activity, + teiUid, + programUid, + enrollmentUid, + ), + ) + dashboardActivity.finish() } override fun openEventDetails(intent: Intent, options: ActivityOptionsCompat) = @@ -463,17 +474,10 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { presenter.fetchEvents() } - override fun openEventCapture(intent: Intent) { - if (dashboardActivity is TeiDashboardMobileActivity) { - contractHandler.editEvent(intent).observe(viewLifecycleOwner) { - presenter.fetchEvents() - } - } - if (dashboardActivity is EventCaptureActivity) { - val selectedEventUid = intent.getStringExtra(Constants.EVENT_UID) - dashboardViewModel.updateSelectedEventUid(selectedEventUid) + override fun openEventCapture(intent: Intent) = + contractHandler.editEvent(intent).observe(viewLifecycleOwner) { + presenter.fetchEvents() } - } override fun goToEventInitial( eventCreationType: EventCreationType, @@ -564,7 +568,9 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { } override fun showProgramRuleErrorMessage() { - dashboardActivity.executeOnUIThread() + dashboardActivity.runOnUiThread { + showDescription(getString(R.string.error_applying_rule_effects)) + } } companion object { diff --git a/app/src/main/java/org/dhis2/usescases/troubleshooting/TroubleshootingRepository.kt b/app/src/main/java/org/dhis2/usescases/troubleshooting/TroubleshootingRepository.kt index f7b881d9cf..34a06d2656 100644 --- a/app/src/main/java/org/dhis2/usescases/troubleshooting/TroubleshootingRepository.kt +++ b/app/src/main/java/org/dhis2/usescases/troubleshooting/TroubleshootingRepository.kt @@ -10,6 +10,7 @@ import org.hisp.dhis.android.core.program.Program import org.hisp.dhis.android.core.program.ProgramRuleActionType import org.hisp.dhis.antlr.ParserExceptionWithoutContext import org.hisp.dhis.lib.expression.Expression +import org.hisp.dhis.lib.expression.ExpressionMode import org.hisp.dhis.lib.expression.spi.ExpressionData import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor import org.hisp.dhis.rules.api.EnvironmentVariables.ENV_VARIABLES @@ -40,7 +41,7 @@ class TroubleshootingRepository( rule.condition, valueMap, null, - Expression.Mode.RULE_ENGINE_CONDITION, + ExpressionMode.RULE_ENGINE_CONDITION, ) if (ruleConditionResult.isNotEmpty()) { ruleValidationItem = ruleValidationItem.copy(conditionError = ruleConditionResult) @@ -164,7 +165,7 @@ class TroubleshootingRepository( condition: String, valueMap: Map, ruleActionType: String? = null, - mode: Expression.Mode, + mode: ExpressionMode, ): String { if (condition.isEmpty()) { return if (ruleActionType != null) { @@ -230,7 +231,7 @@ class TroubleshootingRepository( ruleAction.data ?: "", valueMap, ruleAction.ruleActionType(), - Expression.Mode.RULE_ENGINE_ACTION, + ExpressionMode.RULE_ENGINE_ACTION, ) actionConditionResult.ifEmpty { null diff --git a/app/src/main/java/org/dhis2/utils/customviews/BreakTheGlassBottomDialog.kt b/app/src/main/java/org/dhis2/utils/customviews/BreakTheGlassBottomDialog.kt index 02d18c824b..7f68d77e86 100644 --- a/app/src/main/java/org/dhis2/utils/customviews/BreakTheGlassBottomDialog.kt +++ b/app/src/main/java/org/dhis2/utils/customviews/BreakTheGlassBottomDialog.kt @@ -1,5 +1,6 @@ package org.dhis2.utils.customviews +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -15,13 +16,11 @@ import org.dhis2.commons.resources.ColorType import org.dhis2.commons.resources.ColorUtils import org.dhis2.commons.resources.ResourceManager import org.dhis2.databinding.BreakTheGlassBottomDialogBindingImpl -import javax.inject.Inject class BreakTheGlassBottomDialog : BottomSheetDialogFragment() { private lateinit var programUid: String - @Inject lateinit var resourceManager: ResourceManager val colorUtils: ColorUtils = ColorUtils() @@ -42,11 +41,19 @@ class BreakTheGlassBottomDialog : BottomSheetDialogFragment() { isCancelable = false } + override fun onAttach(context: Context) { + super.onAttach(context) + resourceManager = ResourceManager( + context, + colorUtils, + ) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { return BreakTheGlassBottomDialogBindingImpl.inflate(inflater, container, false).apply { message.text = resourceManager.formatWithEnrollmentLabel( programUid, diff --git a/app/src/main/res/raw/testing_credentials.json b/app/src/main/res/raw/testing_credentials.json index ba9d176646..12d8d551fc 100644 --- a/app/src/main/res/raw/testing_credentials.json +++ b/app/src/main/res/raw/testing_credentials.json @@ -1,10 +1,4 @@ [ - { - "server_url": "https://play.dhis2.org/2.38", - "user_name": "android", - "user_pass": "Android123", - "server_version": "2.38" - }, { "server_url": "https://play.dhis2.org/2.39", "user_name": "android", @@ -16,5 +10,11 @@ "user_name": "android", "user_pass": "Android123", "server_version": "40" + }, + { + "server_url": "https://play.dhis2.org/41", + "user_name": "android", + "user_pass": "Android123", + "server_version": "41" } ] diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c026a8ca33..d48bcee1f7 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -44,6 +44,8 @@ %s resultados Agregar nuevo Añadir + Introduce %s + Introduce / Salta %s Programar Remitir @@ -76,6 +78,9 @@ Buscar + Buscar / Añadir nuevo %s + Buscar %s + Añadir nuevo %s Introducir texto Introducir texto largo Introducir numero @@ -92,6 +97,8 @@ Programa suspendido Abrir + No completado + Evento completado Sólo lectura Acabar y completar Completado @@ -142,6 +149,9 @@ Borrar Total Fecha de evento más reciente + + No hay opciones disponibles + Listado de admisiones Programas activos Programas disponibles para inscripción Admitir @@ -151,18 +161,22 @@ Ver detalles Ver mas + Editar Seguimiento Seguimiento activado Seguimiento desactivado Desactivar Este programa no admite más eventos + No hay datos Eventos Inicio Información de la App Cerrar Sesion Bloquear sesion + Mostrar %s más... + Mostrar menos... 1 minuto 15 minutos 30 minutos @@ -188,6 +202,7 @@ Modulo SMS: SMS gateway Emisor del SMS de resultado + "Tiempo de espera de recepción del SMS " segundos numero numero @@ -222,6 +237,10 @@ Verifique los parámetros relacionados con la SMS gateway + Actualización de software + Software + Exportar base de datos + Exporta tu base de datos y compártala con su administrador Fecha de inscripción @@ -373,6 +392,7 @@ Eventos Completados Todos los eventos de este programa están completados. ¿Quiere completar el programa también? ¿ Completar también la inscripción? + Le gustaría completar el %s también? Ver Inicio @@ -469,6 +489,9 @@ Debe seleccionar al menos una unidad organizativa. Enviar datos Ayude a DHIS2 a mejorar esta app compartiendo errores asociados a su usuario. + Quiere ayudarnos a mejorar esta app? + "Si dice que sí la app mandará estadísticas anónimas y registro de errores que ayudaran a mejorar la experiencia y el rendimiento. Para más detalles, consulte nuestra política de privacidad. " + política de privacidad Comprobar campos Se perdió la conexión a internet durante la sincronización. Inténtelo mas tarde. Confirmar @@ -681,6 +704,7 @@ El %s seleccionado no puede ser descargado. Completado por %s: %s Actualizado: %s + ¡Listo! No hemos podido verificar la calidad de los datos. Contacte con su administrador ¿Está seguro? ¿Quiere reabrir este set de datos? @@ -838,6 +862,7 @@ Dispositivo: %s Versión del SO: %s Abrir detalles de %s + Detalles de %s Sincronizando %s... SMS enviado. Esperando respuesta del servidor. Se ha confirmado que el SMS llegó al servidor. Actualizando estado @@ -846,11 +871,85 @@ SMS no enviado. Abriendo aplicación de SMS. Por favor, no modifique el mensaje. Enviar SMS con + ¿Ha enviado el sms? Esperando confirmación manual + Sincronizando eventos + Sincronizando entidades de ratreo + Sincronizando set de datos + Sincronizando recursos + Sincronización finalizada + La sincronización por SMS está habilitada pero debe acceder a ella en cada registro individualmente. + Descargando imágenes... + Descripción del programa + Pin erróneo Inscrito en: + Sincronización necesaria + Error de sincroniación Sincronizado + Advertencia de sincronización Sincronizando + SMS enviado + ¿Quiere mandar todos sus cambios? + ¿Quiere mandar sus cambios de %s? + ¿Quiere mandar sus cambios de este %s? + ¿Quiere comprobar si existen cambios? + ¿Quiere comprobar si existen cambios en %s? + ¿Quiere comprobar si existen cambios para este %s? + ¿Quiere mandar el SMS de nuevo? Enviar Actualizar Ahora no - + Toque aquí para explorar + La versión %s de la app está ahora disponible. ¿Quiere instalarla? + Actualización de software + Más tarde + Descargar ahora + Buscar actualizaciones + Buscando actualizaciones + No hay nuevas actualizaciones + El permiso para fuentes desconocidas está deshabilitado. Habilítelo para instalar esta versión. + El permiso de almacenamiento está deshabilitado. Habilítelo para instalar esta versión. + Versión de la app + Hay algún error en la configuraciónd el programa, no se puede cargar el evento. Por favor contacte a su administrador. + Eliminar persona + Permiso de notificaciones está deshabilitado. + Permiso de notificaciones concedido. + No sincronizaco + Reintentar sincronización + Marcado para seguimiento + %s atrasado + Registrado en: + Solo vista + Programado: %s + Programado para %s + Omitido + Programas: + Sincronización existosa + Aviso en la sincronización + Error de sincronización + Marcar para seguimiento + Eliminar + Coordenadas + No tiene acceso a los datos. Contacte con su administrador. + Parece que está offline. Compruebe su conexión. + ¿Eliminar este %s? + Este %s y todos sus datos de todos los programas serán eliminados. Esta acción no puede dehacerse. + ¿Eliminar de %s? + Los datos del %s serán elimiados. Esta acción no puede deshacerse. + Programar el siguiente + Programar + Etapa + evento + %d eventos + Línea de tiempo + Limpiar búsqueda + Opcional + Base de datos descargada + Importando base de datos + Reabrir el formulario para editarlo + Mostrar más + Mostrar menos + Sincronizar + Mostrar campos + Ocultar campos + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index abfa36ff34..3fc9c7ea73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + SEMIS dhis @@ -244,7 +244,7 @@ Software update Software Export database - Export yout database and share it with your administrator + Export your database and share it with your administrator Enrollment date @@ -496,7 +496,7 @@ Send data Help DHIS2 improve this app by sharing error logs associated to your user id. Do you want to help us improve this app? - If you say yes the app will send anonymous statistics and error logs which help us improve user experience and performance.\n\nFor further details, please refer to our privacy policy. + If you say yes the app will send anonymous statistics and error logs which help us improve user experience and performance. For further details, please refer to our privacy policy. privacy policy Check fields You lost your internet connection while performing your configuration sync. Please try again later @@ -701,7 +701,7 @@ Complete anyway Missing %s %s from %s %s Event status - @string/enrollment_status + @string/enrollment_status Enrollment date There are no data. Click \"+\" to add new a new record There are no data. You don\'t have permission to add new records. @@ -925,7 +925,7 @@ %s warning %s warnings - App version %s is now available.\nDo you want to download it? + App version %s is now available. Do you want to download it? "Software update" Later Download now @@ -972,4 +972,9 @@ Database downloaded Importing database Re-open form to edit + Show more + Show less + Sync + Show fields + Hide fields diff --git a/app/src/test/java/org/dhis2/usescases/main/program/ProgramPresenterTest.kt b/app/src/test/java/org/dhis2/usescases/main/program/ProgramPresenterTest.kt index ed075fbf3a..2ccd66b31a 100644 --- a/app/src/test/java/org/dhis2/usescases/main/program/ProgramPresenterTest.kt +++ b/app/src/test/java/org/dhis2/usescases/main/program/ProgramPresenterTest.kt @@ -187,6 +187,7 @@ class ProgramPresenterTest { filtersAreActive = false, downloadState = ProgramDownloadState.NONE, stockConfig = null, + isSEMIS = false, ) } @@ -210,6 +211,7 @@ class ProgramPresenterTest { filtersAreActive = false, downloadState = ProgramDownloadState.NONE, stockConfig = null, + isSEMIS = false, ) } } diff --git a/app/src/test/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryTest.kt b/app/src/test/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryTest.kt new file mode 100644 index 0000000000..7374d434d9 --- /dev/null +++ b/app/src/test/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryTest.kt @@ -0,0 +1,90 @@ +package org.dhis2.usescases.searchTrackEntity + +import androidx.paging.PagingData +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import org.dhis2.commons.resources.MetadataIconProvider +import org.dhis2.commons.viewmodel.DispatcherProvider +import org.dhis2.data.search.SearchParametersModel +import org.dhis2.form.ui.FieldViewModelFactory +import org.hisp.dhis.android.core.D2 +import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchCollectionRepository +import org.hisp.dhis.android.core.trackedentity.search.TrackedEntitySearchItem +import org.junit.After +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito +import org.mockito.Mockito.verify +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +class SearchRepositoryTest { + private val searchRepositoryJava: SearchRepository = mock() + + private val d2: D2 = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS) + + private val dispatcher: DispatcherProvider = mock() + + private val fieldViewModelFactory: FieldViewModelFactory = mock() + + private val metadataIconProvider: MetadataIconProvider = mock() + + private lateinit var searchRepositoryImplKt: SearchRepositoryImplKt + + private val testDispatcher = UnconfinedTestDispatcher() + + @Before + fun setup() { + Dispatchers.setMain(testDispatcher) + searchRepositoryImplKt = SearchRepositoryImplKt( + searchRepositoryJava, + d2, + dispatcher, + fieldViewModelFactory, + metadataIconProvider, + ) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `test searchTrackedEntities returns PagingData flow`() = runTest { + val searchParametersModel: SearchParametersModel = SearchParametersModel( + selectedProgram = null, + queryData = null, + ) + val trackedEntitySearchCollectionRepository: TrackedEntitySearchCollectionRepository = + mock() + val pagingDataFlow = flowOf(PagingData.empty()) + + whenever(searchRepositoryJava.getFilteredRepository(searchParametersModel)) doReturn trackedEntitySearchCollectionRepository + whenever(trackedEntitySearchCollectionRepository.allowOnlineCache()) doReturn mock() + whenever(trackedEntitySearchCollectionRepository.allowOnlineCache().eq(false)) doReturn mock() + whenever(trackedEntitySearchCollectionRepository.allowOnlineCache().eq(false).offlineFirst()) doReturn mock() + whenever(trackedEntitySearchCollectionRepository.allowOnlineCache().eq(false).offlineOnly()) doReturn mock() + whenever(trackedEntitySearchCollectionRepository.allowOnlineCache().eq(false).offlineOnly().getPagingData(10)) doReturn mock() + whenever(trackedEntitySearchCollectionRepository.allowOnlineCache().eq(false).offlineFirst().getPagingData(10)) doReturn mock() + whenever(trackedEntitySearchCollectionRepository.getPagingData(10)) doReturn pagingDataFlow + + val result = + searchRepositoryImplKt.searchTrackedEntities(searchParametersModel, isOnline = true) + + result.collect { pagingData -> + assertTrue(pagingData is PagingData) + } + + verify(searchRepositoryJava).getFilteredRepository(searchParametersModel) +// verify(trackedEntitySearchCollectionRepository).getPagingData(10) + } +} diff --git a/app/src/test/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListPresenterTest.kt b/app/src/test/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListPresenterTest.kt index b1d2381382..e73bed4256 100644 --- a/app/src/test/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListPresenterTest.kt +++ b/app/src/test/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListPresenterTest.kt @@ -175,6 +175,7 @@ class TeiProgramListPresenterTest { filtersAreActive = false, downloadState = ProgramDownloadState.NONE, stockConfig = null, + isSEMIS = false, ) } diff --git a/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt b/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt index 841d2db328..3e717cf3b0 100644 --- a/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt +++ b/commons/src/main/java/org/dhis2/commons/featureconfig/data/FeatureConfigRepositoryImpl.kt @@ -3,45 +3,36 @@ package org.dhis2.commons.featureconfig.data import org.dhis2.commons.featureconfig.model.Feature import org.dhis2.commons.featureconfig.model.FeatureState import org.dhis2.commons.prefs.PreferenceProvider -import org.hisp.dhis.android.core.D2 -import org.hisp.dhis.android.core.settings.ExperimentalFeature import javax.inject.Inject class FeatureConfigRepositoryImpl @Inject constructor( val preferences: PreferenceProvider, - val d2: D2, ) : FeatureConfigRepository { - val SET_FROM_DEVELOPMENT = "SET_FROM_DEVELOPMENT" - override val featuresList: List - get() = Feature.entries.map { - FeatureState(it, isFeatureEnable(it)) - } + get() = Feature.entries + .filter { it.name != Feature.COMPOSE_FORMS.name } + .map { FeatureState(it, isFeatureEnable(it)) } override fun updateItem(featureState: FeatureState) { - if (featureState.feature.name == Feature.COMPOSE_FORMS.name) { - preferences.setValue(SET_FROM_DEVELOPMENT, true) - } preferences.setValue(featureState.feature.name, !featureState.enable) } override fun isFeatureEnable(feature: Feature): Boolean { return when { feature.name == Feature.COMPOSE_FORMS.name -> { - val fromDevelopment = preferences.getBoolean(SET_FROM_DEVELOPMENT, false) + val fromDevelopment = preferences.getBoolean("SET_FROM_TESTING", false) if (fromDevelopment) { preferences.getBoolean(feature.name, false) - } else if (d2.settingModule().generalSetting().blockingExists()) { - d2.settingModule().generalSetting() - .hasExperimentalFeature(ExperimentalFeature.NewFormLayout).blockingGet() } else { true } } + preferences.contains(feature.name) -> { preferences.getBoolean(feature.name, false) } + else -> false } } diff --git a/commons/src/main/java/org/dhis2/commons/featureconfig/di/FeatureConfigModule.kt b/commons/src/main/java/org/dhis2/commons/featureconfig/di/FeatureConfigModule.kt index 81059726b7..9293dce433 100644 --- a/commons/src/main/java/org/dhis2/commons/featureconfig/di/FeatureConfigModule.kt +++ b/commons/src/main/java/org/dhis2/commons/featureconfig/di/FeatureConfigModule.kt @@ -5,15 +5,12 @@ import dagger.Provides import org.dhis2.commons.featureconfig.data.FeatureConfigRepository import org.dhis2.commons.featureconfig.data.FeatureConfigRepositoryImpl import org.dhis2.commons.prefs.PreferenceProvider -import org.hisp.dhis.android.core.D2Manager @Module class FeatureConfigModule { @Provides fun provideRepository(preferenceProvider: PreferenceProvider): FeatureConfigRepository { - return FeatureConfigRepositoryImpl(preferenceProvider, provideD2()) + return FeatureConfigRepositoryImpl(preferenceProvider) } - - private fun provideD2() = D2Manager.getD2() } diff --git a/commons/src/main/res/values-es/strings.xml b/commons/src/main/res/values-es/strings.xml index 5f60ed38e4..cb38784c3b 100644 --- a/commons/src/main/res/values-es/strings.xml +++ b/commons/src/main/res/values-es/strings.xml @@ -42,7 +42,69 @@ Asignado a mí Fecha Fecha de evento - Fecha de inscripción + La conexión de red no está disponible + Se requiere conexión a internet para usar mapas. + Hoy + + %d día atrasado + %d días atrasados + %d días atrasados + + + %d mes atrasado + %d meses atrasados + %d meses atrasados + + + %d año atrasado + %d años atrasados + %d años atrasados + + + + En %d día + En %d días + En %d días + + + En %d mes + En %d meses + En %d meses + + + En %d año + En %d años + En %d años + + Abrir con + Archivo descargado con éxito + Mostrar coordenadas de %s + Datos de %s + Coordenadas de %s + Fecha de %s + Estado de %s + Es necesario proveer la razón para consultar este %s en este programa protegido. Toda actividad será registrada. + Razón para consultar %s + Los datos no son editables por que el %s está completado o cancelado + %s completado + %s cancelado + Nuevo %s + Registrado en: + Eliminar solo %s + %s del programa + %s completado + %s creado + Nuevo %s + Fecha de %s + Eliminar %s + %s eliminado con éxito + Éste %s aún no ha sido creado + Éste %s ha sido omitido + ¿Está seguro de que desea eliminar éste %s? + %s actualizado + Éste %s no puede tener una fecha a futuro. Cámbielo por favor. + Error al insertar un nuevo %s + Semana %d %s a %s Estado del evento Estado de la inscripción. Seguido %s @@ -202,8 +264,12 @@ No hay suficientes atributos para realizar la búsqueda. Por favor redefina la búsqueda añadiendo más atributos. La búsqueda online solo está disponible para las unidades organizativas de búsqueda. El query contiene caracteres invalidos. Por favor contacte a su administrador para verificar la configuración del servidor (relaxedQueryChars,...). + El servidor no está disponible. Pruebe más tarde. + Está desconectado. Habilite su conexión a internet y pruebe otra vez. Este campo no está disponible para la captura por móvil. Añadir imagen + Añadir firma + Añadir archivo De acuerdo La ubicación de su dispositivo está desconectada. Por favor habilítela para capturar coordenadas. Seguir editando diff --git a/dhis2-mobile-program-rules/src/main/java/org/dhis2/mobileProgramRules/RulesRepository.kt b/dhis2-mobile-program-rules/src/main/java/org/dhis2/mobileProgramRules/RulesRepository.kt index 73e75d0c98..f651041278 100644 --- a/dhis2-mobile-program-rules/src/main/java/org/dhis2/mobileProgramRules/RulesRepository.kt +++ b/dhis2-mobile-program-rules/src/main/java/org/dhis2/mobileProgramRules/RulesRepository.kt @@ -24,7 +24,9 @@ import org.hisp.dhis.rules.models.Rule import org.hisp.dhis.rules.models.RuleAttributeValue import org.hisp.dhis.rules.models.RuleDataValue import org.hisp.dhis.rules.models.RuleEnrollment +import org.hisp.dhis.rules.models.RuleEnrollmentStatus import org.hisp.dhis.rules.models.RuleEvent +import org.hisp.dhis.rules.models.RuleEventStatus import org.hisp.dhis.rules.models.RuleVariable import java.util.Calendar import java.util.Date @@ -112,9 +114,9 @@ class RulesRepository(private val d2: D2) { .uid(event.programStage()) .blockingGet()!!.name()!!, status = if (event.status() == EventStatus.VISITED) { - RuleEvent.Status.ACTIVE + RuleEventStatus.ACTIVE } else { - RuleEvent.Status.valueOf(event.status()!!.name) + RuleEventStatus.valueOf(event.status()!!.name) }, eventDate = Instant.fromEpochMilliseconds(event.eventDate()!!.time), dueDate = event.dueDate()?.let { @@ -206,9 +208,9 @@ class RulesRepository(private val d2: D2) { .blockingGet()!!.name()!!, status = if (event.status() == EventStatus.VISITED) { - RuleEvent.Status.ACTIVE + RuleEventStatus.ACTIVE } else { - RuleEvent.Status.valueOf(event.status()!!.name) + RuleEventStatus.valueOf(event.status()!!.name) }, eventDate = event.eventDate()!!.toRuleEngineInstant(), dueDate = event.dueDate()?.toRuleEngineLocalDate(), @@ -242,7 +244,7 @@ class RulesRepository(private val d2: D2) { programName!!, Calendar.getInstance().time.toRuleEngineLocalDate(), Calendar.getInstance().time.toRuleEngineLocalDate(), - RuleEnrollment.Status.CANCELLED, + RuleEnrollmentStatus.CANCELLED, event.organisationUnit()!!, ouCode, ArrayList(), @@ -255,7 +257,7 @@ class RulesRepository(private val d2: D2) { programName!!, (enrollment.incidentDate() ?: Date()).toRuleEngineLocalDate(), enrollment.enrollmentDate()!!.toRuleEngineLocalDate(), - RuleEnrollment.Status.valueOf(enrollment.status()!!.name), + RuleEnrollmentStatus.valueOf(enrollment.status()!!.name), event.organisationUnit()!!, ouCode, getAttributesValues(enrollment), @@ -314,7 +316,7 @@ class RulesRepository(private val d2: D2) { programName = d2.program(enrollment.program()!!)?.name()!!, incidentDate = (enrollment.incidentDate() ?: Date()).toRuleEngineLocalDate(), enrollmentDate = (enrollment.enrollmentDate() ?: Date()).toRuleEngineLocalDate(), - status = RuleEnrollment.Status.valueOf(enrollment.status()!!.name), + status = RuleEnrollmentStatus.valueOf(enrollment.status()!!.name), organisationUnit = enrollment.organisationUnit()!!, organisationUnitCode = d2.organisationUnit(enrollment.organisationUnit()!!) ?.code() ?: "", @@ -328,7 +330,7 @@ class RulesRepository(private val d2: D2) { event = event.uid(), programStage = event.programStage()!!, programStageName = d2.programStage(event.programStage()!!)?.name()!!, - status = RuleEvent.Status.valueOf(event.status()!!.name), + status = RuleEventStatus.valueOf(event.status()!!.name), eventDate = event.eventDate()!!.toRuleEngineInstant(), dueDate = event.dueDate()?.toRuleEngineLocalDate(), completedDate = event.completedDate()?.toRuleEngineLocalDate(), diff --git a/emis/src/main/java/org/saudigitus/emis/service/RuleEngineRepository.kt b/emis/src/main/java/org/saudigitus/emis/service/RuleEngineRepository.kt index 2fd684ea09..a29b510232 100644 --- a/emis/src/main/java/org/saudigitus/emis/service/RuleEngineRepository.kt +++ b/emis/src/main/java/org/saudigitus/emis/service/RuleEngineRepository.kt @@ -22,6 +22,7 @@ import org.hisp.dhis.rules.api.RuleEngine import org.hisp.dhis.rules.api.RuleEngineContext import org.hisp.dhis.rules.models.Rule import org.hisp.dhis.rules.models.RuleEvent +import org.hisp.dhis.rules.models.RuleEventStatus import org.hisp.dhis.rules.models.RuleVariable import javax.inject.Inject @@ -98,9 +99,9 @@ class RuleEngineRepository @Inject constructor( .uid(event.programStage()) .blockingGet()!!.name()!!, status = if (event.status() == EventStatus.VISITED) { - RuleEvent.Status.ACTIVE + RuleEventStatus.ACTIVE } else { - RuleEvent.Status.valueOf(event.status()!!.name) + RuleEventStatus.valueOf(event.status()!!.name) }, eventDate = Instant.fromEpochMilliseconds(event.eventDate()!!.time), dueDate = event.dueDate()?.let { @@ -185,7 +186,7 @@ class RuleEngineRepository @Inject constructor( event = event.uid(), programStage = event.programStage()!!, programStageName = d2.programStage(event.programStage()!!)?.name()!!, - status = RuleEvent.Status.valueOf(event.status()!!.name), + status = RuleEventStatus.valueOf(event.status()!!.name), eventDate = event.eventDate()!!.toRuleEngineInstant(), dueDate = event.dueDate()?.toRuleEngineLocalDate(), completedDate = event.completedDate()?.toRuleEngineLocalDate(), diff --git a/form/src/main/java/org/dhis2/form/data/RulesRepository.kt b/form/src/main/java/org/dhis2/form/data/RulesRepository.kt index 87d6f8aed0..f9957226ed 100644 --- a/form/src/main/java/org/dhis2/form/data/RulesRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/RulesRepository.kt @@ -23,7 +23,9 @@ import org.hisp.dhis.android.core.program.ProgramRule import org.hisp.dhis.rules.models.Rule import org.hisp.dhis.rules.models.RuleAttributeValue import org.hisp.dhis.rules.models.RuleEnrollment +import org.hisp.dhis.rules.models.RuleEnrollmentStatus import org.hisp.dhis.rules.models.RuleEvent +import org.hisp.dhis.rules.models.RuleEventStatus import org.hisp.dhis.rules.models.RuleVariable import java.util.Calendar import java.util.Date @@ -131,9 +133,9 @@ class RulesRepository(private val d2: D2) { .uid(event.programStage()) .blockingGet()!!.name()!!, status = if (event.status() == EventStatus.VISITED) { - RuleEvent.Status.ACTIVE + RuleEventStatus.ACTIVE } else { - RuleEvent.Status.valueOf(event.status()!!.name) + RuleEventStatus.valueOf(event.status()!!.name) }, eventDate = Instant.fromEpochMilliseconds(event.eventDate()!!.time), dueDate = event.dueDate()?.let { @@ -226,9 +228,9 @@ class RulesRepository(private val d2: D2) { .blockingGet()!!.name()!!, status = if (event.status() == EventStatus.VISITED) { - RuleEvent.Status.ACTIVE + RuleEventStatus.ACTIVE } else { - RuleEvent.Status.valueOf(event.status()!!.name) + RuleEventStatus.valueOf(event.status()!!.name) }, eventDate = event.eventDate()!!.toRuleEngineInstant(), dueDate = event.dueDate()?.toRuleEngineLocalDate(), @@ -263,7 +265,7 @@ class RulesRepository(private val d2: D2) { programName!!, Calendar.getInstance().time.toRuleEngineLocalDate(), Calendar.getInstance().time.toRuleEngineLocalDate(), - RuleEnrollment.Status.CANCELLED, + RuleEnrollmentStatus.CANCELLED, event.organisationUnit()!!, ouCode, ArrayList(), @@ -278,7 +280,7 @@ class RulesRepository(private val d2: D2) { programName!!, (enrollment.incidentDate() ?: Date()).toRuleEngineLocalDate(), enrollment.enrollmentDate()!!.toRuleEngineLocalDate(), - RuleEnrollment.Status.valueOf(enrollment.status()!!.name), + RuleEnrollmentStatus.valueOf(enrollment.status()!!.name), event.organisationUnit()!!, ouCode, getAttributesValues(enrollment), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cbb449e0ac..3ce422dd1d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,8 +2,8 @@ ndk = "21.4.7075529" sdk = "34" minSdk = "21" -vCode = "130" -vName = "3.0" +vCode = "134" +vName = "3.0.0.1" kotlinCompilerExtensionVersion = "1.5.6" gradle = "8.2.2" kotlin = '1.9.21' @@ -15,10 +15,10 @@ androidxNavigation = "2.6.0" androidxHiltNavigation = "1.0.0" jacoco = '0.8.10' -designSystem = "0.2-20240430.062706-60" -dhis2sdk = "1.10.0-20240426.151240-44" -ruleEngine = "3.0.0-20240119.134348-12" -expressionParser = "1.1.0-20240219.115041-14" +designSystem = "0.2" +dhis2sdk = "1.10.0.1" +ruleEngine = "3.0.0" +expressionParser = "1.1.0" appcompat = "1.6.1" annotation = "1.6.0" cardview = "1.0.0" diff --git a/stock-usecase/src/main/java/org/dhis2/android/rtsm/services/rules/RuleValidationHelperImpl.kt b/stock-usecase/src/main/java/org/dhis2/android/rtsm/services/rules/RuleValidationHelperImpl.kt index 7b649394b9..eaa1cf88fa 100644 --- a/stock-usecase/src/main/java/org/dhis2/android/rtsm/services/rules/RuleValidationHelperImpl.kt +++ b/stock-usecase/src/main/java/org/dhis2/android/rtsm/services/rules/RuleValidationHelperImpl.kt @@ -27,6 +27,7 @@ import org.hisp.dhis.rules.api.RuleEngine import org.hisp.dhis.rules.models.RuleDataValue import org.hisp.dhis.rules.models.RuleEffect import org.hisp.dhis.rules.models.RuleEvent +import org.hisp.dhis.rules.models.RuleEventStatus import org.hisp.dhis.rules.models.RuleVariable import timber.log.Timber import java.util.Date @@ -129,7 +130,7 @@ class RuleValidationHelperImpl @Inject constructor( eventUid ?: UUID.randomUUID().toString(), programStage.uid(), programStage.name()!!, - RuleEvent.Status.ACTIVE, + RuleEventStatus.ACTIVE, period.toRuleEngineInstant(), period.toRuleEngineLocalDate(), period.toRuleEngineLocalDate(), @@ -230,9 +231,9 @@ class RuleValidationHelperImpl @Inject constructor( d2.programModule().programStages().uid(event.programStage()) .blockingGet()!!.name()!!, if (event.status() == EventStatus.VISITED) { - RuleEvent.Status.ACTIVE + RuleEventStatus.ACTIVE } else { - RuleEvent.Status.valueOf(event.status()!!.name) + RuleEventStatus.valueOf(event.status()!!.name) }, (event.eventDate() ?: Date()).toRuleEngineInstant(), event.dueDate()?.toRuleEngineLocalDate(), diff --git a/stock-usecase/src/test/java/org/dhis2/android/rtsm/services/ProgramRuleTests.kt b/stock-usecase/src/test/java/org/dhis2/android/rtsm/services/ProgramRuleTests.kt index 5494967655..8b41b4af8b 100644 --- a/stock-usecase/src/test/java/org/dhis2/android/rtsm/services/ProgramRuleTests.kt +++ b/stock-usecase/src/test/java/org/dhis2/android/rtsm/services/ProgramRuleTests.kt @@ -10,7 +10,9 @@ import org.hisp.dhis.rules.models.Rule import org.hisp.dhis.rules.models.RuleAction import org.hisp.dhis.rules.models.RuleDataValue import org.hisp.dhis.rules.models.RuleEnrollment +import org.hisp.dhis.rules.models.RuleEnrollmentStatus import org.hisp.dhis.rules.models.RuleEvent +import org.hisp.dhis.rules.models.RuleEventStatus import org.hisp.dhis.rules.models.RuleValueType import org.hisp.dhis.rules.models.RuleVariable import org.hisp.dhis.rules.models.RuleVariableCurrentEvent @@ -124,7 +126,7 @@ class ProgramRuleTests { "test_program", Date().toRuleEngineLocalDate(), Date().toRuleEngineLocalDate(), - RuleEnrollment.Status.ACTIVE, + RuleEnrollmentStatus.ACTIVE, "test_ou", "test_ou_code", emptyList(), @@ -134,7 +136,7 @@ class ProgramRuleTests { "test_event", "test_program_stage", "", - RuleEvent.Status.ACTIVE, + RuleEventStatus.ACTIVE, Date().toRuleEngineInstant(), Date().toRuleEngineLocalDate(), null, diff --git a/whatsnew/whatsnew-en-US b/whatsnew/whatsnew-en-US index 29ab46b2fc..19cb83d747 100644 --- a/whatsnew/whatsnew-en-US +++ b/whatsnew/whatsnew-en-US @@ -1,8 +1,9 @@ -This is a patch version that fixes: -- ANDROAPP-5749 Incorrect behavior when tapping on Next on sections that are too long -- ANDROAPP-5885 Data set indicators don't update until the user moves to a different cell -- ANDROAPP-5741 [Local Analytics] App crashes if charts are empty -- ANDROAPP-5604 App crashes when one attempts to synchronise TEI and events imported via QR code -- ANDROAPP-5872 Search in stock management is not updating the list - -You can find all the details on Jira and Github. \ No newline at end of file +# Release notes - Android App for DHIS2 - 3.0.0.1 + +### Bug + +[ANDROAPP-6194](https://dhis2.atlassian.net/browse/ANDROAPP-6194) Unable to search outside the program + +[ANDROAPP-6195](https://dhis2.atlassian.net/browse/ANDROAPP-6195) Missing terms in transifex + +[ANDROAPP-6210](https://dhis2.atlassian.net/browse/ANDROAPP-6210) UninitializedPropertyAccessException on breaking the glass \ No newline at end of file