diff --git a/.github/workflows/build-release-candidate.yml b/.github/workflows/build-release-candidate.yml new file mode 100644 index 0000000000..df7609ec4a --- /dev/null +++ b/.github/workflows/build-release-candidate.yml @@ -0,0 +1,65 @@ +# This is a basic workflow that is manually triggered + +name: Build Release Candidate + +env: + # The name of the main module repository + main_project_module: app + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + greet: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - uses: actions/checkout@v3 + + # 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@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 prod + run: ./gradlew app:assembleDhisRelease + 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: 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 '"')" + + # Upload Artifact Build + - name: Upload Android artifacts + 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 diff --git a/.github/workflows/publish-libraries.yml b/.github/workflows/publish-libraries.yml new file mode 100644 index 0000000000..94b5ccb317 --- /dev/null +++ b/.github/workflows/publish-libraries.yml @@ -0,0 +1,32 @@ +# This is a basic workflow that is manually triggered + +name: Publish libraries + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + name: + # Friendly description to be shown in the UI instead of 'name' + description: 'Person to greet' + # Default value if no value is explicitly provided + default: 'World' + # Input has to be provided for the workflow to run + required: true + # The data type of the input + type: string + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + greet: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - name: Send greeting + run: echo "Hello ${{ inputs.name }}" diff --git a/Jenkinsfile b/Jenkinsfile index c9ac08f86d..5c8ba67d08 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -55,6 +55,7 @@ pipeline { BROWSERSTACK = credentials('android-browserstack') form_apk = sh(returnStdout: true, script: 'find form/build/outputs -iname "*.apk" | sed -n 1p') form_apk_path = "${env.WORKSPACE}/${form_apk}" + buildTag = "${env.GIT_BRANCH} - form" } steps { dir("${env.WORKSPACE}/scripts"){ @@ -71,6 +72,7 @@ pipeline { BROWSERSTACK = credentials('android-browserstack') compose_table_apk = sh(returnStdout: true, script: 'find compose-table/build/outputs -iname "*.apk" | sed -n 1p') compose_table_apk_path = "${env.WORKSPACE}/${compose_table_apk}" + buildTag = "${env.GIT_BRANCH} - table" } steps { dir("${env.WORKSPACE}/scripts"){ @@ -89,6 +91,7 @@ pipeline { test_apk = sh(returnStdout: true, script: 'find app/build/outputs -iname "*.apk" | sed -n 2p') app_apk_path = "${env.WORKSPACE}/${app_apk}" test_apk_path = "${env.WORKSPACE}/${test_apk}" + buildTag = "${env.GIT_BRANCH}" } steps { dir("${env.WORKSPACE}/scripts"){ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 80f3d81242..535bb1ac95 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -35,6 +35,17 @@ android { } } + signingConfigs { + create("release"){ + keyAlias = System.getenv("SIGNING_KEY_ALIAS") + keyPassword = System.getenv("SIGNING_KEY_PASSWORD") + System.getenv("SIGNING_KEYSTORE_PATH")?.let {path-> + storeFile = file(path) + } + storePassword = System.getenv("SIGNING_STORE_PASSWORD") + } + } + testOptions { execution = "ANDROIDX_TEST_ORCHESTRATOR" unitTests { @@ -143,6 +154,7 @@ android { getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) + signingConfig = signingConfigs.getByName("release") buildConfigField("int", "MATOMO_ID", "1") buildConfigField("String", "BUILD_DATE", "\"" + getBuildDate() + "\"") buildConfigField("String", "GIT_SHA", "\"" + getCommitHash() + "\"") @@ -307,4 +319,4 @@ dependencies { androidTestImplementation(libs.test.compose.ui.test) androidTestImplementation(libs.test.hamcrest) androidTestImplementation(libs.dispatcher.dispatchEspresso) -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/org/dhis2/usescases/event/EventRegistrationRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/event/EventRegistrationRobot.kt index 1d91242e67..c13f41c502 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/event/EventRegistrationRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/event/EventRegistrationRobot.kt @@ -3,6 +3,7 @@ package org.dhis2.usescases.event import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performScrollTo import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches @@ -30,6 +31,7 @@ class EventRegistrationRobot : BaseRobot() { fun checkEventDataEntryIsOpened(completion: Int, email: String, composeTestRule: ComposeTestRule) { onView(withId(R.id.completion)).check(matches(hasCompletedPercentage(completion))) + composeTestRule.onNodeWithText(email).performScrollTo() composeTestRule.onNodeWithText(email).assertIsDisplayed() } diff --git a/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt b/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt index 28e714eab8..966881f270 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/filters/FilterTest.kt @@ -146,7 +146,7 @@ class FilterTest : BaseTest() { eventWithoutRegistrationRobot(composeTestRule) { clickOnEventAtPosition(0) } - formRobot { + formRobot(composeTestRule) { clickOnSelectOption(1, 1) pressBack() pressBack() 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 9d0f2001df..458845c14f 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 @@ -144,6 +144,7 @@ class SyncFlowTest : BaseTest() { prepareFacilityDataSetIntentAndLaunchActivity(ruleDataSet) dataSetRobot { + composeTestRule.waitForIdle() clickOnDataSetAtPosition(0) } @@ -166,7 +167,10 @@ class SyncFlowTest : BaseTest() { clickOnDataSetToSync(0) clickOnSyncButton() workInfoStatusLiveData.postValue(arrayListOf(mockedGranularWorkInfo(WorkInfo.State.RUNNING))) + composeTestRule.waitForIdle() workInfoStatusLiveData.postValue(arrayListOf(mockedGranularWorkInfo(WorkInfo.State.SUCCEEDED))) + composeTestRule.waitForIdle() + waitToDebounce(3000) checkSyncWasSuccessfully() //sync failed } cleanLocalDatabase() diff --git a/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt index 35e309b349..e42dfdab16 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/form/FormRobot.kt @@ -2,9 +2,11 @@ package org.dhis2.usescases.form import android.app.Activity import android.view.MenuItem +import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onFirst +import androidx.compose.ui.test.onNodeWithText import androidx.test.espresso.Espresso.onData import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click @@ -32,13 +34,16 @@ import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.not -fun formRobot(formRobot: FormRobot.() -> Unit) { - FormRobot().apply { +fun formRobot( + composeTestRule: ComposeTestRule, + formRobot: FormRobot.() -> Unit +) { + FormRobot(composeTestRule).apply { formRobot() } } -class FormRobot : BaseRobot() { +class FormRobot(val composeTestRule: ComposeTestRule) : BaseRobot() { fun clickOnASpecificSection(sectionLabel: String) { onView(withText(sectionLabel)).perform(click()) @@ -104,10 +109,8 @@ class FormRobot : BaseRobot() { } fun checkIndicatorIsDisplayed(name: String, value: String) { - onView(withId(R.id.indicator_name)) - .check(matches(allOf(isDisplayed(), withText(name)))) - onView(withId(R.id.indicator_value)) - .check(matches(allOf(isDisplayed(), withText(value)))) + composeTestRule.onNodeWithText(name).assertIsDisplayed() + composeTestRule.onNodeWithText(value).assertIsDisplayed() } fun checkLabel(label: String, position: Int) { diff --git a/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt b/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt index a02c59542e..06c74900a8 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/form/FormTest.kt @@ -32,7 +32,7 @@ class FormTest : BaseTest() { fun shouldApplyProgramRules() { prepareIntentAndLaunchEventActivity(ruleEvent) - formRobot { + formRobot(composeTestRule) { clickOnASpecificSection("Gamma Rules A") } applyHideField() @@ -51,7 +51,7 @@ class FormTest : BaseTest() { } private fun applyHideField() { - formRobot { + formRobot(composeTestRule) { clickOnSelectOption( firstSectionPosition, HIDE_FIELD_POSITION @@ -61,7 +61,7 @@ class FormTest : BaseTest() { } private fun applyHideSection() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -72,7 +72,7 @@ class FormTest : BaseTest() { } private fun applyShowWarning() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -83,7 +83,7 @@ class FormTest : BaseTest() { } private fun applyShowError() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -94,7 +94,7 @@ class FormTest : BaseTest() { } private fun applySetMandatoryField() { - formRobot { + formRobot(composeTestRule) { val nonMandatoryLabel = "ZZ TEST NUMBER" val mandatoryLabel = "ZZ TEST NUMBER *" val position = 5 @@ -109,7 +109,7 @@ class FormTest : BaseTest() { } private fun applyHideOption() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -120,7 +120,7 @@ class FormTest : BaseTest() { } private fun applyHideOptionGroup() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -132,7 +132,7 @@ class FormTest : BaseTest() { } private fun applyShowOptionGroup() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -146,7 +146,7 @@ class FormTest : BaseTest() { } private fun applyAssignValue() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -157,7 +157,7 @@ class FormTest : BaseTest() { } private fun applyDisplayText() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -171,7 +171,7 @@ class FormTest : BaseTest() { } private fun applyDisplayKeyValue() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -185,7 +185,7 @@ class FormTest : BaseTest() { } private fun applyWarningOnComplete() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, @@ -200,7 +200,7 @@ class FormTest : BaseTest() { } private fun applyErrorOnComplete() { - formRobot { + formRobot(composeTestRule) { resetToNoAction(firstSectionPosition) clickOnSelectOption( firstSectionPosition, diff --git a/app/src/androidTest/java/org/dhis2/usescases/programevent/ProgramEventTest.kt b/app/src/androidTest/java/org/dhis2/usescases/programevent/ProgramEventTest.kt index b8894c8c08..68549602fa 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/programevent/ProgramEventTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/programevent/ProgramEventTest.kt @@ -12,6 +12,7 @@ import org.dhis2.usescases.programEventDetail.ProgramEventDetailActivity import org.dhis2.usescases.programevent.robot.programEventsRobot import org.dhis2.usescases.teidashboard.robot.eventRobot import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test @@ -76,6 +77,7 @@ class ProgramEventTest : BaseTest() { } } + @Ignore("Flaky test, will be look om issue ANDROAPP-6030") @Test fun shouldCompleteAnEventAndReopenIt() { val eventDate = "15/3/2020" diff --git a/app/src/androidTest/java/org/dhis2/usescases/programevent/robot/ProgramEventsRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/programevent/robot/ProgramEventsRobot.kt index 04cb72efce..d430956e6d 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/programevent/robot/ProgramEventsRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/programevent/robot/ProgramEventsRobot.kt @@ -62,9 +62,7 @@ class ProgramEventsRobot(val composeTestRule: ComposeContentTestRule) : BaseRobo ).check(matches(isDisplayed())) } - @OptIn(ExperimentalTestApi::class) fun checkEventIsComplete(eventDate: String) { - composeTestRule.waitUntilAtLeastOneExists(hasText(eventDate)) composeTestRule.onNodeWithText(eventDate).assertIsDisplayed() composeTestRule.onNodeWithText("Event completed").assertIsDisplayed() } 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 8ed623b3e9..4557f174bf 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/TeiDashboardTest.kt @@ -152,6 +152,7 @@ class TeiDashboardTest : BaseTest() { } } + @Ignore("To fix in ANDROAPP-6109") @Test fun shouldSuccessfullyScheduleAnEvent() { prepareTeiOpenedWithNoPreviousEventProgrammeAndLaunchActivity(rule) @@ -219,7 +220,7 @@ class TeiDashboardTest : BaseTest() { goToAnalytics() } - indicatorsRobot { + indicatorsRobot(composeTestRule) { checkDetails("0", "4817") } } @@ -302,7 +303,7 @@ class TeiDashboardTest : BaseTest() { goToAnalytics() } - indicatorsRobot { + indicatorsRobot(composeTestRule) { checkGraphIsRendered(chartName) } diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/IndicatorsRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/IndicatorsRobot.kt index 62f4c50948..5a6af744fe 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/IndicatorsRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/IndicatorsRobot.kt @@ -1,73 +1,41 @@ package org.dhis2.usescases.teidashboard.robot +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onNodeWithText import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.hasDescendant -import androidx.test.espresso.matcher.ViewMatchers.hasSibling -import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import org.dhis2.R import org.dhis2.common.BaseRobot import org.dhis2.common.matchers.RecyclerviewMatchers.Companion.atPosition -import org.dhis2.common.matchers.RecyclerviewMatchers.Companion.isNotEmpty -import org.hamcrest.CoreMatchers.allOf -fun indicatorsRobot(indicatorsRobot: IndicatorsRobot.() -> Unit) { - IndicatorsRobot().apply { +fun indicatorsRobot( + composeTestRule: ComposeTestRule, + indicatorsRobot: IndicatorsRobot.() -> Unit +) { + IndicatorsRobot(composeTestRule).apply { indicatorsRobot() } } -class IndicatorsRobot : BaseRobot() { +class IndicatorsRobot(val composeTestRule: ComposeTestRule) : BaseRobot() { fun checkDetails(yellowFeverIndicator: String, weightIndicator: String) { - onView(withId(R.id.indicators_recycler)).check( - matches( - allOf( - isDisplayed(), isNotEmpty(), - atPosition( - 2, - hasDescendant( - allOf( - withText(yellowFeverIndicator), - hasSibling( - allOf( - withId(R.id.indicator_name), - withText("Measles + Yellow fever doses") - ) - ) - ) - ) - ) - ) - ) - ) + composeTestRule.onNodeWithText(yellowFeverIndicator).assertIsDisplayed() + composeTestRule.onNodeWithText(weightIndicator).assertIsDisplayed() + } + fun checkGraphIsRendered(chartName: String) { onView(withId(R.id.indicators_recycler)).check( matches( - allOf( - isDisplayed(), isNotEmpty(), - atPosition( - 1, - hasDescendant( - allOf( - withText(weightIndicator), - hasSibling( - allOf( - withId(R.id.indicator_name), - withText("Average weight (g)") - ) - ) - ) - ) - ) + atPosition( + 1, + hasDescendant(withText(chartName)) ) ) ) } - - fun checkGraphIsRendered(chartName:String){ - onView(withId(R.id.indicators_recycler)).check(matches(atPosition(1, hasDescendant(withText(chartName))))) - } } diff --git a/app/src/main/java/org/dhis2/App.java b/app/src/main/java/org/dhis2/App.java index a1efb3fc2f..099a38e85d 100644 --- a/app/src/main/java/org/dhis2/App.java +++ b/app/src/main/java/org/dhis2/App.java @@ -131,6 +131,7 @@ public void initCrashController() { if (areTrackingPermissionGranted()) { SentryAndroid.init(this, options -> { options.setDsn(BuildConfig.SENTRY_DSN); + options.setAnrReportInDebug(true); // Add a callback that will be used before the event is sent to Sentry. // With this callback, you can modify the event or, when returning null, also discard the event. diff --git a/app/src/main/java/org/dhis2/bindings/ValidationStrategyExtensions.kt b/app/src/main/java/org/dhis2/bindings/ValidationStrategyExtensions.kt deleted file mode 100644 index 63df251fb5..0000000000 --- a/app/src/main/java/org/dhis2/bindings/ValidationStrategyExtensions.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.dhis2.bindings - -import org.hisp.dhis.android.core.common.ValidationStrategy - -fun ValidationStrategy.canSkipErrorFix(hasErrorFields: Boolean, hasEmptyMandatoryFields: Boolean) = - when (this) { - ValidationStrategy.ON_COMPLETE -> true - ValidationStrategy.ON_UPDATE_AND_INSERT -> !hasErrorFields && !hasEmptyMandatoryFields - } diff --git a/app/src/main/java/org/dhis2/data/dhislogic/DhisEventUtils.kt b/app/src/main/java/org/dhis2/data/dhislogic/DhisEventUtils.kt deleted file mode 100644 index 27aac9e132..0000000000 --- a/app/src/main/java/org/dhis2/data/dhislogic/DhisEventUtils.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.dhis2.data.dhislogic - -import org.hisp.dhis.android.core.D2 -import org.hisp.dhis.android.core.common.FeatureType -import javax.inject.Inject - -class DhisEventUtils @Inject constructor(val d2: D2) { - fun newEventNeedsExtraInfo(eventUid: String): Boolean { - val event = d2.eventModule().events().uid(eventUid) - .blockingGet() - val stage = d2.programModule().programStages().uid(event?.programStage()) - .blockingGet() - val program = d2.programModule().programs().uid(stage?.program()?.uid()) - .blockingGet() - val hasCoordinates = stage?.featureType() != null && stage.featureType() != FeatureType.NONE - val hasNonDefaultCatCombo = d2.categoryModule().categoryCombos() - .uid(program?.categoryComboUid()).blockingGet()?.isDefault != true - return hasCoordinates || hasNonDefaultCatCombo - } -} diff --git a/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStore.kt b/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStore.kt index 0ba894599e..4381d11c82 100644 --- a/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStore.kt +++ b/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStore.kt @@ -18,11 +18,6 @@ interface ValueStore { fun deleteOptionValues(optionCodeValuesToDelete: List) fun deleteOptionValueIfSelected(field: String, optionUid: String): StoreResult - fun deleteOptionValueIfSelectedInGroup( - field: String, - optionGroupUid: String, - isInGroup: Boolean, - ): StoreResult fun overrideProgram(programUid: String?) fun validate(dataElementUid: String, value: String?): ValidatorResult diff --git a/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStoreImpl.kt b/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStoreImpl.kt index ebd4b20bd7..8ec8091e42 100644 --- a/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStoreImpl.kt +++ b/app/src/main/java/org/dhis2/data/forms/dataentry/ValueStoreImpl.kt @@ -258,37 +258,6 @@ class ValueStoreImpl( } } - override fun deleteOptionValueIfSelectedInGroup( - field: String, - optionGroupUid: String, - isInGroup: Boolean, - ): StoreResult { - val optionsInGroup = - d2.optionModule().optionGroups() - .withOptions() - .uid(optionGroupUid) - .blockingGet() - ?.options() - ?.map { d2.optionModule().options().uid(it.uid()).blockingGet()?.code()!! } - ?: arrayListOf() - return when (entryMode) { - EntryMode.DE -> deleteDataElementValueIfNotInGroup( - field, - optionsInGroup, - isInGroup, - ) - EntryMode.ATTR -> deleteAttributeValueIfNotInGroup( - field, - optionsInGroup, - isInGroup, - ) - EntryMode.DV, - -> throw IllegalArgumentException( - "DataValues can't be saved using these arguments. Use the other one.", - ) - } - } - private fun deleteDataElementValue(field: String, optionUid: String): StoreResult { val option = d2.optionModule().options().uid(optionUid).blockingGet() val possibleValues = arrayListOf(option?.name(), option?.code()).filterNotNull() @@ -317,38 +286,6 @@ class ValueStoreImpl( } } - private fun deleteDataElementValueIfNotInGroup( - field: String, - optionCodesToShow: List, - isInGroup: Boolean, - ): StoreResult { - val valueRepository = - d2.trackedEntityModule().trackedEntityDataValues().value(recordUid, field) - return if (valueRepository.blockingExists() && - optionCodesToShow.contains(valueRepository.blockingGet()?.value()) == isInGroup - ) { - save(field, null).blockingFirst() - } else { - StoreResult(field, ValueStoreResult.VALUE_HAS_NOT_CHANGED) - } - } - - private fun deleteAttributeValueIfNotInGroup( - field: String, - optionCodesToShow: List, - isInGroup: Boolean, - ): StoreResult { - val valueRepository = - d2.trackedEntityModule().trackedEntityAttributeValues().value(field, recordUid) - return if (valueRepository.blockingExists() && - optionCodesToShow.contains(valueRepository.blockingGet()?.value()) == isInGroup - ) { - save(field, null).blockingFirst() - } else { - StoreResult(field, ValueStoreResult.VALUE_HAS_NOT_CHANGED) - } - } - override fun deleteOptionValues(optionCodeValuesToDelete: List) { when (entryMode) { EntryMode.DE -> deleteOptionValuesForEvents(optionCodeValuesToDelete) diff --git a/app/src/main/java/org/dhis2/data/service/SyncGranularWorker.kt b/app/src/main/java/org/dhis2/data/service/SyncGranularWorker.kt index 96122ae22f..02a769c62e 100644 --- a/app/src/main/java/org/dhis2/data/service/SyncGranularWorker.kt +++ b/app/src/main/java/org/dhis2/data/service/SyncGranularWorker.kt @@ -25,10 +25,18 @@ package org.dhis2.data.service +import android.app.NotificationChannel +import android.app.NotificationManager import android.content.Context +import android.content.pm.ServiceInfo +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.work.ForegroundInfo import androidx.work.Worker import androidx.work.WorkerParameters import org.dhis2.App +import org.dhis2.R import org.dhis2.commons.Constants.ATTRIBUTE_OPTION_COMBO import org.dhis2.commons.Constants.CATEGORY_OPTION_COMBO import org.dhis2.commons.Constants.CONFLICT_TYPE @@ -36,9 +44,10 @@ import org.dhis2.commons.Constants.ORG_UNIT import org.dhis2.commons.Constants.PERIOD_ID import org.dhis2.commons.Constants.UID import org.dhis2.commons.sync.ConflictType -import java.util.Objects import javax.inject.Inject +private const val GRANULAR_CHANNEL = "sync_granular_notification" +private const val SYNC_GRANULAR_ID = 8071988 class SyncGranularWorker( context: Context, workerParams: WorkerParameters, @@ -48,12 +57,18 @@ class SyncGranularWorker( internal lateinit var presenter: SyncPresenter override fun doWork(): Result { - Objects.requireNonNull((applicationContext as App).userComponent())!! - .plus(SyncGranularRxModule()).inject(this) + (applicationContext as App).userComponent() + ?.plus(SyncGranularRxModule())?.inject(this) val uid = inputData.getString(UID) ?: return Result.failure() val conflictType = inputData.getString(CONFLICT_TYPE)?.let { ConflictType.valueOf(it) } + triggerNotification( + title = applicationContext.getString(R.string.app_name), + content = applicationContext.getString(R.string.syncing_data), + progress = 0, + ) + val result = when (conflictType) { ConflictType.PROGRAM -> { presenter.blockSyncGranularProgram(uid) @@ -79,6 +94,47 @@ class SyncGranularWorker( ) else -> Result.failure() } + + cancelNotification() return result } + + private fun triggerNotification(title: String, content: String, progress: Int) { + val notificationManager = + applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val mChannel = NotificationChannel( + GRANULAR_CHANNEL, + "GranularSync", + NotificationManager.IMPORTANCE_HIGH, + ) + notificationManager.createNotificationChannel(mChannel) + } + val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder( + applicationContext, + GRANULAR_CHANNEL, + ) + .setSmallIcon(R.drawable.ic_sync) + .setContentTitle(title) + .setContentText(content) + .setOngoing(true) + .setOnlyAlertOnce(true) + .setAutoCancel(false) + .setProgress(100, progress, true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + setForegroundAsync( + ForegroundInfo( + SYNC_GRANULAR_ID, + notificationBuilder.build(), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0, + ), + ) + } + + private fun cancelNotification() { + val notificationManager = NotificationManagerCompat.from( + applicationContext, + ) + notificationManager.cancel(SYNC_GRANULAR_ID) + } } diff --git a/app/src/main/java/org/dhis2/data/service/SyncPresenterImpl.kt b/app/src/main/java/org/dhis2/data/service/SyncPresenterImpl.kt index 0da82acab8..9e1acd36e8 100644 --- a/app/src/main/java/org/dhis2/data/service/SyncPresenterImpl.kt +++ b/app/src/main/java/org/dhis2/data/service/SyncPresenterImpl.kt @@ -215,7 +215,7 @@ class SyncPresenterImpl( ).andThen( Completable.fromObservable( d2.fileResourceModule().fileResourceDownloader() - .byDomainType().eq(FileResourceDomainType.CUSTOM_ICON) + .byDomainType().eq(FileResourceDomainType.ICON) .download(), ), ).blockingAwait() diff --git a/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetDetail/DataSetDetailFragment.kt b/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetDetail/DataSetDetailFragment.kt index e570fbd008..cf0fa562b4 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetDetail/DataSetDetailFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetDetail/DataSetDetailFragment.kt @@ -20,6 +20,7 @@ import org.hisp.dhis.android.core.common.ObjectStyle import org.hisp.dhis.android.core.dataset.DataSetInstance import org.hisp.dhis.android.core.period.Period import org.hisp.dhis.android.core.period.PeriodType +import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor import java.util.Date import java.util.Locale import javax.inject.Inject @@ -149,7 +150,7 @@ class DataSetDetailFragment private constructor() : FragmentGlobalAbstract(), Da override fun setStyle(style: ObjectStyle?) { style?.let { binding.composeDataSetIcon.setUpMetadataIcon( - metadataIconProvider(style), + metadataIconProvider(style, SurfaceColor.Primary), ) } } diff --git a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListAdapter.kt b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListAdapter.kt index d7ed9797e9..235945ccaf 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListAdapter.kt +++ b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListAdapter.kt @@ -3,6 +3,11 @@ package org.dhis2.usescases.datasets.datasetDetail.datasetList import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter @@ -13,6 +18,7 @@ import org.dhis2.usescases.datasets.datasetDetail.DataSetDetailModel import org.dhis2.usescases.datasets.datasetDetail.datasetList.mapper.DatasetCardMapper import org.hisp.dhis.mobile.ui.designsystem.component.ListCard import org.hisp.dhis.mobile.ui.designsystem.component.ListCardTitleModel +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing class DataSetListAdapter( val viewModel: DataSetListViewModel, @@ -48,16 +54,28 @@ class DataSetListAdapter( viewModel.openDataSet(it) }, ) - ListCard( - listAvatar = card.avatar, - title = ListCardTitleModel(text = card.title), - lastUpdated = card.lastUpdated, - additionalInfoList = card.additionalInfo, - actionButton = card.actionButton, - expandLabelText = card.expandLabelText, - shrinkLabelText = card.shrinkLabelText, - onCardClick = card.onCardCLick, - ) + Column( + modifier = Modifier + .padding( + start = Spacing.Spacing8, + end = Spacing.Spacing8, + bottom = Spacing.Spacing4, + ), + ) { + if (position == 0) { + Spacer(modifier = Modifier.size(Spacing.Spacing8)) + } + ListCard( + listAvatar = card.avatar, + title = ListCardTitleModel(text = card.title), + lastUpdated = card.lastUpdated, + additionalInfoList = card.additionalInfo, + actionButton = card.actionButton, + expandLabelText = card.expandLabelText, + shrinkLabelText = card.shrinkLabelText, + onCardClick = card.onCardCLick, + ) + } } holder.bind(it, viewModel) diff --git a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListViewModel.kt b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListViewModel.kt index 3439625446..9eb943de26 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListViewModel.kt +++ b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/datasetList/DataSetListViewModel.kt @@ -23,7 +23,7 @@ class DataSetListViewModel( schedulerProvider: SchedulerProvider, val filterManager: FilterManager, val matomoAnalyticsController: MatomoAnalyticsController, - private val dispatcher: DispatcherProvider, + dispatcher: DispatcherProvider, ) : ViewModel() { @@ -45,13 +45,12 @@ class DataSetListViewModel( filterManager.asFlowable() .startWith(filterManager) .flatMap { filterManager: FilterManager -> - dataSetDetailRepository.dataSetGroups( filterManager.orgUnitUidsFilters, filterManager.periodFilters, filterManager.stateFilters, filterManager.catOptComboFilters, - ) + ).subscribeOn(schedulerProvider.io()) } .subscribeOn(schedulerProvider.io()) .observeOn(schedulerProvider.ui()) diff --git a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentActivity.kt b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentActivity.kt index b5952cf2a4..e9f318a053 100644 --- a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentActivity.kt @@ -10,7 +10,6 @@ import androidx.databinding.DataBindingUtil import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.dhis2.App import org.dhis2.R -import org.dhis2.commons.Constants import org.dhis2.commons.Constants.ENROLLMENT_UID import org.dhis2.commons.Constants.PROGRAM_UID import org.dhis2.commons.Constants.TEI_UID @@ -32,7 +31,6 @@ import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialogUiModel import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle import org.dhis2.usescases.events.ScheduledEventActivity import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.EventCaptureActivity -import org.dhis2.usescases.eventsWithoutRegistration.eventInitial.EventInitialActivity import org.dhis2.usescases.general.ActivityGlobalAbstract import org.dhis2.usescases.teiDashboard.TeiDashboardMobileActivity import org.dhis2.utils.granularsync.OPEN_ERROR_LOCATION @@ -194,35 +192,15 @@ class EnrollmentActivity : ActivityGlobalAbstract(), EnrollmentView { if (presenter.isEventScheduleOrSkipped(eventUid)) { val scheduleEventIntent = ScheduledEventActivity.getIntent(this, eventUid) openEventForResult.launch(scheduleEventIntent) - } else if (presenter.openInitial(eventUid)) { - val bundle = EventInitialActivity.getBundle( - presenter.getProgram()?.uid(), - eventUid, - null, - presenter.getEnrollment()!!.trackedEntityInstance(), - null, - presenter.getEnrollment()!!.organisationUnit(), - presenter.getEventStage(eventUid), - presenter.getEnrollment()!!.uid(), - 0, - presenter.getEnrollment()!!.status(), - ) - val eventInitialIntent = Intent(abstracContext, EventInitialActivity::class.java) - eventInitialIntent.putExtras(bundle) - startActivityForResult(eventInitialIntent, RQ_EVENT) } else { val eventCreationIntent = Intent(abstracContext, EventCaptureActivity::class.java) eventCreationIntent.putExtras( EventCaptureActivity.getActivityBundle( eventUid, presenter.getProgram()?.uid() ?: "", - EventMode.CHECK, + EventMode.NEW, ), ) - eventCreationIntent.putExtra( - Constants.TRACKED_ENTITY_INSTANCE, - presenter.getEnrollment()!!.trackedEntityInstance(), - ) startActivityForResult(eventCreationIntent, RQ_EVENT) } } @@ -352,9 +330,6 @@ class EnrollmentActivity : ActivityGlobalAbstract(), EnrollmentView { binding.enrollmentStatus = status } - override fun showStatusOptions(currentStatus: EnrollmentStatus) { - } - /*endregion*/ override fun requestFocus() { diff --git a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentPresenterImpl.kt b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentPresenterImpl.kt index e0bfac9128..a87db3bff1 100644 --- a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentPresenterImpl.kt +++ b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentPresenterImpl.kt @@ -19,7 +19,6 @@ import org.dhis2.utils.analytics.AnalyticsHelper import org.dhis2.utils.analytics.DELETE_AND_BACK import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.arch.repositories.`object`.ReadOnlyOneObjectRepositoryFinalImpl -import org.hisp.dhis.android.core.common.FeatureType import org.hisp.dhis.android.core.common.Geometry import org.hisp.dhis.android.core.common.State import org.hisp.dhis.android.core.enrollment.Enrollment @@ -50,7 +49,6 @@ class EnrollmentPresenterImpl( private val eventCollectionRepository: EventCollectionRepository, private val teiAttributesProvider: TeiAttributesProvider, ) { - private var finishing: Boolean = false private val disposable = CompositeDisposable() private val backButtonProcessor: FlowableProcessor = PublishProcessor.create() private var hasShownIncidentDateEditionWarning = false @@ -168,6 +166,7 @@ class EnrollmentPresenterImpl( ), ) } + EnrollmentActivity.EnrollmentMode.CHECK -> view.setResultAndFinish() } } @@ -178,29 +177,12 @@ class EnrollmentPresenterImpl( view.showDateEditionWarning() } } - if (finishing) { - view.performSaveClick() - } - finishing = false } fun backIsClicked() { backButtonProcessor.onNext(true) } - fun openInitial(eventUid: String): Boolean { - val catComboUid = getProgram()?.categoryComboUid() - val event = d2.eventModule().events().uid(eventUid).blockingGet() - val stage = d2.programModule().programStages().uid(event?.programStage()).blockingGet() - val needsCatCombo = programRepository.blockingGet()?.categoryComboUid() != null && - d2.categoryModule().categoryCombos().uid(catComboUid) - .blockingGet()?.isDefault == false - val needsCoordinates = - stage?.featureType() != null && stage.featureType() != FeatureType.NONE - - return needsCatCombo || needsCoordinates - } - fun getEnrollment(): Enrollment? { return enrollmentObjectRepository.blockingGet() } @@ -224,8 +206,6 @@ class EnrollmentPresenterImpl( } } - fun hasAccess() = getProgram()?.access()?.data()?.write() ?: false - fun saveEnrollmentGeometry(geometry: Geometry?) { enrollmentObjectRepository.setGeometry(geometry) } @@ -258,13 +238,6 @@ class EnrollmentPresenterImpl( } } - fun setFinishing() { - finishing = true - } - - fun getEventStage(eventUid: String) = - enrollmentFormRepository.getProgramStageUidFromEvent(eventUid) - fun showOrHideSaveButton() { val teiUid = teiRepository.blockingGet()?.uid() ?: "" val programUid = getProgram()?.uid() ?: "" diff --git a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentView.kt b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentView.kt index bc0c10c7b0..9a953b3bd7 100644 --- a/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentView.kt +++ b/app/src/main/java/org/dhis2/usescases/enrollment/EnrollmentView.kt @@ -9,7 +9,6 @@ interface EnrollmentView : AbstractActivityContracts.View { fun setAccess(access: Boolean?) fun renderStatus(status: EnrollmentStatus) - fun showStatusOptions(currentStatus: EnrollmentStatus) fun setSaveButtonVisible(visible: Boolean) diff --git a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventActivity.kt b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventActivity.kt index 85f18d0d2c..28952bf12a 100644 --- a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventActivity.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.databinding.DataBindingUtil import org.dhis2.App import org.dhis2.R -import org.dhis2.commons.data.EventCreationType import org.dhis2.commons.date.DateUtils import org.dhis2.commons.dialogs.PeriodDialog import org.dhis2.databinding.ActivityEventScheduledBinding @@ -21,7 +20,6 @@ import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.models.EventIn import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.ProvideInputDate import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.ProvidePeriodSelector import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.willShowCalendar -import org.dhis2.usescases.eventsWithoutRegistration.eventInitial.EventInitialActivity import org.dhis2.usescases.general.ActivityGlobalAbstract import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.event.EventStatus @@ -237,28 +235,11 @@ class ScheduledEventActivity : ActivityGlobalAbstract(), ScheduledEventContract. } } - override fun openInitialActivity() { - val bundle = EventInitialActivity.getBundle( - program.uid(), - event.uid(), - EventCreationType.DEFAULT.name, - presenter.getEventTei(), - stage.periodType(), - presenter.getEnrollment()?.organisationUnit(), - stage.uid(), - event.enrollment(), - stage.standardInterval() ?: 0, - presenter.getEnrollment()?.status(), - ) - startActivity(Intent(this, EventInitialActivity::class.java).apply { putExtras(bundle) }) - finish() - } - override fun openFormActivity() { val bundle = EventCaptureActivity.getActivityBundle( event.uid(), program.uid(), - EventMode.CHECK, + EventMode.SCHEDULE, ) Intent(activity, EventCaptureActivity::class.java).apply { putExtras(bundle) diff --git a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventContract.kt b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventContract.kt index 052a792c2a..dfc435ff34 100644 --- a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventContract.kt +++ b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventContract.kt @@ -16,7 +16,6 @@ class ScheduledEventContract { fun setEvent(event: Event) fun setStage(programStage: ProgramStage, event: Event) fun setProgram(program: Program) - fun openInitialActivity() fun openFormActivity() } diff --git a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventModule.kt b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventModule.kt index 8f54a87090..ff27377572 100644 --- a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventModule.kt +++ b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventModule.kt @@ -3,7 +3,6 @@ package org.dhis2.usescases.events import dagger.Module import dagger.Provides import org.dhis2.commons.di.dagger.PerActivity -import org.dhis2.data.dhislogic.DhisEventUtils import org.hisp.dhis.android.core.D2 @Module @@ -13,8 +12,7 @@ class ScheduledEventModule(val eventUid: String, val view: ScheduledEventContrac @PerActivity internal fun providePresenter( d2: D2, - eventUtils: DhisEventUtils, ): ScheduledEventContract.Presenter { - return ScheduledEventPresenterImpl(view, d2, eventUid, eventUtils) + return ScheduledEventPresenterImpl(view, d2, eventUid) } } diff --git a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventPresenterImpl.kt b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventPresenterImpl.kt index 0815cde501..fc8cebc39c 100644 --- a/app/src/main/java/org/dhis2/usescases/events/ScheduledEventPresenterImpl.kt +++ b/app/src/main/java/org/dhis2/usescases/events/ScheduledEventPresenterImpl.kt @@ -3,11 +3,10 @@ package org.dhis2.usescases.events import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable -import org.dhis2.data.dhislogic.DhisEventUtils +import org.dhis2.commons.date.DateUtils import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.DEFAULT_MAX_DATE import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.DEFAULT_MIN_DATE import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.InputDateValues -import org.dhis2.utils.DateUtils import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.arch.helpers.UidsHelper import org.hisp.dhis.android.core.category.CategoryOption @@ -25,7 +24,6 @@ class ScheduledEventPresenterImpl( val view: ScheduledEventContract.View, val d2: D2, val eventUid: String, - val eventUtils: DhisEventUtils, ) : ScheduledEventContract.Presenter { private lateinit var disposable: CompositeDisposable @@ -84,11 +82,7 @@ class ScheduledEventPresenterImpl( override fun setEventDate(date: Date) { d2.eventModule().events().uid(eventUid).setEventDate(date) d2.eventModule().events().uid(eventUid).setStatus(EventStatus.ACTIVE) - if (eventUtils.newEventNeedsExtraInfo(eventUid)) { - view.openInitialActivity() - } else { - view.openFormActivity() - } + view.openFormActivity() } override fun formatDateValues(date: InputDateValues): Date { 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 b0c1f1338a..d16cbe5515 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 @@ -67,9 +67,8 @@ class EventCaptureActivity : TEIDataActivityContract { private lateinit var binding: ActivityEventCaptureBinding - @JvmField @Inject - var presenter: EventCaptureContract.Presenter? = null + override lateinit var presenter: EventCaptureContract.Presenter @JvmField @Inject @@ -124,8 +123,8 @@ class EventCaptureActivity : } } showProgress() - presenter!!.initNoteCounter() - presenter!!.init() + presenter.initNoteCounter() + presenter.init() binding.syncButton.setOnClickListener { showSyncDialog(EVENT_SYNC) } if (intent.shouldLaunchSyncDialog()) { @@ -227,14 +226,14 @@ class EventCaptureActivity : override fun onResume() { super.onResume() - presenter!!.refreshTabCounters() + presenter.refreshTabCounters() with(dashboardViewModel) { this?.selectedEventUid()?.observe(this@EventCaptureActivity, ::updateLandscapeViewsOnEventChange) } } override fun onDestroy() { - presenter!!.onDettach() + presenter.onDettach() super.onDestroy() } @@ -272,11 +271,11 @@ class EventCaptureActivity : { /*Unused*/ }, - { presenter!!.deleteEvent() }, + { presenter.deleteEvent() }, ) dialog.show(supportFragmentManager, AlertBottomDialog::class.java.simpleName) } else if (isFormScreen()) { - presenter?.emitAction(EventCaptureAction.ON_BACK) + presenter.emitAction(EventCaptureAction.ON_BACK) } else { finishDataEntry() } @@ -284,7 +283,7 @@ class EventCaptureActivity : private fun isFormScreen(): Boolean { return if (this.isPortrait()) { - adapter?.isFormScreenShown(binding.eventViewPager?.currentItem) == true + adapter?.isFormScreenShown(binding.eventViewPager.currentItem) == true } else { true } @@ -292,7 +291,7 @@ class EventCaptureActivity : override fun updatePercentage(primaryValue: Float) { binding.completion.setCompletionPercentage(primaryValue) - if (!presenter!!.completionPercentageVisibility) { + if (!presenter.getCompletionPercentageVisibility()) { binding.completion.visibility = View.GONE } } @@ -328,15 +327,15 @@ class EventCaptureActivity : } } - override fun SaveAndFinish() { + override fun saveAndFinish() { displayMessage(getString(R.string.saved)) setAction(FormBottomDialog.ActionType.FINISH) } override fun attemptToSkip() { instance - .setAccessDataWrite(presenter!!.canWrite()) - .setIsExpired(presenter!!.hasExpired()) + .setAccessDataWrite(presenter.canWrite()) + .setIsExpired(presenter.hasExpired()) .setSkip(true) .setListener { actionType: FormBottomDialog.ActionType -> setAction(actionType) } .show(supportFragmentManager, SHOW_OPTIONS) @@ -344,8 +343,8 @@ class EventCaptureActivity : override fun attemptToReschedule() { instance - .setAccessDataWrite(presenter!!.canWrite()) - .setIsExpired(presenter!!.hasExpired()) + .setAccessDataWrite(presenter.canWrite()) + .setIsExpired(presenter.hasExpired()) .setReschedule(true) .setListener { actionType: FormBottomDialog.ActionType -> setAction(actionType) } .show(supportFragmentManager, SHOW_OPTIONS) @@ -355,12 +354,12 @@ class EventCaptureActivity : when (actionType) { FormBottomDialog.ActionType.COMPLETE -> { isEventCompleted = true - presenter!!.completeEvent(false) + presenter.completeEvent(false) } - FormBottomDialog.ActionType.COMPLETE_ADD_NEW -> presenter!!.completeEvent(true) + FormBottomDialog.ActionType.COMPLETE_ADD_NEW -> presenter.completeEvent(true) FormBottomDialog.ActionType.FINISH_ADD_NEW -> restartDataEntry() - FormBottomDialog.ActionType.SKIP -> presenter!!.skipEvent() + FormBottomDialog.ActionType.SKIP -> presenter.skipEvent() FormBottomDialog.ActionType.RESCHEDULE -> { // Do nothing } @@ -401,29 +400,15 @@ class EventCaptureActivity : finish() } - override fun renderInitialInfo( - stageName: String, - eventDate: String, - orgUnit: String, - catOption: String, - ) { + override fun renderInitialInfo(stageName: String) { binding.programStageName.text = stageName - val eventDataString = StringBuilder(String.format("%s | %s", eventDate, orgUnit)) - if (catOption.isNotEmpty()) { - eventDataString.append(String.format(" | %s", catOption)) - } - binding.eventSecundaryInfo.text = eventDataString - } - - override fun getPresenter(): EventCaptureContract.Presenter { - return presenter!! } override fun showMoreOptions(view: View) { AppMenuHelper.Builder().menu(this, R.menu.event_menu).anchor(view) .onMenuInflated { popupMenu: PopupMenu -> popupMenu.menu.findItem(R.id.menu_delete).isVisible = - presenter!!.canWrite() && presenter!!.isEnrollmentOpen + presenter.canWrite() && presenter.isEnrollmentOpen() popupMenu.menu.findItem(R.id.menu_share).isVisible = false } .onMenuItemClicked { itemId: Int? -> @@ -448,7 +433,7 @@ class EventCaptureActivity : } private fun confirmDeleteEvent() { - presenter?.programStage().let { + presenter.programStage().let { CustomDialog( this, resourceManager.formatWithEventLabel( @@ -465,7 +450,7 @@ class EventCaptureActivity : object : DialogClickListener { override fun onPositive() { analyticsHelper().setEvent(DELETE_EVENT, CLICK, DELETE_EVENT) - presenter!!.deleteEvent() + presenter.deleteEvent() } override fun onNegative() { @@ -482,7 +467,7 @@ class EventCaptureActivity : .setMessage( resourceManager.formatWithEventLabel( R.string.event_label_date_in_future_message, - programStageUid = presenter?.programStage(), + programStageUid = presenter.programStage(), ), ) .setPositiveButton( @@ -556,7 +541,15 @@ class EventCaptureActivity : contextView, R.string.sync_offline_check_connection, Snackbar.LENGTH_SHORT, - ).show() + ).onNoConnectionListener { + val contextView = findViewById(R.id.navigationBar) + Snackbar.make( + contextView, + R.string.sync_offline_check_connection, + Snackbar.LENGTH_SHORT, + ).show() + } + .show() } .show(syncType) } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java deleted file mode 100644 index 0cbab10877..0000000000 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java +++ /dev/null @@ -1,166 +0,0 @@ -package org.dhis2.usescases.eventsWithoutRegistration.eventCapture; - -import androidx.lifecycle.LiveData; - -import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue; -import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCompletionDialog; -import org.dhis2.usescases.general.AbstractActivityContracts; -import org.hisp.dhis.android.core.common.ValidationStrategy; -import org.hisp.dhis.android.core.event.EventStatus; -import org.hisp.dhis.android.core.organisationunit.OrganisationUnit; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Date; -import java.util.List; -import java.util.Map; - -import io.reactivex.Flowable; -import io.reactivex.Observable; -import io.reactivex.Single; - -public class EventCaptureContract { - - public interface View extends AbstractActivityContracts.View { - - void renderInitialInfo(String stageName, String eventDate, String orgUnit, String catOption); - - EventCaptureContract.Presenter getPresenter(); - - void updatePercentage(float primaryValue); - - void showCompleteActions( - boolean canComplete, - Map emptyMandatoryFields, - EventCompletionDialog eventCompletionDialog); - - void restartDataEntry(); - - void finishDataEntry(); - - void SaveAndFinish(); - - void showSnackBar(int messageId, String programStage); - - void attemptToSkip(); - - void attemptToReschedule(); - - void showEventIntegrityAlert(); - - void updateNoteBadge(int numberOfNotes); - - void goBack(); - - void showProgress(); - - void hideProgress(); - - void showNavigationBar(); - - void hideNavigationBar(); - } - - public interface Presenter extends AbstractActivityContracts.Presenter { - - LiveData observeActions(); - - void init(); - - void onBackClick(); - - void attemptFinish(boolean canComplete, - @Nullable String onCompleteMessage, - List errorFields, - Map emptyMandatoryFields, - List warningFields); - - boolean isEnrollmentOpen(); - - void completeEvent(boolean addNew); - - void deleteEvent(); - - void skipEvent(); - - void rescheduleEvent(Date time); - - boolean canWrite(); - - boolean hasExpired(); - - void initNoteCounter(); - - void refreshTabCounters(); - - void hideProgress(); - - void showProgress(); - - boolean getCompletionPercentageVisibility(); - - void emitAction(@NotNull EventCaptureAction onBack); - - String programStage(); - - @Nullable - String getTeiUid(); - - @Nullable - String getEnrollmentUid(); - } - - public interface EventCaptureRepository { - - Flowable eventIntegrityCheck(); - - Flowable programStageName(); - - Flowable eventDate(); - - Flowable orgUnit(); - - Flowable catOption(); - - Observable completeEvent(); - - Flowable eventStatus(); - - boolean isEnrollmentOpen(); - - Observable deleteEvent(); - - Observable updateEventStatus(EventStatus skipped); - - Observable rescheduleEvent(Date time); - - Observable programStage(); - - boolean getAccessDataWrite(); - - boolean isEnrollmentCancelled(); - - boolean isEventEditable(String eventUid); - - Single canReOpenEvent(); - - Observable isCompletedEventExpired(String eventUid); - - Single getNoteCount(); - - boolean showCompletionPercentage(); - - boolean hasAnalytics(); - - boolean hasRelationships(); - - ValidationStrategy validationStrategy(); - - @Nullable - String getTeiUid(); - - @Nullable - String getEnrollmentUid(); - } - -} diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.kt new file mode 100644 index 0000000000..91e9fff76c --- /dev/null +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.kt @@ -0,0 +1,89 @@ +package org.dhis2.usescases.eventsWithoutRegistration.eventCapture + +import androidx.lifecycle.LiveData +import io.reactivex.Flowable +import io.reactivex.Observable +import io.reactivex.Single +import org.dhis2.form.model.EventMode +import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue +import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCompletionDialog +import org.dhis2.usescases.general.AbstractActivityContracts +import org.hisp.dhis.android.core.common.ValidationStrategy +import org.hisp.dhis.android.core.event.EventStatus +import org.hisp.dhis.android.core.organisationunit.OrganisationUnit +import java.util.Date + +class EventCaptureContract { + interface View : AbstractActivityContracts.View { + fun renderInitialInfo(stageName: String) + val presenter: Presenter + fun updatePercentage(primaryValue: Float) + fun showCompleteActions(eventCompletionDialog: EventCompletionDialog) + + fun restartDataEntry() + fun finishDataEntry() + fun saveAndFinish() + fun showSnackBar(messageId: Int, programStage: String) + fun attemptToSkip() + fun attemptToReschedule() + fun showEventIntegrityAlert() + fun updateNoteBadge(numberOfNotes: Int) + fun goBack() + fun showProgress() + fun hideProgress() + fun showNavigationBar() + fun hideNavigationBar() + } + + interface Presenter : AbstractActivityContracts.Presenter { + fun observeActions(): LiveData + fun init() + fun onBackClick() + fun attemptFinish( + canComplete: Boolean, + onCompleteMessage: String?, + errorFields: List, + emptyMandatoryFields: Map, + warningFields: List, + eventMode: EventMode? = null, + ) + + fun isEnrollmentOpen(): Boolean + fun completeEvent(addNew: Boolean) + fun deleteEvent() + fun skipEvent() + fun rescheduleEvent(time: Date) + fun canWrite(): Boolean + fun hasExpired(): Boolean + fun initNoteCounter() + fun refreshTabCounters() + fun hideProgress() + fun showProgress() + fun getCompletionPercentageVisibility(): Boolean + fun emitAction(onBack: EventCaptureAction) + fun programStage(): String + } + + interface EventCaptureRepository { + fun eventIntegrityCheck(): Flowable + fun programStageName(): Flowable + fun orgUnit(): Flowable + fun completeEvent(): Observable + fun eventStatus(): Flowable + val isEnrollmentOpen: Boolean + fun deleteEvent(): Observable + fun updateEventStatus(skipped: EventStatus): Observable + fun rescheduleEvent(time: Date): Observable + fun programStage(): Observable + val accessDataWrite: Boolean + val isEnrollmentCancelled: Boolean + fun isEventEditable(eventUid: String): Boolean + fun canReOpenEvent(): Single + fun isCompletedEventExpired(eventUid: String): Observable + val noteCount: Single + fun showCompletionPercentage(): Boolean + fun hasAnalytics(): Boolean + fun hasRelationships(): Boolean + fun validationStrategy(): ValidationStrategy + } +} diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt index 51c2072858..cc317af7eb 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt @@ -7,17 +7,19 @@ import io.reactivex.Flowable import io.reactivex.disposables.CompositeDisposable import io.reactivex.processors.PublishProcessor import org.dhis2.R -import org.dhis2.bindings.canSkipErrorFix import org.dhis2.commons.prefs.Preference import org.dhis2.commons.prefs.PreferenceProvider import org.dhis2.commons.schedulers.SchedulerProvider import org.dhis2.commons.schedulers.defaultSubscribe +import org.dhis2.form.data.EventRepository +import org.dhis2.form.model.EventMode import org.dhis2.ui.dialogs.bottomsheet.FieldWithIssue import org.dhis2.usescases.eventsWithoutRegistration.EventIdlingResourceSingleton import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.EventCaptureContract.EventCaptureRepository import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.domain.ConfigureEventCompletionDialog import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCaptureInitialInfo import org.hisp.dhis.android.core.common.Unit +import org.hisp.dhis.android.core.common.ValidationStrategy import org.hisp.dhis.android.core.event.EventStatus import timber.log.Timber import java.util.Date @@ -56,9 +58,7 @@ class EventCapturePresenterImpl( compositeDisposable.add( Flowable.zip( eventCaptureRepository.programStageName(), - eventCaptureRepository.eventDate(), eventCaptureRepository.orgUnit(), - eventCaptureRepository.catOption(), ::EventCaptureInitialInfo, ).defaultSubscribe( schedulerProvider, @@ -69,9 +69,6 @@ class EventCapturePresenterImpl( ) view.renderInitialInfo( initialInfo.programStageName, - initialInfo.eventDate, - initialInfo.organisationUnit.displayName(), - initialInfo.categoryOption, ) }, Timber::e, @@ -114,15 +111,21 @@ class EventCapturePresenterImpl( errorFields: List, emptyMandatoryFields: Map, warningFields: List, + eventMode: EventMode?, ) { val eventStatus = eventStatus if (eventStatus != EventStatus.ACTIVE) { setUpActionByStatus(eventStatus) } else { - val validationStrategy = eventCaptureRepository.validationStrategy() - val canSkipErrorFix = validationStrategy.canSkipErrorFix( + val canSkipErrorFix = canSkipErrorFix( hasErrorFields = errorFields.isNotEmpty(), hasEmptyMandatoryFields = emptyMandatoryFields.isNotEmpty(), + hasEmptyEventCreationMandatoryFields = with(emptyMandatoryFields) { + containsValue(EventRepository.EVENT_DETAILS_SECTION_UID) || + containsValue(EventRepository.EVENT_CATEGORY_COMBO_SECTION_UID) + }, + eventMode = eventMode, + validationStrategy = eventCaptureRepository.validationStrategy(), ) val eventCompletionDialog = configureEventCompletionDialog.invoke( errorFields, @@ -132,20 +135,32 @@ class EventCapturePresenterImpl( onCompleteMessage, canSkipErrorFix, ) - view.showCompleteActions( - canComplete && eventCaptureRepository.isEnrollmentOpen, - emptyMandatoryFields, - eventCompletionDialog, - ) + view.showCompleteActions(eventCompletionDialog) } view.showNavigationBar() } + private fun canSkipErrorFix( + hasErrorFields: Boolean, + hasEmptyMandatoryFields: Boolean, + hasEmptyEventCreationMandatoryFields: Boolean, + eventMode: EventMode?, + validationStrategy: ValidationStrategy, + ): Boolean { + return when (validationStrategy) { + ValidationStrategy.ON_COMPLETE -> when (eventMode) { + EventMode.NEW -> !hasEmptyEventCreationMandatoryFields + else -> true + } + ValidationStrategy.ON_UPDATE_AND_INSERT -> !hasErrorFields && !hasEmptyMandatoryFields + } + } + private fun setUpActionByStatus(eventStatus: EventStatus) { when (eventStatus) { EventStatus.COMPLETED -> if (!hasExpired && !eventCaptureRepository.isEnrollmentCancelled) { - view.SaveAndFinish() + view.saveAndFinish() } else { view.finishDataEntry() } @@ -163,11 +178,13 @@ class EventCapturePresenterImpl( } override fun completeEvent(addNew: Boolean) { + EventIdlingResourceSingleton.increment() compositeDisposable.add( eventCaptureRepository.completeEvent() .defaultSubscribe( schedulerProvider, - { + onNext = { + EventIdlingResourceSingleton.decrement() if (addNew) { view.restartDataEntry() } else { @@ -175,7 +192,10 @@ class EventCapturePresenterImpl( view.finishDataEntry() } }, - Timber::e, + onError = { + EventIdlingResourceSingleton.decrement() + Timber.e(it) + }, ), ) } @@ -280,11 +300,4 @@ class EventCapturePresenterImpl( get() = eventCaptureRepository.eventStatus().blockingFirst() override fun programStage(): String = eventCaptureRepository.programStage().blockingFirst() - - override fun getEnrollmentUid(): String? { - return eventCaptureRepository.enrollmentUid - } - override fun getTeiUid(): String? { - return eventCaptureRepository.teiUid - } } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java index 7111c4bf5f..2a0e94832c 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java @@ -2,7 +2,6 @@ import org.dhis2.commons.bindings.SdkExtensionsKt; import org.dhis2.data.dhislogic.AuthoritiesKt; -import org.dhis2.utils.DateUtils; import org.hisp.dhis.android.core.D2; import org.hisp.dhis.android.core.common.BaseIdentifiableObject; import org.hisp.dhis.android.core.common.ValidationStrategy; @@ -72,31 +71,17 @@ public Flowable programStageName() { } @Override - public Flowable eventDate() { - Event currentEvent = getCurrentEvent(); + public Flowable orgUnit() { return Flowable.just( - currentEvent.eventDate() != null ? DateUtils.uiDateFormat().format(currentEvent.eventDate()) : "" + Objects.requireNonNull( + d2.organisationUnitModule() + .organisationUnits() + .uid(getCurrentEvent().organisationUnit()) + .blockingGet() + ) ); } - @Override - public Flowable orgUnit() { - return Flowable.just(d2.organisationUnitModule().organisationUnits().uid(getCurrentEvent().organisationUnit()).blockingGet()); - } - - - @Override - public Flowable catOption() { - return Flowable.just(d2.categoryModule().categoryOptionCombos().uid(getCurrentEvent().attributeOptionCombo())) - .map(categoryOptionComboRepo -> { - if (categoryOptionComboRepo.blockingGet() == null) - return ""; - else - return categoryOptionComboRepo.blockingGet().displayName(); - }) - .map(displayName -> displayName.equals("default") ? "" : displayName); - } - @Override public Observable completeEvent() { return Observable.fromCallable(() -> { @@ -162,8 +147,8 @@ public Single canReOpenEvent() { @Override public Observable isCompletedEventExpired(String eventUid) { return d2.eventModule().eventService().getEditableStatus(eventUid).map(editionStatus -> { - if (editionStatus instanceof EventEditableStatus.NonEditable) { - return ((EventEditableStatus.NonEditable) editionStatus).getReason() == EventNonEditableReason.EXPIRED; + if (editionStatus instanceof EventEditableStatus.NonEditable nonEditableStatus) { + return nonEditableStatus.getReason() == EventNonEditableReason.EXPIRED; } else { return false; } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/domain/ConfigureEventCompletionDialog.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/domain/ConfigureEventCompletionDialog.kt index c85784aa50..3543b4641b 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/domain/ConfigureEventCompletionDialog.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/domain/ConfigureEventCompletionDialog.kt @@ -96,7 +96,7 @@ class ConfigureEventCompletionDialog( WARNING, SUCCESSFUL, -> EventCompletionButtons( - CompleteButton(), + CompleteButton, FormBottomDialog.ActionType.COMPLETE, ) } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java index f3915a35b3..0e00f1c11e 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java @@ -96,7 +96,7 @@ public void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedI return Unit.INSTANCE; }) .onDataIntegrityResult(result -> { - presenter.handleDataIntegrityResult(result); + presenter.handleDataIntegrityResult(result, eventMode); return Unit.INSTANCE; }) .factory(activity.getSupportFragmentManager()) diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormPresenter.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormPresenter.kt index b3d8625fc3..4b491b27c1 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormPresenter.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormPresenter.kt @@ -13,6 +13,7 @@ import org.dhis2.form.data.FieldsWithWarningResult import org.dhis2.form.data.MissingMandatoryResult import org.dhis2.form.data.NotSavedResult import org.dhis2.form.data.SuccessfulResult +import org.dhis2.form.model.EventMode import org.dhis2.usescases.eventsWithoutRegistration.EventIdlingResourceSingleton import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.EventCaptureContract import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.domain.ReOpenEventUseCase @@ -32,7 +33,7 @@ class EventCaptureFormPresenter( private val dispatcherProvider: DispatcherProvider, ) { - fun handleDataIntegrityResult(result: DataIntegrityCheckResult) { + fun handleDataIntegrityResult(result: DataIntegrityCheckResult, eventMode: EventMode? = null) { when (result) { is FieldsWithErrorResult -> activityPresenter.attemptFinish( result.canComplete, @@ -40,6 +41,7 @@ class EventCaptureFormPresenter( result.fieldUidErrorList, result.mandatoryFields, result.warningFields, + eventMode, ) is FieldsWithWarningResult -> activityPresenter.attemptFinish( @@ -48,6 +50,7 @@ class EventCaptureFormPresenter( emptyList(), emptyMap(), result.fieldUidWarningList, + eventMode, ) is MissingMandatoryResult -> activityPresenter.attemptFinish( @@ -56,6 +59,7 @@ class EventCaptureFormPresenter( result.errorFields, result.mandatoryFields, result.warningFields, + eventMode, ) is SuccessfulResult -> activityPresenter.attemptFinish( @@ -96,7 +100,7 @@ class EventCaptureFormPresenter( EventNonEditableReason.EVENT_DATE_IS_NOT_IN_ORGUNIT_RANGE -> resourceManager.getString(R.string.event_date_not_in_orgunit_range) to false EventNonEditableReason.NO_CATEGORY_COMBO_ACCESS -> resourceManager.getString(R.string.edition_no_catcombo_access) to false EventNonEditableReason.ENROLLMENT_IS_NOT_OPEN -> resourceManager.formatWithEnrollmentLabel( - null, + d2.eventModule().events().uid(eventUid).blockingGet()?.program(), R.string.edition_enrollment_is_no_open_V2, 1, ) to false diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialComponent.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialComponent.kt deleted file mode 100644 index c2dc482ebc..0000000000 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialComponent.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventInitialFragment - -import dagger.Subcomponent - -@Subcomponent(modules = [EventCaptureInitialModule::class]) -interface EventCaptureInitialComponent { - fun inject(fragment: EventCaptureInitialFragment) -} diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialFragment.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialFragment.kt deleted file mode 100644 index ee69557db7..0000000000 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialFragment.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventInitialFragment - -import org.dhis2.usescases.general.FragmentGlobalAbstract - -class EventCaptureInitialFragment : FragmentGlobalAbstract() diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialModule.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialModule.kt deleted file mode 100644 index 3a5d2ef621..0000000000 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventInitialFragment/EventCaptureInitialModule.kt +++ /dev/null @@ -1,6 +0,0 @@ -package org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventInitialFragment - -import dagger.Module - -@Module -class EventCaptureInitialModule diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/model/EventCaptureInitialInfo.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/model/EventCaptureInitialInfo.kt index cb20466399..aee1cf565d 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/model/EventCaptureInitialInfo.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/model/EventCaptureInitialInfo.kt @@ -4,7 +4,5 @@ import org.hisp.dhis.android.core.organisationunit.OrganisationUnit data class EventCaptureInitialInfo( val programStageName: String, - val eventDate: String, val organisationUnit: OrganisationUnit, - val categoryOption: String, ) diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/data/EventDetailsRepository.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/data/EventDetailsRepository.kt index 29dcda480d..a70ef1c12c 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/data/EventDetailsRepository.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/data/EventDetailsRepository.kt @@ -3,11 +3,12 @@ package org.dhis2.usescases.eventsWithoutRegistration.eventDetails.data import io.reactivex.Observable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow +import org.dhis2.commons.data.EventCreationType +import org.dhis2.commons.date.DateUtils import org.dhis2.data.dhislogic.AUTH_ALL import org.dhis2.data.dhislogic.AUTH_UNCOMPLETE_EVENT import org.dhis2.form.model.FieldUiModel import org.dhis2.form.ui.FieldViewModelFactory -import org.dhis2.utils.DateUtils import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.arch.repositories.scope.RepositoryScope import org.hisp.dhis.android.core.category.CategoryCombo @@ -36,6 +37,7 @@ class EventDetailsRepository( private val eventUid: String?, private val programStageUid: String?, private val fieldFactory: FieldViewModelFactory?, + private val eventCreationType: EventCreationType, private val onError: (Throwable) -> String?, ) { @@ -148,10 +150,15 @@ class EventDetailsRepository( return d2.organisationUnitModule().organisationUnits() .byProgramUids(listOf(programUid)) .byParentUid().eq(parentUid) - .byOrganisationUnitScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE) + .byOrganisationUnitScope(getOrgUnitScope()) .blockingGet() } + private fun getOrgUnitScope() = when (eventCreationType) { + EventCreationType.REFERAL -> OrganisationUnit.Scope.SCOPE_TEI_SEARCH + else -> OrganisationUnit.Scope.SCOPE_DATA_CAPTURE + } + fun getOrganisationUnit(orgUnitUid: String): OrganisationUnit? { return d2.organisationUnitModule().organisationUnits() .byUid() @@ -160,10 +167,18 @@ class EventDetailsRepository( } fun getOrganisationUnits(): List { - return d2.organisationUnitModule().organisationUnits() - .byOrganisationUnitScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE) - .byProgramUids(listOf(programUid)) - .blockingGet() + val scope = getOrgUnitScope() + return when (scope) { + OrganisationUnit.Scope.SCOPE_DATA_CAPTURE -> + d2.organisationUnitModule().organisationUnits() + .byOrganisationUnitScope(OrganisationUnit.Scope.SCOPE_DATA_CAPTURE) + .byProgramUids(listOf(programUid)) + .blockingGet() + OrganisationUnit.Scope.SCOPE_TEI_SEARCH -> + d2.organisationUnitModule().organisationUnits() + .byProgramUids(listOf(programUid)) + .blockingGet() + } } fun getGeometryModel(): FieldUiModel { diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/domain/ConfigureEventDetails.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/domain/ConfigureEventDetails.kt index 300ad092f4..4026469dd4 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/domain/ConfigureEventDetails.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/domain/ConfigureEventDetails.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.flowOf import org.dhis2.commons.data.EventCreationType import org.dhis2.commons.data.EventCreationType.REFERAL import org.dhis2.commons.resources.MetadataIconProvider +import org.dhis2.ui.toColor import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.data.EventDetailsRepository import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.models.EventDetails import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.EventDetailResourcesProvider @@ -14,6 +15,7 @@ import org.hisp.dhis.android.core.event.Event import org.hisp.dhis.android.core.event.EventEditableStatus.Editable import org.hisp.dhis.android.core.event.EventEditableStatus.NonEditable import org.hisp.dhis.android.core.event.EventStatus.OVERDUE +import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor import java.util.Date class ConfigureEventDetails( @@ -40,11 +42,17 @@ class ConfigureEventDetails( ) val storedEvent = repository.getEvent() val programStage = repository.getProgramStage() + val program = repository.getProgram() return flowOf( EventDetails( name = programStage?.displayName(), description = programStage?.displayDescription(), - metadataIconData = programStage?.style()?.let { metadataIconProvider(programStage.style()) }, + metadataIconData = programStage?.style()?.let { + metadataIconProvider( + programStage.style(), + program?.style()?.color()?.toColor() ?: SurfaceColor.Primary, + ) + }, enabled = isEnable(storedEvent), isEditable = isEditable(), editableReason = getEditableReason(), diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/injection/EventDetailsModule.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/injection/EventDetailsModule.kt index dafd987db5..daf4ee8309 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/injection/EventDetailsModule.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/injection/EventDetailsModule.kt @@ -83,6 +83,7 @@ class EventDetailsModule( programUid = programUid, eventUid = eventUid, programStageUid = programStageUid, + eventCreationType = eventCreationType, fieldFactory = FieldViewModelFactoryImpl( UiStyleProviderImpl( FormUiModelColorFactoryImpl(context, colorUtils), diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt index 3ca3f39d94..036a3fe9f7 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt @@ -239,7 +239,11 @@ class EventDetailsFragment : FragmentGlobalAbstract() { detailsEnabled = details.enabled, onDateClick = {}, onDateSelected = { dateValues -> - viewModel.onDateSet(dateValues.year, dateValues.month - 1, dateValues.day) + viewModel.onDateSet( + dateValues.year, + dateValues.month - 1, + dateValues.day, + ) }, onClear = { viewModel.onClearEventReportDate() }, required = true, @@ -298,7 +302,10 @@ class EventDetailsFragment : FragmentGlobalAbstract() { ) } } else if (!catCombo.isDefault) { - ProvideEmptyCategorySelector(name = catCombo.displayName ?: getString(R.string.cat_combo), option = getString(R.string.no_options)) + ProvideEmptyCategorySelector( + name = catCombo.displayName ?: getString(R.string.cat_combo), + option = getString(R.string.no_options), + ) } ProvideCoordinates( coordinates = coordinates, @@ -339,7 +346,20 @@ class EventDetailsFragment : FragmentGlobalAbstract() { ) .singleSelection() .orgUnitScope( - OrgUnitSelectorScope.ProgramCaptureScope(viewModel.eventOrgUnit.value.programUid!!), + when (getEventCreationType(requireArguments().getString(EVENT_CREATION_TYPE))) { + EventCreationType.REFERAL -> + OrgUnitSelectorScope.ProgramSearchScope( + viewModel.eventOrgUnit.value.programUid!!, + ) + + EventCreationType.DEFAULT, + EventCreationType.ADDNEW, + EventCreationType.SCHEDULE, + -> + OrgUnitSelectorScope.ProgramCaptureScope( + viewModel.eventOrgUnit.value.programUid!!, + ) + }, ) .onSelection { selectedOrgUnits -> viewModel.setUpOrgUnit(selectedOrgUnit = selectedOrgUnits.firstOrNull()?.uid()) diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java index 78c2b7adf3..010541dad7 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java @@ -3,13 +3,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.dhis2.R; import org.dhis2.commons.resources.MetadataIconProvider; import org.dhis2.data.forms.FormSectionViewModel; -import org.dhis2.mobileProgramRules.RuleEngineHelper; import org.dhis2.form.model.FieldUiModel; import org.dhis2.form.model.OptionSetConfiguration; import org.dhis2.form.ui.FieldViewModelFactory; +import org.dhis2.mobileProgramRules.RuleEngineHelper; import org.dhis2.ui.MetadataIconData; import org.dhis2.utils.Result; import org.hisp.dhis.android.core.D2; @@ -59,7 +58,6 @@ public class EventInitialRepositoryImpl implements EventInitialRepository { private final String stageUid; private final MetadataIconProvider metadataIconProvider; - EventInitialRepositoryImpl(String eventUid, String stageUid, D2 d2, @@ -303,9 +301,9 @@ public Flowable> list() { @Override public Flowable> calculate() { - if(ruleEngineHelper!=null) { + if (ruleEngineHelper != null) { return Flowable.just(ruleEngineHelper.evaluate()).map(Result::success); - }else{ + } else { return Flowable.just(Result.success(new ArrayList<>())); } } @@ -323,6 +321,7 @@ private FieldUiModel transform(@NonNull ProgramStageDataElement stage, DataEleme String formName = dataElement.displayFormName(); String description = dataElement.displayDescription(); + OptionSetConfiguration optionSetConfig = null; if (optionSet != null) { List