diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt index 42f4a5bf0e..2c099a831a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistry.kt @@ -18,8 +18,6 @@ package org.smartregister.fhircore.engine.configuration import android.content.Context import android.database.SQLException -import android.graphics.Bitmap -import androidx.compose.runtime.mutableStateMapOf import ca.uhn.fhir.context.ConfigurationException import ca.uhn.fhir.context.FhirContext import ca.uhn.fhir.parser.DataFormatException @@ -98,7 +96,6 @@ constructor( val configsJsonMap = mutableMapOf() val configCacheMap = mutableMapOf() - val decodedImageMap = mutableStateMapOf() val localizationHelper: LocalizationHelper by lazy { LocalizationHelper(this) } private val supportedFileExtensions = listOf("json", "properties") private var _isNonProxy = BuildConfig.IS_NON_PROXY_APK diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt index 89ff99b756..ff865ce566 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/QuestionnaireConfig.kt @@ -66,6 +66,7 @@ data class QuestionnaireConfig( val managingEntityRelationshipCode: String? = null, val uniqueIdAssignment: UniqueIdAssignmentConfig? = null, val linkIds: List? = null, + val showSubmitAnywayButton: String = "false", ) : java.io.Serializable, Parcelable { fun interpolate(computedValuesMap: Map) = @@ -100,6 +101,7 @@ data class QuestionnaireConfig( uniqueIdAssignment?.copy(linkId = uniqueIdAssignment.linkId.interpolate(computedValuesMap)), linkIds = linkIds?.onEach { it.linkId.interpolate(computedValuesMap) }, saveButtonText = saveButtonText?.interpolate(computedValuesMap), + showSubmitAnywayButton = showSubmitAnywayButton.interpolate(computedValuesMap), ) } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ListProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ListProperties.kt index 30ba712769..2f4db021bf 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ListProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ListProperties.kt @@ -69,4 +69,5 @@ data class ListResourceConfig( val sortConfig: SortConfig? = null, val fhirPathExpression: String? = null, val relatedResources: List = emptyList(), + val isRevInclude: Boolean = true, ) : Parcelable, java.io.Serializable diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt index 65fff51f32..860a518339 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/StackViewProperties.kt @@ -36,7 +36,7 @@ data class StackViewProperties( override val clickable: String = "false", override val visible: String = "true", val opacity: Float = 0f, - val size: Int? = 0, + val size: Int = 0, val children: List = emptyList(), ) : ViewProperties(), Parcelable { override fun interpolate(computedValuesMap: Map): StackViewProperties { diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt index acba6336e1..abccc74501 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/data/local/DefaultRepository.kt @@ -1070,14 +1070,20 @@ constructor( if (currentPage != null && pageSize != null) { val fromIndex = currentPage * pageSize val toIndex = (currentPage + 1) * pageSize - with(searchResults.subList(fromIndex, min(toIndex, searchResults.size))) { - mapResourceToRepositoryResourceData( - relatedResourcesConfig = relatedResourcesConfig, - configComputedRuleValues = configComputedRuleValues, - secondaryResourceConfigs = secondaryResourceConfigs, - filterActiveResources = filterActiveResources, - baseResourceConfig = baseResourceConfig, - ) + val maxSublistIndex = min(toIndex, searchResults.size) + + if (fromIndex < maxSublistIndex) { + with(searchResults.subList(fromIndex, maxSublistIndex)) { + mapResourceToRepositoryResourceData( + relatedResourcesConfig = relatedResourcesConfig, + configComputedRuleValues = configComputedRuleValues, + secondaryResourceConfigs = secondaryResourceConfigs, + filterActiveResources = filterActiveResources, + baseResourceConfig = baseResourceConfig, + ) + } + } else { + emptyList() } } else { searchResults.mapResourceToRepositoryResourceData( diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt index 97bf938c12..47b2dabfb3 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutor.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateMap import com.google.android.fhir.datacapture.extensions.logicalId +import javax.inject.Inject import org.hl7.fhir.r4.model.Resource import org.jeasy.rules.api.Facts import org.smartregister.fhircore.engine.configuration.view.ListProperties @@ -29,7 +30,6 @@ import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.domain.model.RuleConfig import org.smartregister.fhircore.engine.domain.model.ViewType import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid -import javax.inject.Inject /** * This class is used to fire rules used to extract and manipulate data from FHIR resources. @@ -115,7 +115,7 @@ class ResourceDataRulesExecutor @Inject constructor(val rulesFactory: RulesFacto this.forEach { baseListResource -> val relatedResourcesQueue = ArrayDeque>>().apply { - addFirst(Pair(baseListResource, listOf(listResourceConfig))) + addFirst(Pair(baseListResource, listResourceConfig.relatedResources)) } val listItemRelatedResources = mutableMapOf>() diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/RulesFactory.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/RulesFactory.kt index 1ff6e122b1..d9177e10af 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/RulesFactory.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/rulesengine/RulesFactory.kt @@ -183,6 +183,7 @@ constructor( relatedResourceKey: String, referenceFhirPathExpression: String?, relatedResourcesMap: Map>? = null, + isRevInclude: Boolean = true, ): List { val value: List = relatedResourcesMap?.get(relatedResourceKey) @@ -192,14 +193,23 @@ constructor( emptyList() } - return if (referenceFhirPathExpression.isNullOrEmpty()) { - value + if (referenceFhirPathExpression.isNullOrEmpty()) { + return value + } + + // Reverse search; look for related resource that references the provided resource + return if (isRevInclude) { + value.filter { res -> + fhirPathDataExtractor.extractData(res, referenceFhirPathExpression).all { + resource.logicalId == it.primitiveValue().extractLogicalIdUuid() + } + } } else { - value.filter { - resource.logicalId == - fhirPathDataExtractor - .extractValue(it, referenceFhirPathExpression) - .extractLogicalIdUuid() + // Forward search; extract value provided resource, then search resources with matching id + value.filter { res -> + fhirPathDataExtractor.extractData(resource, referenceFhirPathExpression).all { + res.logicalId == it.primitiveValue().extractLogicalIdUuid() + } } } } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepositoryTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepositoryTest.kt index 1dc0027224..142cebf0f6 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepositoryTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/data/local/register/RegisterRepositoryTest.kt @@ -183,6 +183,50 @@ class RegisterRepositoryTest : RobolectricTest() { } } + @Ignore("Refactor this test") + @Test + fun countRegisterDataWithParamsAndRelatedEntityLocationFilter() { + runTest { + val paramsList = + arrayListOf( + ActionParameter( + key = "paramsName", + paramType = ActionParameterType.PARAMDATA, + value = "testing1", + dataType = DataType.STRING, + linkId = null, + ), + ActionParameter( + key = "paramName2", + paramType = ActionParameterType.PARAMDATA, + value = "testing2", + dataType = DataType.STRING, + linkId = null, + ), + ) + paramsList + .asSequence() + .filter { it.paramType == ActionParameterType.PARAMDATA && it.value.isNotEmpty() } + .associate { it.key to it.value } + val paramsMap = emptyMap() + val searchSlot = slot() + every { + registerRepository.retrieveRegisterConfiguration(PATIENT_REGISTER, emptyMap()) + } returns + RegisterConfiguration( + appId = "app", + id = PATIENT_REGISTER, + fhirResource = fhirResourceConfig(), + filterDataByRelatedEntityLocation = true, + ) + coEvery { fhirEngine.count(capture(searchSlot)) } returns 20 + val recordsCount = + registerRepository.countRegisterData(registerId = PATIENT_REGISTER, paramsMap = paramsMap) + Assert.assertEquals(ResourceType.Group, searchSlot.captured.type) + Assert.assertEquals(20, recordsCount) + } + } + @Test fun testLoadRegisterDataWithForwardAndReverseIncludedResources() = runTest(timeout = 90.seconds) { diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/RulesFactoryTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/RulesFactoryTest.kt index 95b342dabb..d303149695 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/RulesFactoryTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/RulesFactoryTest.kt @@ -72,6 +72,7 @@ import org.smartregister.fhircore.engine.rule.CoroutineTestRule import org.smartregister.fhircore.engine.rulesengine.services.LocationService import org.smartregister.fhircore.engine.util.DispatcherProvider import org.smartregister.fhircore.engine.util.extension.SDF_YYYY_MM_DD +import org.smartregister.fhircore.engine.util.extension.asReference import org.smartregister.fhircore.engine.util.extension.plusYears import org.smartregister.fhircore.engine.util.fhirpath.FhirPathDataExtractor @@ -298,6 +299,29 @@ class RulesFactoryTest : RobolectricTest() { Assert.assertEquals("careplan-1", result[0].logicalId) } + @Test + fun retrieveRelatedResourcesReturnsCorrectResourceWithForwardInclude() { + val patient = Faker.buildPatient() + val group = + Group().apply { + id = "grp1" + addMember( + Group.GroupMemberComponent().apply { entity = patient.asReference() }, + ) + } + populateFactsWithResources(group) + val result = + rulesEngineService.retrieveRelatedResources( + resource = group, + relatedResourceKey = ResourceType.Patient.name, + referenceFhirPathExpression = "Group.member.entity.reference", + isRevInclude = false, + ) + Assert.assertEquals(1, result.size) + Assert.assertEquals("Patient", result[0].resourceType.name) + Assert.assertEquals(patient.logicalId, result[0].logicalId) + } + @Test fun retrieveRelatedResourcesWithoutReferenceReturnsResources() { populateFactsWithResources() @@ -887,13 +911,16 @@ class RulesFactoryTest : RobolectricTest() { Assert.assertTrue(result.isEmpty()) } - private fun populateFactsWithResources() { + private fun populateFactsWithResources(vararg resource: Resource = emptyArray()) { val carePlanRelatedResource = mutableListOf(Faker.buildCarePlan()) - val patientRelatedResource = mutableListOf(Faker.buildPatient()) + val patient = Faker.buildPatient() + val patientRelatedResource = mutableListOf(patient) + val facts = ReflectionHelpers.getField(rulesFactory, "facts") facts.apply { put(carePlanRelatedResource[0].resourceType.name, carePlanRelatedResource) put(patientRelatedResource[0].resourceType.name, patientRelatedResource) + resource.forEach { put(it.resourceType.name, it) } } ReflectionHelpers.setField(rulesFactory, "facts", facts) } diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 6dea09e86e..0cc0cac3ef 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -26,7 +26,7 @@ easyRulesCore = "4.1.1-SNAPSHOT" espresso-core = "3.6.1" fhir-sdk-contrib-barcode = "0.1.0-beta3-preview7-rc1-SNAPSHOT" fhir-sdk-contrib-locationwidget = "0.1.0-alpha01-preview2-rc1-SNAPSHOT" -fhir-sdk-data-capture = "1.1.0-preview14-rc3-SNAPSHOT" +fhir-sdk-data-capture = "1.2.0-preview-1-SNAPSHOT" fhir-sdk-engine = "1.0.0-preview14-rc3-SNAPSHOT" fhir-sdk-knowledge = "0.1.0-alpha03-preview5-rc1-SNAPSHOT" fhir-sdk-workflow = "0.1.0-alpha04-preview10-rc1-SNAPSHOT" @@ -49,7 +49,7 @@ kotlinx-coroutines = "1.9.0" kotlinx-serialization-json = "1.6.0" kt3k-coveralls-ver="2.12.0" ktlint = "0.50.0" -kujaku-library = "0.10.6-2-SNAPSHOT" +kujaku-library = "0.10.7-SNAPSHOT" kujaku-mapbox-sdk-turf = "7.2.0" leakcanary-android = "2.10" lifecycle= "2.8.5" diff --git a/android/quest/build.gradle.kts b/android/quest/build.gradle.kts index 537bdce8f5..1d22c6e27b 100644 --- a/android/quest/build.gradle.kts +++ b/android/quest/build.gradle.kts @@ -321,11 +321,18 @@ android { manifestPlaceholders["appLabel"] = "PSI WFA" } - create("eusm") { + create("eusmMg") { dimension = "apps" - applicationIdSuffix = ".eusm" - versionNameSuffix = "-eusm" - manifestPlaceholders["appLabel"] = "EUSM" + applicationIdSuffix = ".eusmMg" + versionNameSuffix = "-eusmMg" + manifestPlaceholders["appLabel"] = "EUSM Madagascar" + } + + create("eusmBi") { + dimension = "apps" + applicationIdSuffix = ".eusmBi" + versionNameSuffix = "-eusmBi" + manifestPlaceholders["appLabel"] = "EUSM Burundi" } create("demoEir") { diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/AppDrawerTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/AppDrawerTest.kt index 72a8151442..9f11816f26 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/AppDrawerTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/AppDrawerTest.kt @@ -184,6 +184,7 @@ class AppDrawerTest { appVersionPair = Pair(1, "0.0.1"), onCountUnSyncedResources = {}, unSyncedResourceCount = remember { mutableIntStateOf(0) }, + decodeImage = null, ) } } diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/TopScreenSectionTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/TopScreenSectionTest.kt index 0210d52b4e..11316b4c6f 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/TopScreenSectionTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/main/components/TopScreenSectionTest.kt @@ -52,6 +52,7 @@ class TopScreenSectionTest { navController = TestNavHostController(LocalContext.current), isSearchBarVisible = true, onClick = {}, + decodeImage = null, ) } @@ -83,6 +84,7 @@ class TopScreenSectionTest { navController = TestNavHostController(LocalContext.current), isSearchBarVisible = true, onClick = {}, + decodeImage = null, ) } @@ -116,6 +118,7 @@ class TopScreenSectionTest { navController = TestNavHostController(LocalContext.current), isSearchBarVisible = true, onClick = {}, + decodeImage = null, ) } @@ -135,6 +138,7 @@ class TopScreenSectionTest { navController = TestNavHostController(LocalContext.current), isSearchBarVisible = true, onClick = {}, + decodeImage = null, ) } composeTestRule.onNodeWithTag(TRAILING_QR_SCAN_ICON_BUTTON_TEST_TAG).assertDoesNotExist() @@ -150,6 +154,7 @@ class TopScreenSectionTest { navController = TestNavHostController(LocalContext.current), isSearchBarVisible = true, onClick = {}, + decodeImage = null, ) } composeTestRule.onNodeWithTag(TRAILING_QR_SCAN_ICON_BUTTON_TEST_TAG).assertIsDisplayed() diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/ProfileScreenTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/ProfileScreenTest.kt index 92d6d47417..f3ef783bfa 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/ProfileScreenTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/ProfileScreenTest.kt @@ -75,8 +75,8 @@ class ProfileScreenTest { ProfileScreen( navController = rememberNavController(), profileUiState = profileUiState, - onEvent = {}, snackStateFlow = snackBarStateFlow, + onEvent = {}, ) } } diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/components/MemberProfileBottomSheetViewTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/components/MemberProfileBottomSheetViewTest.kt index 035be7a946..3cc6f2a24f 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/components/MemberProfileBottomSheetViewTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/profile/components/MemberProfileBottomSheetViewTest.kt @@ -50,6 +50,7 @@ class MemberProfileBottomSheetViewTest { buttonProperties = buttonProperties, onViewProfile = { /*Do nothing*/}, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + decodeImage = null, ) } } diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/components/RegisterCardListTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/components/RegisterCardListTest.kt index 0eb987fcc1..b8b15cb234 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/components/RegisterCardListTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/components/RegisterCardListTest.kt @@ -60,6 +60,7 @@ class RegisterCardListTest { registerUiState = RegisterUiState(), currentPage = mutableStateOf(1), onSearchByQrSingleResultAction = {}, + decodeImage = decodeImage, ) } @@ -85,6 +86,7 @@ class RegisterCardListTest { registerUiState = RegisterUiState(), currentPage = mutableStateOf(1), onSearchByQrSingleResultAction = {}, + decodeImage = decodeImage, ) } @@ -117,6 +119,7 @@ class RegisterCardListTest { currentPage = mutableStateOf(1), showPagination = true, onSearchByQrSingleResultAction = {}, + decodeImage = decodeImage, ) } @@ -143,6 +146,7 @@ class RegisterCardListTest { registerUiState = RegisterUiState(), currentPage = mutableStateOf(1), onSearchByQrSingleResultAction = {}, + decodeImage = decodeImage, ) } diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ActionableButtonTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ActionableButtonTest.kt index 8a84deab04..33995a3828 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ActionableButtonTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ActionableButtonTest.kt @@ -123,6 +123,7 @@ class ActionableButtonTest { ), resourceData = ResourceData("id", ResourceType.Patient, computedValuesMap), navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } } diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ExtendedFabTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ExtendedFabTest.kt index 79dc736a41..ff7e86c534 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ExtendedFabTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ExtendedFabTest.kt @@ -63,6 +63,7 @@ class ExtendedFabTest { resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = TestNavHostController(LocalContext.current), lazyListState = null, + decodeImage = null, ) } } @@ -90,6 +91,7 @@ class ExtendedFabTest { resourceData = null, navController = TestNavHostController(LocalContext.current), lazyListState = null, + decodeImage = null, ) } composeRule @@ -150,6 +152,7 @@ class ExtendedFabTest { resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = TestNavHostController(LocalContext.current), lazyListState = null, + decodeImage = null, ) } composeRule.run { @@ -188,6 +191,7 @@ class ExtendedFabTest { resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = TestNavHostController(LocalContext.current), lazyListState = null, + decodeImage = null, ) } composeRule.run { diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ServiceCardTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ServiceCardTest.kt index 32bcd09ae9..840b68933f 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ServiceCardTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ServiceCardTest.kt @@ -46,6 +46,7 @@ class ServiceCardTest { serviceCardProperties = initTestServiceCardProperties(), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = decodeImage, ) } composeRule @@ -62,6 +63,7 @@ class ServiceCardTest { initTestServiceCardProperties(serviceStatus = ServiceStatus.OVERDUE.name, text = "1"), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = decodeImage, ) } composeRule.onNodeWithText("1", useUnmergedTree = true).assertExists().assertIsDisplayed() @@ -74,6 +76,7 @@ class ServiceCardTest { serviceCardProperties = initTestServiceCardProperties(visible = "false"), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = decodeImage, ) } composeRule.onNodeWithText("Next visit 09-10-2022", useUnmergedTree = true).assertDoesNotExist() @@ -86,6 +89,7 @@ class ServiceCardTest { serviceCardProperties = initTestServiceCardProperties(showVerticalDivider = false), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = decodeImage, ) } composeRule.onNodeWithTag(DIVIDER_TEST_TAG).assertDoesNotExist() diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ViewGeneratorTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ViewGeneratorTest.kt index 5a0f402ff8..54c19c934a 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ViewGeneratorTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/ViewGeneratorTest.kt @@ -383,7 +383,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), - decodedImageMap = decodedImageMap, + decodeImage = decodedImageMap, ) } composeRule diff --git a/android/quest/src/eusm/res/drawable/ic_app_logo.png b/android/quest/src/eusmBi/res/drawable/ic_app_logo.png similarity index 100% rename from android/quest/src/eusm/res/drawable/ic_app_logo.png rename to android/quest/src/eusmBi/res/drawable/ic_app_logo.png diff --git a/android/quest/src/eusm/res/drawable/ic_launcher.png b/android/quest/src/eusmBi/res/drawable/ic_launcher.png similarity index 100% rename from android/quest/src/eusm/res/drawable/ic_launcher.png rename to android/quest/src/eusmBi/res/drawable/ic_launcher.png diff --git a/android/quest/src/eusmMg/res/drawable/ic_app_logo.png b/android/quest/src/eusmMg/res/drawable/ic_app_logo.png new file mode 100644 index 0000000000..c054c39f8e Binary files /dev/null and b/android/quest/src/eusmMg/res/drawable/ic_app_logo.png differ diff --git a/android/quest/src/eusmMg/res/drawable/ic_launcher.png b/android/quest/src/eusmMg/res/drawable/ic_launcher.png new file mode 100644 index 0000000000..8da44beb41 Binary files /dev/null and b/android/quest/src/eusmMg/res/drawable/ic_launcher.png differ diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/data/DataMigration.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/data/DataMigration.kt index a565ea5d29..09ec272474 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/data/DataMigration.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/data/DataMigration.kt @@ -212,7 +212,7 @@ constructor( if (migrationConfig.createLocalChangeEntitiesAfterPurge) { defaultRepository.addOrUpdate(resource = updatedResource as Resource) } else { - defaultRepository.createRemote(resource = *arrayOf(updatedResource as Resource)) + defaultRepository.createRemote(resource = arrayOf(updatedResource as Resource)) } } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/bottomsheet/SummaryBottomSheetView.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/bottomsheet/SummaryBottomSheetView.kt index 08eee50cc7..4842fc5186 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/bottomsheet/SummaryBottomSheetView.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/bottomsheet/SummaryBottomSheetView.kt @@ -17,8 +17,6 @@ package org.smartregister.fhircore.quest.ui.bottomsheet import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.remember import androidx.navigation.NavController import org.smartregister.fhircore.engine.configuration.view.ViewProperties import org.smartregister.fhircore.engine.domain.model.ResourceData @@ -34,6 +32,6 @@ fun SummaryBottomSheetView( viewProperties = properties, resourceData = resourceData, navController = navController, - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt index 18c1046818..348231fcf0 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherFragment.kt @@ -155,6 +155,7 @@ class GeoWidgetLauncherFragment : Fragment(), OnSyncListener { navController = findNavController(), unSyncedResourceCount = appMainViewModel.unSyncedResourcesCount, onCountUnSyncedResources = appMainViewModel::updateUnSyncedResourcesCount, + decodeImage = { geoWidgetLauncherViewModel.getImageBitmap(it) }, ) }, snackbarHost = { snackBarHostState -> @@ -189,6 +190,7 @@ class GeoWidgetLauncherFragment : Fragment(), OnSyncListener { isFirstTimeSync = geoWidgetLauncherViewModel.isFirstTime(), appDrawerUIState = appDrawerUIState, onAppMainEvent = appMainViewModel::onEvent, + decodeImage = { TODO("Return bitmap") }, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherScreen.kt index e2d016c7b9..0c91a2b3c8 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherScreen.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.geowidget +import android.graphics.Bitmap import android.view.View import android.widget.FrameLayout import androidx.compose.foundation.layout.Box @@ -58,6 +59,7 @@ fun GeoWidgetLauncherScreen( search: (String) -> Unit, isFirstTimeSync: Boolean, appDrawerUIState: AppDrawerUIState, + decodeImage: ((String) -> Bitmap?)?, onAppMainEvent: (AppMainEvent) -> Unit, ) { val context = LocalContext.current @@ -87,6 +89,7 @@ fun GeoWidgetLauncherScreen( isFilterIconEnabled = false, topScreenSection = geoWidgetConfiguration.topScreenSection, navController = navController, + decodeImage = decodeImage, ) { event -> when (event) { ToolbarClickEvent.Navigate -> diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherViewModel.kt index 6420fff3d7..3d34c607e5 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/geowidget/GeoWidgetLauncherViewModel.kt @@ -17,6 +17,8 @@ package org.smartregister.fhircore.quest.ui.geowidget import android.content.Context +import android.graphics.Bitmap +import androidx.compose.runtime.mutableStateMapOf import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -29,6 +31,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import kotlinx.serialization.json.JsonPrimitive import org.hl7.fhir.r4.model.Enumerations @@ -55,6 +58,7 @@ import org.smartregister.fhircore.engine.util.extension.retrieveRelatedEntitySyn import org.smartregister.fhircore.geowidget.model.GeoJsonFeature import org.smartregister.fhircore.geowidget.model.Geometry import org.smartregister.fhircore.quest.ui.shared.QuestionnaireHandler +import org.smartregister.fhircore.quest.util.extensions.referenceToBitmap @HiltViewModel class GeoWidgetLauncherViewModel @@ -80,6 +84,8 @@ constructor( val geoJsonFeatures: MutableStateFlow> = MutableStateFlow(emptyList()) + private val decodedImageMap = mutableStateMapOf() + fun retrieveLocations(geoWidgetConfig: GeoWidgetConfiguration, searchText: String?) { viewModelScope.launch { val totalCount = @@ -265,6 +271,10 @@ constructor( .read(SharedPreferenceKey.LAST_SYNC_TIMESTAMP.name, null) .isNullOrEmpty() && applicationConfiguration.usePractitionerAssignedLocationOnSync + fun getImageBitmap(reference: String) = runBlocking { + reference.referenceToBitmap(defaultRepository.fhirEngine, decodedImageMap) + } + private companion object { const val KEY_LATITUDE = "positionLatitude" const val KEY_LONGITUDE = "positionLongitude" diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt index 81c1f8371b..c1c47cdbfd 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt @@ -24,6 +24,7 @@ import android.os.Bundle import android.provider.Settings import android.view.View import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -51,6 +52,8 @@ import org.smartregister.fhircore.engine.domain.model.LauncherType import org.smartregister.fhircore.engine.rulesengine.services.LocationCoordinate import org.smartregister.fhircore.engine.sync.OnSyncListener import org.smartregister.fhircore.engine.sync.SyncListenerManager +import org.smartregister.fhircore.engine.ui.base.AlertDialogue +import org.smartregister.fhircore.engine.ui.base.AlertIntent import org.smartregister.fhircore.engine.ui.base.BaseMultiLanguageActivity import org.smartregister.fhircore.engine.util.DispatcherProvider import org.smartregister.fhircore.engine.util.extension.parcelable @@ -152,6 +155,7 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, } setupLocationServices() + overrideOnBackPressListener() findViewById(R.id.mainScreenProgressBar).apply { visibility = View.GONE } findViewById(R.id.mainScreenProgressBarText).apply { visibility = View.GONE } @@ -300,4 +304,26 @@ open class AppMainActivity : BaseMultiLanguageActivity(), QuestionnaireHandler, } } } + + private fun overrideOnBackPressListener() { + onBackPressedDispatcher.addCallback( + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + val navHostFragment = + (supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment) + if (navHostFragment.childFragmentManager.backStackEntryCount == 0) { + AlertDialogue.showAlert( + this@AppMainActivity, + alertIntent = AlertIntent.CONFIRM, + title = getString(R.string.exit_app), + message = getString(R.string.exit_app_message), + cancellable = false, + confirmButtonListener = { finish() }, + neutralButtonListener = { dialog -> dialog.dismiss() }, + ) + } else navHostFragment.navController.navigateUp() + } + }, + ) + } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt index 0a7fce88db..51a0b66342 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt @@ -52,7 +52,6 @@ import org.smartregister.fhircore.engine.configuration.ConfigType import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.configuration.app.ApplicationConfiguration import org.smartregister.fhircore.engine.configuration.app.SyncStrategy -import org.smartregister.fhircore.engine.configuration.navigation.ICON_TYPE_REMOTE import org.smartregister.fhircore.engine.configuration.navigation.NavigationConfiguration import org.smartregister.fhircore.engine.configuration.navigation.NavigationMenuConfig import org.smartregister.fhircore.engine.configuration.report.measure.MeasureReportConfiguration @@ -86,7 +85,6 @@ import org.smartregister.fhircore.quest.ui.report.measure.worker.MeasureReportMo import org.smartregister.fhircore.quest.ui.shared.models.AppDrawerUIState import org.smartregister.fhircore.quest.ui.shared.models.QuestionnaireSubmission import org.smartregister.fhircore.quest.util.extensions.handleClickEvent -import org.smartregister.fhircore.quest.util.extensions.resourceReferenceToBitMap import org.smartregister.fhircore.quest.util.extensions.schedulePeriodically @HiltViewModel @@ -133,23 +131,6 @@ constructor( configurationRegistry.retrieveConfigurations(ConfigType.MeasureReport) } - fun retrieveIconsAsBitmap() { - viewModelScope.launch(dispatcherProvider.io()) { - navigationConfiguration.clientRegisters - .asSequence() - .filter { - it.menuIconConfig != null && - it.menuIconConfig?.type == ICON_TYPE_REMOTE && - !it.menuIconConfig?.reference.isNullOrBlank() - } - .mapNotNull { it.menuIconConfig!!.reference } - .resourceReferenceToBitMap( - fhirEngine = fhirEngine, - decodedImageMap = configurationRegistry.decodedImageMap, - ) - } - } - fun retrieveAppMainUiState(refreshAll: Boolean = true) { if (refreshAll) { appMainUiState.value = diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/AppDrawer.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/AppDrawer.kt index 2e2d6b3362..e9c82a8d02 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/AppDrawer.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/AppDrawer.kt @@ -17,6 +17,7 @@ package org.smartregister.fhircore.quest.ui.main.components import android.content.Context +import android.graphics.Bitmap import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -124,6 +125,7 @@ fun AppDrawer( appVersionPair: Pair? = null, unSyncedResourceCount: MutableIntState, onCountUnSyncedResources: () -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { val context = LocalContext.current val (versionCode, versionName) = remember { appVersionPair ?: context.appVersion() } @@ -154,6 +156,7 @@ fun AppDrawer( unSyncedResourceCount = unSyncedResourceCount, onSideMenuClick = onSideMenuClick, openDrawer = openDrawer, + decodeImage = decodeImage, ) }, ) { innerPadding -> @@ -180,14 +183,19 @@ fun AppDrawer( imageConfig = navigationMenu.menuIconConfig, title = navigationMenu.display, endText = appUiState.registerCountMap[navigationMenu.id]?.toString() ?: "", - showEndText = navigationMenu.showCount, endTextColor = MenuItemColor, - ) { - openDrawer(false) - onSideMenuClick( - AppMainEvent.TriggerWorkflow(navController = navController, navMenu = navigationMenu), - ) - } + showEndText = navigationMenu.showCount, + onSideMenuClick = { + openDrawer(false) + onSideMenuClick( + AppMainEvent.TriggerWorkflow( + navController = navController, + navMenu = navigationMenu, + ), + ) + }, + decodeImage = decodeImage, + ) } item { @@ -197,6 +205,7 @@ fun AppDrawer( onSideMenuClick = onSideMenuClick, openDrawer = openDrawer, navController = navController, + decodeImage = decodeImage, ) if (navigationConfiguration.staticMenu.isNotEmpty()) Divider(color = DividerColor) } @@ -210,14 +219,19 @@ fun AppDrawer( imageConfig = navigationMenu.menuIconConfig, title = navigationMenu.display, endText = appUiState.registerCountMap[navigationMenu.id]?.toString() ?: "", - showEndText = navigationMenu.showCount, endTextColor = MenuItemColor, - ) { - openDrawer(false) - onSideMenuClick( - AppMainEvent.TriggerWorkflow(navController = navController, navMenu = navigationMenu), - ) - } + showEndText = navigationMenu.showCount, + onSideMenuClick = { + openDrawer(false) + onSideMenuClick( + AppMainEvent.TriggerWorkflow( + navController = navController, + navMenu = navigationMenu, + ), + ) + }, + decodeImage = decodeImage, + ) } } } @@ -231,6 +245,7 @@ private fun NavBottomSection( unSyncedResourceCount: MutableIntState, onSideMenuClick: (AppMainEvent) -> Unit, openDrawer: (Boolean) -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { val currentSyncJobStatus = appDrawerUIState.currentSyncJobStatus val context = LocalContext.current @@ -285,6 +300,7 @@ private fun NavBottomSection( unSyncedResourceCount = unSyncedResourceCount, openDrawer = openDrawer, onSideMenuClick = onSideMenuClick, + decodeImage = decodeImage, ) } else { SyncStatusView( @@ -301,6 +317,7 @@ private fun NavBottomSection( unSyncedResourceCount = unSyncedResourceCount, openDrawer = openDrawer, onSideMenuClick = onSideMenuClick, + decodeImage = decodeImage, ) } } @@ -313,6 +330,7 @@ private fun DefaultSyncStatus( context: Context, unSyncedResourceCount: MutableIntState, openDrawer: (Boolean) -> Unit, + decodeImage: ((String) -> Bitmap?)?, onSideMenuClick: (AppMainEvent) -> Unit, ) { val allDataSynced = unSyncedResourceCount.intValue == 0 @@ -330,6 +348,7 @@ private fun DefaultSyncStatus( SideMenuItem( modifier = Modifier, imageConfig = ImageConfig(type = ICON_TYPE_LOCAL, reference = "ic_sync"), + mainTextColor = if (allDataSynced) Color.White else Color.Unspecified, title = stringResource( if (allDataSynced) { @@ -346,17 +365,18 @@ private fun DefaultSyncStatus( }, subTitleTextColor = SubtitleTextColor, endText = appUiState.lastSyncTime, + endTextColor = if (allDataSynced) SubtitleTextColor else Color.Unspecified, padding = 0, showEndText = true, - endTextColor = if (allDataSynced) SubtitleTextColor else Color.Unspecified, - mainTextColor = if (allDataSynced) Color.White else Color.Unspecified, mainTextBold = !allDataSynced, startIcon = if (allDataSynced) null else Icons.Default.Error, startIconColor = if (allDataSynced) null else WarningColor, - ) { - openDrawer(false) - onSideMenuClick(AppMainEvent.SyncData(context)) - } + onSideMenuClick = { + openDrawer(false) + onSideMenuClick(AppMainEvent.SyncData(context)) + }, + decodeImage = decodeImage, + ) } } @@ -364,6 +384,7 @@ private fun DefaultSyncStatus( private fun OtherPatientsItem( navigationConfiguration: NavigationConfiguration, onSideMenuClick: (AppMainEvent) -> Unit, + decodeImage: ((String) -> Bitmap?)?, openDrawer: (Boolean) -> Unit, navController: NavController, ) { @@ -375,24 +396,26 @@ private fun OtherPatientsItem( stringResource(org.smartregister.fhircore.engine.R.string.other_patients) }, endText = "", + endTextColor = SubtitleTextColor, showEndText = false, endImageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - endTextColor = SubtitleTextColor, - ) { - openDrawer(false) - onSideMenuClick( - AppMainEvent.OpenRegistersBottomSheet( - registersList = navigationConfiguration.bottomSheetRegisters?.registers, - navController = navController, - title = - if (navigationConfiguration.bottomSheetRegisters?.display.isNullOrEmpty()) { - context.getString(org.smartregister.fhircore.engine.R.string.other_patients) - } else { - navigationConfiguration.bottomSheetRegisters?.display - }, - ), - ) - } + onSideMenuClick = { + openDrawer(false) + onSideMenuClick( + AppMainEvent.OpenRegistersBottomSheet( + registersList = navigationConfiguration.bottomSheetRegisters?.registers, + navController = navController, + title = + if (navigationConfiguration.bottomSheetRegisters?.display.isNullOrEmpty()) { + context.getString(org.smartregister.fhircore.engine.R.string.other_patients) + } else { + navigationConfiguration.bottomSheetRegisters?.display + }, + ), + ) + }, + decodeImage = decodeImage, + ) } @Composable @@ -492,6 +515,7 @@ private fun SideMenuItem( startIcon: ImageVector? = null, startIconColor: Color? = null, onSideMenuClick: () -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { Row( horizontalArrangement = Arrangement.SpaceBetween, @@ -521,6 +545,7 @@ private fun SideMenuItem( imageProperties = ImageProperties(imageConfig = imageConfig, size = 32), tint = MenuItemColor, navController = rememberNavController(), + decodeImage = decodeImage, ) } Column { @@ -596,6 +621,7 @@ fun AppDrawerPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -634,6 +660,7 @@ fun AppDrawerWithUnSyncedDataPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(10) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -676,6 +703,7 @@ fun AppDrawerOnSyncCompletePreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -718,6 +746,7 @@ fun AppDrawerOnSyncFailedPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -761,6 +790,7 @@ fun AppDrawerOnSyncRunningPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/TopScreenSection.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/TopScreenSection.kt index acb6571cc7..c56728f026 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/TopScreenSection.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/components/TopScreenSection.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.main.components +import android.graphics.Bitmap import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -40,17 +41,13 @@ import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.FilterAlt import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -113,6 +110,7 @@ fun TopScreenSection( performSearchOnValueChanged: Boolean = true, isFilterIconEnabled: Boolean = false, topScreenSection: TopScreenSectionConfig? = null, + decodeImage: ((String) -> Bitmap?)?, onClick: (ToolbarClickEvent) -> Unit = {}, ) { val currentContext = LocalContext.current @@ -140,7 +138,7 @@ fun TopScreenSection( Icon( when (toolBarHomeNavigation) { ToolBarHomeNavigation.OPEN_DRAWER -> Icons.Filled.Menu - ToolBarHomeNavigation.NAVIGATE_BACK -> Icons.Filled.ArrowBack + ToolBarHomeNavigation.NAVIGATE_BACK -> Icons.AutoMirrored.Filled.ArrowBack }, contentDescription = DRAWER_MENU, tint = Color.White, @@ -157,7 +155,13 @@ fun TopScreenSection( // if menu icons are more than two then we will add a overflow menu for other menu icons // to support m3 guidelines // https://m3.material.io/components/top-app-bar/guidelines#b1b64842-7d88-4c3f-8ffb-4183fe648c9e - SetupToolbarIcons(topScreenSection?.menuIcons, navController, modifier, onClick) + SetupToolbarIcons( + menuIcons = topScreenSection?.menuIcons, + navController = navController, + modifier = modifier, + onClick = onClick, + decodeImage = decodeImage, + ) if (isFilterIconEnabled) Spacer(modifier = Modifier.width(24.dp)) @@ -237,9 +241,7 @@ fun TopScreenSection( when { !searchQuery.isBlank() -> { IconButton( - onClick = { - onSearchTextChanged(SearchQuery.emptyText, performSearchOnValueChanged) - }, + onClick = { onSearchTextChanged(SearchQuery.emptyText, true) }, modifier = modifier.testTag(TRAILING_ICON_BUTTON_TEST_TAG), ) { Icon( @@ -289,18 +291,17 @@ fun SetupToolbarIcons( navController: NavController, modifier: Modifier, onClick: (ToolbarClickEvent) -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { if (menuIcons?.isNotEmpty() == true && menuIcons.size > 2) { - var menuExpanded by remember { mutableStateOf(false) } Row { RenderMenuIcons( menuIcons = menuIcons.take(2), navController = navController, modifier = modifier, onClick = onClick, + decodeImage = decodeImage, ) - // FIXME - Do not use material 3 library for now. We have to use dropdown menu to render the - // other menu icons } } else { menuIcons?.let { @@ -309,6 +310,7 @@ fun SetupToolbarIcons( navController = navController, modifier = modifier, onClick = onClick, + decodeImage = decodeImage, ) } } @@ -320,6 +322,7 @@ fun RenderMenuIcons( navController: NavController, modifier: Modifier, onClick: (ToolbarClickEvent) -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { LazyRow(horizontalArrangement = Arrangement.spacedBy(16.dp)) { items(menuIcons) { @@ -331,6 +334,7 @@ fun RenderMenuIcons( modifier .clickable { onClick(ToolbarClickEvent.Actions(it.actions)) } .testTag(TOP_ROW_TOGGLE_ICON_TEST_tAG), + decodeImage = decodeImage, ) } } @@ -361,6 +365,7 @@ fun TopScreenSectionWithFilterItemOverNinetyNinePreview() { ), ), navController = rememberNavController(), + decodeImage = null, ) } @@ -377,6 +382,7 @@ fun TopScreenSectionWithFilterCountNinetyNinePreview() { onClick = {}, isSearchBarVisible = true, navController = rememberNavController(), + decodeImage = null, ) } @@ -401,6 +407,7 @@ fun TopScreenSectionNoFilterIconPreview() { ImageProperties(imageConfig = ImageConfig(reference = "ic_service_points")), ), ), + decodeImage = null, ) } @@ -426,6 +433,7 @@ fun TopScreenSectionWithFilterIconAndToggleIconPreview() { ImageProperties(imageConfig = ImageConfig(reference = "ic_service_points")), ), ), + decodeImage = null, ) } @@ -442,5 +450,6 @@ fun TopScreenSectionWithToggleIconPreview() { onClick = {}, isSearchBarVisible = true, navController = rememberNavController(), + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragment.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragment.kt index faeabe4d2e..f627153a43 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragment.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragment.kt @@ -63,20 +63,17 @@ class ProfileFragment : Fragment() { with(profileFragmentArgs) { lifecycleScope.launch { profileViewModel.run { - decodeBinaryResourceIconsToBitmap(profileId) retrieveProfileUiState(profileId, resourceId, resourceConfig, params) } } } profileViewModel.refreshProfileDataLiveData.observe(viewLifecycleOwner) { - viewLifecycleOwner.lifecycleScope.launch { - if (it == true) { - with(profileFragmentArgs) { - profileViewModel.retrieveProfileUiState(profileId, resourceId, resourceConfig, params) - } - profileViewModel.refreshProfileDataLiveData.value = null + if (it == true) { + with(profileFragmentArgs) { + profileViewModel.retrieveProfileUiState(profileId, resourceId, resourceConfig, params) } + profileViewModel.refreshProfileDataLiveData.value = null } } @@ -87,9 +84,9 @@ class ProfileFragment : Fragment() { ProfileScreen( navController = findNavController(), profileUiState = profileViewModel.profileUiState.value, - onEvent = profileViewModel::onEvent, snackStateFlow = profileViewModel.snackBarStateFlow, - decodedImageMap = configurationRegistry.decodedImageMap, + onEvent = profileViewModel::onEvent, + decodeImage = { profileViewModel.getImageBitmap(it) }, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileScreen.kt index 04a946015a..1bcc672edd 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileScreen.kt @@ -43,7 +43,7 @@ import androidx.compose.material.Scaffold import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable @@ -89,8 +89,8 @@ fun ProfileScreen( navController: NavController, profileUiState: ProfileUiState, snackStateFlow: SharedFlow, - decodedImageMap: MutableMap = mutableMapOf(), onEvent: (ProfileEvent) -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { val scaffoldState = rememberScaffoldState() val lazyListState = rememberLazyListState() @@ -111,6 +111,7 @@ fun ProfileScreen( lazyListState = lazyListState, onEvent = onEvent, collapsible = false, + decodeImage = decodeImage, ) } else { CustomProfileTopAppBar( @@ -118,6 +119,7 @@ fun ProfileScreen( profileUiState = profileUiState, onEvent = onEvent, lazyListState = lazyListState, + decodeImage = decodeImage, ) } }, @@ -129,6 +131,7 @@ fun ProfileScreen( resourceData = profileUiState.resourceData, navController = navController, lazyListState = lazyListState, + decodeImage = decodeImage, ) } }, @@ -174,7 +177,7 @@ fun ProfileScreen( resourceData = profileUiState.resourceData ?: ResourceData("", ResourceType.Patient, emptyMap()), navController = navController, - decodedImageMap = profileUiState.decodedImageMap, + decodeImage = decodeImage, ) } } @@ -189,6 +192,7 @@ fun CustomProfileTopAppBar( profileUiState: ProfileUiState, onEvent: (ProfileEvent) -> Unit, lazyListState: LazyListState, + decodeImage: ((String) -> Bitmap?)?, ) { val topBarConfig = remember { profileUiState.profileConfiguration?.topAppBar ?: TopBarConfig() } @@ -205,6 +209,7 @@ fun CustomProfileTopAppBar( collapsible = topBarConfig.collapsible, onEvent = onEvent, lazyListState = lazyListState, + decodeImage = decodeImage, ) if (topBarConfig.collapsible) { AnimatedVisibility(visible = lazyListState.isScrollingDown()) { @@ -214,6 +219,7 @@ fun CustomProfileTopAppBar( profileUiState = profileUiState, navController = navController, titleContentPadding = 16, + decodeImage = decodeImage, ) } } else { @@ -223,6 +229,7 @@ fun CustomProfileTopAppBar( profileUiState = profileUiState, navController = navController, titleContentPadding = 0, + decodeImage = decodeImage, ) } } @@ -235,6 +242,7 @@ private fun RenderSimpleAppTopBar( profileUiState: ProfileUiState, navController: NavController, titleContentPadding: Int, + decodeImage: ((String) -> Bitmap?)?, ) { Column( modifier = @@ -249,7 +257,7 @@ private fun RenderSimpleAppTopBar( resourceData = profileUiState.resourceData ?: ResourceData("", ResourceType.Patient, emptyMap()), navController = navController, - decodedImageMap = profileUiState.decodedImageMap, + decodeImage = decodeImage, ) } } @@ -263,6 +271,7 @@ private fun SimpleTopAppBar( profileUiState: ProfileUiState, lazyListState: LazyListState, collapsible: Boolean, + decodeImage: ((String) -> Bitmap?)?, onEvent: (ProfileEvent) -> Unit, ) { TopAppBar( @@ -289,7 +298,7 @@ private fun SimpleTopAppBar( navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon( - Icons.Filled.ArrowBack, + Icons.AutoMirrored.Filled.ArrowBack, null, modifier = modifier.testTag(PROFILE_TOP_BAR_ICON_TEST_TAG), ) @@ -300,6 +309,7 @@ private fun SimpleTopAppBar( profileUiState = profileUiState, onEvent = onEvent, navController = navController, + decodeImage = decodeImage, ) }, elevation = elevation.dp, @@ -312,6 +322,7 @@ private fun ProfileTopAppBarMenuAction( onEvent: (ProfileEvent) -> Unit, navController: NavController, modifier: Modifier = Modifier, + decodeImage: ((String) -> Bitmap?)?, ) { if (!profileUiState.profileConfiguration?.overFlowMenuItems.isNullOrEmpty()) { var showOverflowMenu by remember { mutableStateOf(false) } @@ -363,6 +374,7 @@ private fun ProfileTopAppBarMenuAction( tint = contentColor, navController = navController, resourceData = profileUiState.resourceData!!, + decodeImage = decodeImage, ) if (overflowMenuItemConfig.icon != null) Spacer(modifier = Modifier.width(4.dp)) Text(text = overflowMenuItemConfig.title, color = contentColor) diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileUiState.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileUiState.kt index bbd3ac27fe..624f8b8b15 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileUiState.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileUiState.kt @@ -16,9 +16,6 @@ package org.smartregister.fhircore.quest.ui.profile -import android.graphics.Bitmap -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.snapshots.SnapshotStateMap import org.smartregister.fhircore.engine.configuration.app.SnackBarThemeConfig import org.smartregister.fhircore.engine.configuration.profile.ProfileConfiguration import org.smartregister.fhircore.engine.domain.model.ResourceData @@ -28,5 +25,4 @@ data class ProfileUiState( val profileConfiguration: ProfileConfiguration? = null, val snackBarTheme: SnackBarThemeConfig = SnackBarThemeConfig(), val showDataLoadProgressIndicator: Boolean = true, - val decodedImageMap: SnapshotStateMap = mutableStateMapOf(), ) diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModel.kt index 1673f814d2..ceec5ca81d 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/ProfileViewModel.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.profile +import android.graphics.Bitmap import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshots.SnapshotStateList @@ -30,8 +31,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import org.hl7.fhir.r4.model.Binary import org.hl7.fhir.r4.model.Group import org.hl7.fhir.r4.model.ResourceType import org.smartregister.fhircore.engine.configuration.ConfigType @@ -55,9 +56,8 @@ import org.smartregister.fhircore.engine.util.fhirpath.FhirPathDataExtractor import org.smartregister.fhircore.quest.R import org.smartregister.fhircore.quest.ui.profile.bottomSheet.ProfileBottomSheetFragment import org.smartregister.fhircore.quest.ui.profile.model.EligibleManagingEntity -import org.smartregister.fhircore.quest.util.extensions.decodeImageResourcesToBitmap import org.smartregister.fhircore.quest.util.extensions.handleClickEvent -import org.smartregister.fhircore.quest.util.extensions.resourceReferenceToBitMap +import org.smartregister.fhircore.quest.util.extensions.referenceToBitmap import org.smartregister.fhircore.quest.util.extensions.toParamDataMap import timber.log.Timber @@ -84,73 +84,45 @@ constructor( private val listResourceDataStateMap = mutableStateMapOf>() - /** - * This function retrieves an image that was synced from the backend as a [Binary] resource, the - * content of the Binary resource is a base64 encoding of the actual image. The encoded imaged is - * then transformed into bitmap for use in an Image Composable (returns null if the referenced - * resource doesn't exist) - */ - suspend fun decodeBinaryResourceIconsToBitmap(profileId: String) { - val profileConfig = - configurationRegistry.retrieveConfiguration( - configId = profileId, - configType = ConfigType.Profile, - ) - withContext(dispatcherProvider.io()) { - profileConfig.overFlowMenuItems - .asSequence() - .filter { it.icon != null && !it.icon?.reference.isNullOrBlank() } - .mapNotNull { it.icon!!.reference } - .resourceReferenceToBitMap( - fhirEngine = registerRepository.fhirEngine, - decodedImageMap = configurationRegistry.decodedImageMap, - ) - } - } + private val decodedImageMap = mutableStateMapOf() - suspend fun retrieveProfileUiState( + fun retrieveProfileUiState( profileId: String, resourceId: String, fhirResourceConfig: FhirResourceConfig? = null, paramsList: Array? = emptyArray(), ) { - if (resourceId.isNotEmpty()) { - val repositoryResourceData = - registerRepository.loadProfileData(profileId, resourceId, fhirResourceConfig, paramsList) - val paramsMap: Map = paramsList.toParamDataMap() - val profileConfigs = retrieveProfileConfiguration(profileId, paramsMap) - val resourceData = - resourceDataRulesExecutor - .processResourceData( - repositoryResourceData = repositoryResourceData, - ruleConfigs = profileConfigs.rules, - params = paramsMap, - ) - .copy(listResourceDataMap = listResourceDataStateMap) - - profileUiState.value = - ProfileUiState( - resourceData = resourceData, - profileConfiguration = profileConfigs, - snackBarTheme = applicationConfiguration.snackBarTheme, - showDataLoadProgressIndicator = false, - decodedImageMap = configurationRegistry.decodedImageMap, - ) + viewModelScope.launch { + if (resourceId.isNotEmpty()) { + val repositoryResourceData = + registerRepository.loadProfileData(profileId, resourceId, fhirResourceConfig, paramsList) + val paramsMap: Map = paramsList.toParamDataMap() + val profileConfigs = retrieveProfileConfiguration(profileId, paramsMap) + val resourceData = + resourceDataRulesExecutor + .processResourceData( + repositoryResourceData = repositoryResourceData, + ruleConfigs = profileConfigs.rules, + params = paramsMap, + ) + .copy(listResourceDataMap = listResourceDataStateMap) - profileConfigs.views.retrieveListProperties().forEach { listProperties -> - resourceDataRulesExecutor.processListResourceData( - listProperties = listProperties, - relatedResourcesMap = repositoryResourceData.relatedResourcesMap, - computedValuesMap = resourceData.computedValuesMap.plus(paramsMap), - listResourceDataStateMap = listResourceDataStateMap, - ) - } + profileUiState.value = + ProfileUiState( + resourceData = resourceData, + profileConfiguration = profileConfigs, + snackBarTheme = applicationConfiguration.snackBarTheme, + showDataLoadProgressIndicator = false, + ) - withContext(dispatcherProvider.io()) { - profileConfigs.views.decodeImageResourcesToBitmap( - fhirEngine = registerRepository.fhirEngine, - decodedImageMap = configurationRegistry.decodedImageMap, - ) + profileConfigs.views.retrieveListProperties().forEach { listProperties -> + resourceDataRulesExecutor.processListResourceData( + listProperties = listProperties, + relatedResourcesMap = repositoryResourceData.relatedResourcesMap, + computedValuesMap = resourceData.computedValuesMap.plus(paramsMap), + listResourceDataStateMap = listResourceDataStateMap, + ) + } } } } @@ -292,4 +264,8 @@ constructor( } } } + + fun getImageBitmap(reference: String) = runBlocking { + reference.referenceToBitmap(registerRepository.fhirEngine, decodedImageMap) + } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/components/MemberProfileBottomSheetView.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/components/MemberProfileBottomSheetView.kt index e88d5162a6..bbd421e62c 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/components/MemberProfileBottomSheetView.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/profile/components/MemberProfileBottomSheetView.kt @@ -18,6 +18,7 @@ package org.smartregister.fhircore.quest.ui.profile.components +import android.graphics.Bitmap import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -72,6 +73,7 @@ fun MemberProfileBottomSheetView( resourceData: ResourceData, navController: NavController = rememberNavController(), onViewProfile: () -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { Column { // Top section displays the name, gender and age for member @@ -115,6 +117,7 @@ fun MemberProfileBottomSheetView( buttonProperties = it, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) } Spacer(modifier = modifier.height(8.dp)) @@ -147,6 +150,7 @@ private fun MemberProfileBottomSheetViewPreview() { navController = rememberNavController(), onViewProfile = { /*Do nothing*/}, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + decodeImage = null, ) } @@ -166,5 +170,6 @@ private fun MemberProfileBottomSheetViewWithFormDataPreview() { navController = rememberNavController(), onViewProfile = { /*Do nothing*/}, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt index 97e6fd1886..6595e8bba4 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/questionnaire/QuestionnaireActivity.kt @@ -284,6 +284,7 @@ class QuestionnaireActivity : BaseMultiLanguageActivity() { .showAsterisk(this.questionnaireConfig.showRequiredTextAsterisk) .showRequiredText(this.questionnaireConfig.showRequiredText) .setIsReadOnly(questionnaireConfig.isSummary()) + .setShowSubmitAnywayButton(questionnaireConfig.showSubmitAnywayButton.toBooleanStrict()) .apply { if (questionnaireResponse != null) { questionnaireResponse diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterFragment.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterFragment.kt index 03d1bb1acf..032a90cf1e 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterFragment.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterFragment.kt @@ -87,16 +87,16 @@ class RegisterFragment : Fragment(), OnSyncListener { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - appMainViewModel.retrieveIconsAsBitmap() - with(registerFragmentArgs) { - lifecycleScope.launchWhenCreated { - registerViewModel.retrieveRegisterUiState( - registerId = registerId, - screenTitle = screenTitle, - params = params, - clearCache = false, - ) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { + registerViewModel.retrieveRegisterUiState( + registerId = registerId, + screenTitle = screenTitle, + params = params, + clearCache = false, + ) + } } } return ComposeView(requireContext()).apply { @@ -150,6 +150,7 @@ class RegisterFragment : Fragment(), OnSyncListener { navController = findNavController(), unSyncedResourceCount = appMainViewModel.unSyncedResourcesCount, onCountUnSyncedResources = appMainViewModel::updateUnSyncedResourcesCount, + decodeImage = { registerViewModel.getImageBitmap(it) }, ) }, bottomBar = { @@ -180,6 +181,7 @@ class RegisterFragment : Fragment(), OnSyncListener { pagingItems = pagingItems, navController = findNavController(), toolBarHomeNavigation = registerFragmentArgs.toolBarHomeNavigation, + decodeImage = { registerViewModel.getImageBitmap(it) }, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterScreen.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterScreen.kt index 78ecbd3b93..c08daccc6e 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterScreen.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterScreen.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.register +import android.graphics.Bitmap import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -99,6 +100,7 @@ fun RegisterScreen( pagingItems: LazyPagingItems, navController: NavController, toolBarHomeNavigation: ToolBarHomeNavigation = ToolBarHomeNavigation.OPEN_DRAWER, + decodeImage: ((String) -> Bitmap?)?, ) { val lazyListState: LazyListState = rememberLazyListState() Scaffold( @@ -126,6 +128,7 @@ fun RegisterScreen( isFilterIconEnabled = filterActions?.isNotEmpty() ?: false, topScreenSection = registerUiState.registerConfiguration?.topScreenSection, navController = navController, + decodeImage = decodeImage, ) { event -> when (event) { ToolbarClickEvent.Navigate -> @@ -151,6 +154,7 @@ fun RegisterScreen( fabActions = fabActions, navController = navController, lazyListState = lazyListState, + decodeImage = decodeImage, ) } }, @@ -213,6 +217,7 @@ fun RegisterScreen( } } }, + decodeImage = decodeImage, ) } else { registerUiState.registerConfiguration?.noResults?.let { noResultConfig -> @@ -310,6 +315,7 @@ fun RegisterScreenWithDataPreview() { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterViewModel.kt index 1aa74b6290..21886825ce 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterViewModel.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterViewModel.kt @@ -16,9 +16,11 @@ package org.smartregister.fhircore.quest.ui.register +import android.graphics.Bitmap import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -38,6 +40,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.hl7.fhir.r4.model.Coding import org.hl7.fhir.r4.model.Enumerations.DataType import org.hl7.fhir.r4.model.QuestionnaireResponse @@ -62,6 +65,7 @@ import org.smartregister.fhircore.engine.util.SharedPreferencesHelper import org.smartregister.fhircore.engine.util.extension.encodeJson import org.smartregister.fhircore.quest.data.register.RegisterPagingSource import org.smartregister.fhircore.quest.data.register.model.RegisterPagingSourceState +import org.smartregister.fhircore.quest.util.extensions.referenceToBitmap import org.smartregister.fhircore.quest.util.extensions.toParamDataMap import timber.log.Timber @@ -93,6 +97,7 @@ constructor( val applicationConfiguration: ApplicationConfiguration by lazy { configurationRegistry.retrieveConfiguration(ConfigType.Application, paramsMap = emptyMap()) } + private val decodedImageMap = mutableStateMapOf() /** * This function paginates the register data. An optional [clearCache] resets the data in the @@ -514,4 +519,8 @@ constructor( suspend fun emitSnackBarState(snackBarMessageConfig: SnackBarMessageConfig) { _snackBarStateFlow.emit(snackBarMessageConfig) } + + fun getImageBitmap(reference: String) = runBlocking { + reference.referenceToBitmap(registerRepository.fhirEngine, decodedImageMap) + } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/components/RegisterCardList.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/components/RegisterCardList.kt index 194038b148..4121a7af8d 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/components/RegisterCardList.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/components/RegisterCardList.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.register.components +import android.graphics.Bitmap import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -25,8 +26,6 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.Divider import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp @@ -66,6 +65,7 @@ fun RegisterCardList( currentPage: MutableState, showPagination: Boolean = false, onSearchByQrSingleResultAction: (ResourceData) -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { LazyColumn(modifier = Modifier.testTag(REGISTER_CARD_LIST_TEST_TAG), state = lazyListState) { items( @@ -81,7 +81,7 @@ fun RegisterCardList( viewProperties = registerCardConfig.views, resourceData = pagingItems[index]!!, navController = navController, - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = decodeImage, ) } Divider(color = DividerColor, thickness = 1.dp) diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ActionableButton.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ActionableButton.kt index 103b2795cd..a7f318414d 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ActionableButton.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ActionableButton.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.shared.components +import android.graphics.Bitmap import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -70,6 +71,7 @@ fun ActionableButton( buttonProperties: ButtonProperties, resourceData: ResourceData, navController: NavController, + decodeImage: ((String) -> Bitmap?)?, ) { if (buttonProperties.visible.toBoolean()) { val status = buttonProperties.status @@ -162,6 +164,7 @@ fun ActionableButton( tint = iconTintColor, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) } else { Icon( @@ -219,6 +222,7 @@ fun ActionableButtonPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } @@ -235,6 +239,7 @@ fun ActionableButtonTinyButtonPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } @@ -257,6 +262,7 @@ fun DisabledActionableButtonPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } } @@ -276,6 +282,7 @@ fun SmallActionableButtonPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) ActionableButton( modifier = Modifier.weight(1.0f), @@ -288,6 +295,7 @@ fun SmallActionableButtonPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/CardView.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/CardView.kt index 50942c3266..9ae9070326 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/CardView.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/CardView.kt @@ -27,9 +27,6 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -62,7 +59,7 @@ fun CardView( viewProperties: CardViewProperties, resourceData: ResourceData, navController: NavController, - decodedImageMap: SnapshotStateMap, + decodeImage: ((String) -> Bitmap?)?, ) { // Check if card is visible if (viewProperties.visible.toBoolean()) { @@ -114,7 +111,7 @@ fun CardView( viewProperties = viewProperties.content, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -153,7 +150,7 @@ private fun CardViewWithoutPaddingPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -190,7 +187,7 @@ private fun CardViewWithPaddingPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -213,7 +210,7 @@ private fun CardViewWithoutPaddingAndHeaderPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -281,7 +278,7 @@ private fun CardViewImageWithItems() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ExtendedFab.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ExtendedFab.kt index baff4a9a17..269a56fc0e 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ExtendedFab.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ExtendedFab.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.shared.components +import android.graphics.Bitmap import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -56,6 +57,7 @@ fun ExtendedFab( resourceData: ResourceData? = null, navController: NavController, lazyListState: LazyListState?, + decodeImage: ((String) -> Bitmap?)?, ) { val firstFabAction = remember { fabActions.first() } val firstFabEnabled = @@ -90,6 +92,7 @@ fun ExtendedFab( tint = if (firstFabEnabled) Color.White else DefaultColor, navController = navController, resourceData = resourceData, + decodeImage = decodeImage, ) } if (text.isNotEmpty()) { @@ -126,6 +129,7 @@ fun PreviewDisabledExtendedFab() { ), navController = rememberNavController(), lazyListState = rememberLazyListState(), + decodeImage = null, ) } @@ -143,6 +147,7 @@ fun PreviewExtendedFab() { ), navController = rememberNavController(), lazyListState = rememberLazyListState(), + decodeImage = null, ) } @@ -160,5 +165,6 @@ fun PreviewExtendedFabJustIcon() { ), navController = rememberNavController(), lazyListState = rememberLazyListState(), + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/Image.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/Image.kt index 38e626aa8d..4e628518b3 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/Image.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/Image.kt @@ -58,7 +58,6 @@ import org.smartregister.fhircore.engine.domain.model.ViewType import org.smartregister.fhircore.engine.ui.theme.DangerColor import org.smartregister.fhircore.engine.util.annotation.PreviewWithBackgroundExcludeGenerated import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid -import org.smartregister.fhircore.engine.util.extension.interpolate import org.smartregister.fhircore.engine.util.extension.parseColor import org.smartregister.fhircore.engine.util.extension.retrieveResourceId import org.smartregister.fhircore.quest.ui.main.components.SIDE_MENU_ICON @@ -76,7 +75,7 @@ fun Image( imageProperties: ImageProperties = ImageProperties(viewType = ViewType.IMAGE, size = 24), navController: NavController, resourceData: ResourceData? = null, - decodedImageMap: MutableMap = mutableMapOf(), + decodeImage: ((String) -> Bitmap?)?, ) { val imageConfig = imageProperties.imageConfig val colorTint = tint ?: imageProperties.imageConfig?.color.parseColor() @@ -95,27 +94,25 @@ fun Image( ) ClickableImageIcon( imageProperties = imageProperties, - imageConfig = imageConfig, tint = colorTint, paddingEnd = paddingEnd, navController = navController, resourceData = resourceData, modifier = modifier, context = context, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } else { ClickableImageIcon( imageProperties = imageProperties, - imageConfig = imageConfig, tint = colorTint, paddingEnd = paddingEnd, navController = navController, resourceData = resourceData, modifier = modifier, context = context, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -125,13 +122,12 @@ fun Image( fun ClickableImageIcon( modifier: Modifier = Modifier, imageProperties: ImageProperties, - imageConfig: ImageConfig, tint: Color, paddingEnd: Int?, navController: NavController, resourceData: ResourceData? = null, context: Context? = null, - decodedImageMap: MutableMap = mutableMapOf(), + decodeImage: ((String) -> Bitmap?)?, ) { Box( contentAlignment = Alignment.Center, @@ -167,34 +163,33 @@ fun ClickableImageIcon( }, ), ) { - when (imageConfig.type) { - ICON_TYPE_LOCAL -> { - LocalContext.current.retrieveResourceId(imageConfig.reference)?.let { drawableId -> - Icon( - modifier = - Modifier.testTag(SIDE_MENU_ITEM_LOCAL_ICON_TEST_TAG) - .conditional(paddingEnd != null, { padding(end = paddingEnd?.dp!!) }) - .align(Alignment.Center) - .fillMaxSize(0.9f), - painter = painterResource(id = drawableId), - contentDescription = SIDE_MENU_ICON, - tint = tint, - ) + val imageConfig = + imageProperties.imageConfig?.interpolate( + resourceData?.computedValuesMap ?: emptyMap(), + ) + if (imageConfig != null) { + when (imageConfig.type) { + ICON_TYPE_LOCAL -> { + LocalContext.current.retrieveResourceId(imageConfig.reference)?.let { drawableId -> + Icon( + modifier = + Modifier.testTag(SIDE_MENU_ITEM_LOCAL_ICON_TEST_TAG) + .conditional(paddingEnd != null, { padding(end = paddingEnd?.dp!!) }) + .align(Alignment.Center) + .fillMaxSize(0.9f), + painter = painterResource(id = drawableId), + contentDescription = SIDE_MENU_ICON, + tint = tint, + ) + } } - } - ICON_TYPE_REMOTE -> - if (decodedImageMap.isNotEmpty()) { - val imageType = imageProperties.imageConfig?.imageType + ICON_TYPE_REMOTE -> { + val imageType = imageConfig.imageType val colorFilter = if (imageType == ImageType.SVG || imageType == ImageType.PNG) tint else null - val contentScale = - convertContentScaleTypeToContentScale(imageProperties.imageConfig!!.contentScale) + val contentScale = convertContentScaleTypeToContentScale(imageConfig.contentScale) val decodedImage = - decodedImageMap[ - imageConfig.reference - ?.interpolate(resourceData!!.computedValuesMap) - ?.extractLogicalIdUuid(), - ] + imageConfig.reference?.extractLogicalIdUuid()?.let { decodeImage?.invoke(it) } if (decodedImage != null) { Image( modifier = @@ -204,12 +199,13 @@ fun ClickableImageIcon( .fillMaxSize(0.9f), bitmap = decodedImage.asImageBitmap(), contentDescription = null, - alpha = imageProperties.imageConfig!!.alpha, + alpha = imageConfig.alpha, contentScale = contentScale, colorFilter = colorFilter?.let { ColorFilter.tint(it) }, ) } } + } } } } @@ -242,6 +238,7 @@ fun ImagePreview() { tint = DangerColor.copy(0.1f), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } @@ -262,5 +259,6 @@ fun ClickableImageWithTextPreview() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/List.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/List.kt index 8f8eec434c..3ba37ba761 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/List.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/List.kt @@ -35,8 +35,6 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.Divider import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity @@ -70,7 +68,7 @@ fun List( viewProperties: ListProperties, resourceData: ResourceData, navController: NavController, - decodedImageMap: SnapshotStateMap = mutableStateMapOf(), + decodeImage: ((String) -> Bitmap?)?, ) { val density = LocalDensity.current val currentListResourceData = resourceData.listResourceDataMap?.get(viewProperties.id) @@ -138,7 +136,7 @@ fun List( viewProperties = interpolatedChildViewProperties, resourceData = listResourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, areViewPropertiesInterpolated = true, // Prevents double interpolation (in this function and inside the // ViewRenderer) which is a waste @@ -161,7 +159,7 @@ fun List( viewProperties = viewProperties.registerCard.views, resourceData = listResourceData, navController = navController, - decodedImageMap = mutableStateMapOf(), + decodeImage = decodeImage, ) } } @@ -231,6 +229,7 @@ private fun ListWithHorizontalOrientationPreview() { baseResourceType = ResourceType.Patient, computedValuesMap = emptyMap(), ), + decodeImage = null, ) } } @@ -282,6 +281,7 @@ private fun ListWithVerticalOrientationPreview() { baseResourceType = ResourceType.Patient, computedValuesMap = emptyMap(), ), + decodeImage = null, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/SearchBar.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/SearchBar.kt index 4020c31808..54b085ec52 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/SearchBar.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/SearchBar.kt @@ -30,7 +30,7 @@ import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -72,7 +72,7 @@ fun SearchBar( leadingIcon = { IconButton(onClick = onBackPress) { Icon( - Icons.Filled.ArrowBack, + Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null, modifier = modifier.padding(16.dp), ) diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ServiceCard.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ServiceCard.kt index 5e555fad0a..1bf739a715 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ServiceCard.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ServiceCard.kt @@ -16,6 +16,7 @@ package org.smartregister.fhircore.quest.ui.shared.components +import android.graphics.Bitmap import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -40,7 +41,6 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -83,6 +83,7 @@ fun ServiceCard( serviceCardProperties: ServiceCardProperties, resourceData: ResourceData, navController: NavController, + decodeImage: ((String) -> Bitmap?)?, ) { val serviceMemberIconsTint = serviceCardProperties.serviceMemberIconsTint.parseColor() if (serviceCardProperties.showVerticalDivider) { @@ -125,6 +126,7 @@ fun ServiceCard( serviceCardProperties = serviceCardProperties, navController = navController, resourceData = resourceData, + decodeImage = decodeImage, ) } } else { @@ -162,6 +164,7 @@ fun ServiceCard( serviceCardProperties = serviceCardProperties, navController = navController, resourceData = resourceData, + decodeImage = decodeImage, ) } } @@ -204,9 +207,7 @@ private fun RowScope.RenderDetails( horizontalArrangement = Arrangement.End, ) { memberIcons.forEach { - if ( - it.isNotEmpty() && ServiceMemberIcon.values().map { icon -> icon.name }.contains(it) - ) { + if (it.isNotEmpty() && ServiceMemberIcon.entries.map { icon -> icon.name }.contains(it)) { Icon( painter = painterResource(id = ServiceMemberIcon.valueOf(it).icon), contentDescription = null, @@ -244,6 +245,7 @@ private fun RowScope.RenderActionButtons( serviceCardProperties: ServiceCardProperties, navController: NavController, resourceData: ResourceData, + decodeImage: ((String) -> Bitmap?)?, ) { Box(modifier = Modifier.weight(weight).padding(start = 6.dp)) { if (serviceCardProperties.serviceButton != null || serviceCardProperties.services != null) { @@ -261,6 +263,7 @@ private fun RowScope.RenderActionButtons( buttonProperties = serviceCardProperties.serviceButton!!, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) } } @@ -283,6 +286,7 @@ private fun RowScope.RenderActionButtons( buttonProperties = buttonProperties, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) } } @@ -410,7 +414,7 @@ private fun ServiceCardServiceOverduePreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -466,7 +470,7 @@ private fun ServiceCardServiceOverdueWithBackgroundColorPreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -522,7 +526,7 @@ private fun ServiceCardServiceOverdueWithNoBackgroundColorAndStatusPreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -578,7 +582,7 @@ private fun ServiceCardServiceDuePreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -633,7 +637,7 @@ private fun ServiceCardServiceUpcomingPreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -669,7 +673,7 @@ private fun ServiceCardServiceFamilyMemberPreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -715,7 +719,7 @@ private fun ServiceCardServiceWithTinyServiceButtonPreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -769,7 +773,7 @@ private fun ServiceCardServiceCompletedPreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -817,7 +821,7 @@ private fun ServiceCardANCServiceDuePreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } @@ -875,7 +879,7 @@ private fun ServiceCardANCServiceOverduePreview() { viewProperties = viewProperties, resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/StackView.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/StackView.kt index 47c29bccf6..adb448e6a2 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/StackView.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/StackView.kt @@ -21,8 +21,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -44,15 +42,15 @@ fun StackView( stackViewProperties: StackViewProperties, resourceData: ResourceData, navController: NavController, - decodedImageMap: SnapshotStateMap = mutableStateMapOf(), + decodeImage: ((String) -> Bitmap?)?, ) { val backgroundColor = stackViewProperties.backgroundColor.parseColor() - val size = stackViewProperties.size Box( modifier = - Modifier.background(backgroundColor.copy(alpha = stackViewProperties.opacity)) - .size(size!!.dp) + modifier + .background(backgroundColor.copy(alpha = stackViewProperties.opacity)) + .size(stackViewProperties.size.dp) .testTag(STACK_VIEW_TEST_TAG), contentAlignment = castViewAlignment(stackViewProperties.alignment), ) { @@ -62,7 +60,7 @@ fun StackView( properties = child.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -101,5 +99,6 @@ private fun PreviewStack() { baseResourceType = ResourceType.Patient, computedValuesMap = emptyMap(), ), + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewGenerator.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewGenerator.kt index 4ffcfc9bd9..4c3fc0d129 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewGenerator.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewGenerator.kt @@ -34,9 +34,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -73,7 +71,7 @@ fun GenerateView( properties: ViewProperties, resourceData: ResourceData, navController: NavController, - decodedImageMap: SnapshotStateMap = mutableStateMapOf(), + decodeImage: ((String) -> Bitmap?)?, ) { if (properties.visible.toBoolean()) { when (properties.viewType) { @@ -90,6 +88,7 @@ fun GenerateView( buttonProperties = properties as ButtonProperties, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) ViewType.COLUMN -> { val children = (properties as ColumnProperties).children @@ -112,7 +111,7 @@ fun GenerateView( properties = properties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -151,7 +150,7 @@ fun GenerateView( properties = child.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) if (properties.showDivider.toBoolean() && index < children.lastIndex) { Divider( @@ -185,7 +184,7 @@ fun GenerateView( properties = properties.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -224,7 +223,7 @@ fun GenerateView( properties = child.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -236,6 +235,7 @@ fun GenerateView( serviceCardProperties = properties as ServiceCardProperties, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) ViewType.CARD -> CardView( @@ -243,7 +243,7 @@ fun GenerateView( viewProperties = properties as CardViewProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) ViewType.PERSONAL_DATA -> PersonalDataView( @@ -262,7 +262,7 @@ fun GenerateView( viewProperties = properties as ListProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) ViewType.IMAGE -> Image( @@ -270,7 +270,7 @@ fun GenerateView( imageProperties = properties as ImageProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) ViewType.STACK -> StackView( @@ -278,7 +278,7 @@ fun GenerateView( stackViewProperties = properties as StackViewProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewRenderer.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewRenderer.kt index 276d67c769..8c6fec4ca0 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewRenderer.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewRenderer.kt @@ -18,9 +18,6 @@ package org.smartregister.fhircore.quest.ui.shared.components import android.graphics.Bitmap import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import org.hl7.fhir.r4.model.ResourceType @@ -51,7 +48,7 @@ fun ViewRenderer( viewProperties: List, resourceData: ResourceData, navController: NavController, - decodedImageMap: SnapshotStateMap, + decodeImage: ((String) -> Bitmap?)?, areViewPropertiesInterpolated: Boolean = false, ) { viewProperties.forEach { properties -> @@ -66,7 +63,7 @@ fun ViewRenderer( properties = interpolatedProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -101,7 +98,7 @@ private fun PreviewWeightedViewsInRow() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } @@ -157,7 +154,7 @@ private fun PreviewWrappedViewsInRow() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } @@ -195,7 +192,7 @@ private fun PreviewSameSizedViewInRow() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } @@ -314,6 +311,6 @@ private fun PreviewCardViewWithRows() { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), - decodedImageMap = remember { mutableStateMapOf() }, + decodeImage = null, ) } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensions.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensions.kt index 68c9075c46..3e2c6b58e2 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensions.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensions.kt @@ -261,16 +261,18 @@ fun Array?.toParamDataMap(): Map = ?.filter { it.paramType == ActionParameterType.PARAMDATA } ?.associate { it.key to it.value } ?: emptyMap() -suspend fun Sequence.resourceReferenceToBitMap( +suspend fun String.referenceToBitmap( fhirEngine: FhirEngine, decodedImageMap: SnapshotStateMap, -) { - forEach { - val resourceId = it.extractLogicalIdUuid() + forceRefresh: Boolean = false, +): Bitmap? { + val resourceId = this.extractLogicalIdUuid() + if (!decodedImageMap.containsKey(resourceId) || forceRefresh) { fhirEngine.loadResource(resourceId)?.let { binary -> binary.data.decodeToBitmap()?.let { bitmap -> decodedImageMap[resourceId] = bitmap } } } + return decodedImageMap[resourceId] } suspend fun List.decodeImageResourcesToBitmap( diff --git a/android/quest/src/main/res/values/strings.xml b/android/quest/src/main/res/values/strings.xml index adb04213d9..77345f7495 100644 --- a/android/quest/src/main/res/values/strings.xml +++ b/android/quest/src/main/res/values/strings.xml @@ -133,4 +133,6 @@ Place your camera over the entire QR Code to start scanning Failed to get GPS location \u0020\u002a + Exit App + Are you sure you want to exit from the app? diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt index da6fbea110..cde4637068 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/main/AppMainActivityTest.kt @@ -37,7 +37,6 @@ import io.mockk.slot import io.mockk.spyk import java.io.Serializable import java.time.OffsetDateTime -import kotlin.test.assertNotNull import kotlinx.coroutines.test.runTest import org.hl7.fhir.r4.model.QuestionnaireResponse import org.junit.Assert diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt index 207e7b8f6d..089ab4260e 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/ui/profile/ProfileFragmentTest.kt @@ -27,6 +27,7 @@ import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.runs @@ -147,7 +148,7 @@ class ProfileFragmentTest : RobolectricTest() { questionnaireResponse = questionnaireResponse, ) - coEvery { profileViewModel.retrieveProfileUiState(any(), any(), any(), any()) } just runs + every { profileViewModel.retrieveProfileUiState(any(), any(), any(), any()) } just runs coEvery { profileViewModel.emitSnackBarState(any()) } just runs runBlocking { profileFragment.handleQuestionnaireSubmission(questionnaireSubmission) } diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsKtTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsKtTest.kt index df1ca86bd5..df659b410d 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsKtTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsKtTest.kt @@ -682,13 +682,15 @@ class ConfigExtensionsKtTest : RobolectricTest() { val decodedImageMap = mutableStateMapOf() withContext(dispatcherProvider.io()) { defaultRepository.create(addResourceTags = true, binaryImage) - navigationMenuConfigs.resourceReferenceToBitMap( - fhirEngine = fhirEngine, - decodedImageMap = decodedImageMap, - ) + navigationMenuConfigs.forEach { + it.referenceToBitmap( + fhirEngine = fhirEngine, + decodedImageMap = decodedImageMap, + ) + } + Assert.assertTrue(decodedImageMap.isNotEmpty()) + Assert.assertTrue(decodedImageMap.containsKey("d60ff460-7671-466a-93f4-c93a2ebf2077")) } - Assert.assertTrue(decodedImageMap.isNotEmpty()) - Assert.assertTrue(decodedImageMap.containsKey("d60ff460-7671-466a-93f4-c93a2ebf2077")) } @Test @@ -697,10 +699,12 @@ class ConfigExtensionsKtTest : RobolectricTest() { val decodedImageMap = mutableStateMapOf() withContext(Dispatchers.IO) { defaultRepository.create(addResourceTags = true, binaryImage) - navigationMenuConfigs.resourceReferenceToBitMap( - fhirEngine = fhirEngine, - decodedImageMap = decodedImageMap, - ) + navigationMenuConfigs.forEach { + it.referenceToBitmap( + fhirEngine = fhirEngine, + decodedImageMap = decodedImageMap, + ) + } } Assert.assertTrue(decodedImageMap.isNotEmpty()) Assert.assertTrue(decodedImageMap.containsKey("d60ff460-7671-466a-93f4-c93a2ebf2077")) diff --git a/docs/engineering/app/automated-releases.mdx b/docs/engineering/app/automated-releases.mdx index 56d9c4b4ec..a86c3d1bb7 100644 --- a/docs/engineering/app/automated-releases.mdx +++ b/docs/engineering/app/automated-releases.mdx @@ -62,12 +62,15 @@ To add a flavor, add an entry to the `productFlavors` map in [`android/opensrp/b ``` create("newFlavor") { dimension = "apps" - applicationIdSuffix = ".new-flavor" - versionNameSuffix = "-new-flavor" + applicationIdSuffix = ".newFlavor" + versionNameSuffix = "-newFlavor" manifestPlaceholders["appLabel"] = "New Flavor App Name" } ``` - Note, flavor names should always be camel cased + +:::tip +Remember to use **[camelCase](https://en.wikipedia.org/wiki/Camel_case)** when adding the **flavor name** e.g `newFlavor`, **`applicationIdSuffix`** e.g `.newFlavor` and **`versionNameSuffix`** e.g `-newFlavor` +::: You can add the following resources: diff --git a/docs/engineering/app/configuring/add-application-flavors.mdx b/docs/engineering/app/configuring/add-application-flavors.mdx index 75264870b3..5a8ba0370d 100644 --- a/docs/engineering/app/configuring/add-application-flavors.mdx +++ b/docs/engineering/app/configuring/add-application-flavors.mdx @@ -35,14 +35,18 @@ productFlavors { The product flavors are easily modigied to the specified app flavor e.g. ``` -create("afyayangu") { +create("afyaYangu") { dimension = "apps" - applicationIdSuffix = ".afyayangu" - versionNameSuffix = "-afyayangu" + applicationIdSuffix = ".afyaYangu" + versionNameSuffix = "-afyaYangu" manifestPlaceholders["appLabel"] = "Afya Yangu" } ``` +:::tip +Remember to use **[camelCase](https://en.wikipedia.org/wiki/Camel_case)** when adding the **flavor name** e.g `newFlavor`, **`applicationIdSuffix`** e.g `.newFlavor` and **`versionNameSuffix`** e.g `-newFlavor` +::: + ### Config properties of productFlavors |Property | Description | Required | Default | |--|--|:--:|:--:|