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/view/ButtonProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ButtonProperties.kt index 5e89b02a09..50b073be0c 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ButtonProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ButtonProperties.kt @@ -43,6 +43,7 @@ data class ButtonProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val contentColor: String? = null, val enabled: String = "true", val text: String? = null, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CardViewProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CardViewProperties.kt index 77a1d41581..dad41a6b3a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CardViewProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CardViewProperties.kt @@ -35,6 +35,7 @@ data class CardViewProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "true", override val visible: String = "true", + override val opacity: Float? = null, val content: List = emptyList(), val elevation: Int = 5, val cornerSize: Int = 6, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ColumnProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ColumnProperties.kt index 6053c58459..d19380d0d4 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ColumnProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ColumnProperties.kt @@ -37,6 +37,7 @@ data class ColumnProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val spacedBy: Int = 8, val wrapContent: Boolean = false, val arrangement: ColumnArrangement? = null, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CompoundTextProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CompoundTextProperties.kt index 91f5e04343..a81b7a3b24 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CompoundTextProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/CompoundTextProperties.kt @@ -35,6 +35,7 @@ data class CompoundTextProperties( override val alignment: ViewAlignment = ViewAlignment.NONE, override val fillMaxWidth: Boolean = false, override val fillMaxHeight: Boolean = false, + override val opacity: Float? = null, override val clickable: String = "false", override val visible: String = "true", val primaryText: String? = null, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/DividerProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/DividerProperties.kt index 7c7e7b4529..34500c8929 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/DividerProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/DividerProperties.kt @@ -35,6 +35,7 @@ data class DividerProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val thickness: Float = 0.5f, ) : ViewProperties(), Parcelable { override fun interpolate(computedValuesMap: Map): DividerProperties { diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ImageProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ImageProperties.kt index 76598f4461..3571c42b58 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ImageProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ImageProperties.kt @@ -40,6 +40,7 @@ data class ImageProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val tint: String? = null, val text: String? = null, val imageConfig: ImageConfig? = null, 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 92341c9c5a..f526b56127 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 @@ -39,12 +39,13 @@ data class ListProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val id: String = "listId", val registerCard: RegisterCardConfig, val showDivider: Boolean = true, val emptyList: NoResultsConfig? = null, val orientation: ListOrientation = ListOrientation.VERTICAL, - val resources: List = emptyList(), + val resources: List = emptyList(), ) : ViewProperties(), Parcelable { override fun interpolate(computedValuesMap: Map): ListProperties { return this.copy( @@ -61,12 +62,13 @@ enum class ListOrientation { @Serializable @Parcelize -data class ListResource( +data class ListResourceConfig( val id: String? = null, val relatedResourceId: String? = null, val resourceType: ResourceType, val conditionalFhirPathExpression: String? = null, val sortConfig: SortConfig? = null, val fhirPathExpression: String? = null, - val relatedResources: List = emptyList(), + 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/PersonalDataProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/PersonalDataProperties.kt index be035e0b53..58283b6ecf 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/PersonalDataProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/PersonalDataProperties.kt @@ -35,6 +35,7 @@ data class PersonalDataProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val personalDataItems: List = emptyList(), ) : ViewProperties(), Parcelable { override fun interpolate(computedValuesMap: Map): PersonalDataProperties { diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/RowProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/RowProperties.kt index 59e4becccc..29f8a26ec1 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/RowProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/RowProperties.kt @@ -37,6 +37,7 @@ data class RowProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", + override val opacity: Float? = null, val spacedBy: Int = 8, val arrangement: RowArrangement? = null, val wrapContent: Boolean = false, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ServiceCardProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ServiceCardProperties.kt index d8731172f2..e7bad1877a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ServiceCardProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ServiceCardProperties.kt @@ -36,6 +36,7 @@ data class ServiceCardProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "true", override val visible: String = "true", + override val opacity: Float? = null, val details: List = emptyList(), val showVerticalDivider: Boolean = false, val serviceMemberIcons: String? = null, diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/SpacerProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/SpacerProperties.kt index 46de6500d9..a61c52ed80 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/SpacerProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/SpacerProperties.kt @@ -33,6 +33,7 @@ data class SpacerProperties( override val alignment: ViewAlignment = ViewAlignment.NONE, override val fillMaxWidth: Boolean = false, override val fillMaxHeight: Boolean = false, + override val opacity: Float? = null, override val clickable: String = "false", override val visible: String = "true", val height: Float? = null, 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..0b138974b2 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 @@ -27,7 +27,7 @@ import org.smartregister.fhircore.engine.util.extension.interpolate data class StackViewProperties( override val viewType: ViewType = ViewType.STACK, override val weight: Float = 0f, - override val backgroundColor: String? = "#FFFFFF", + override val backgroundColor: String? = null, override val padding: Int = 0, override val borderRadius: Int = 0, override val alignment: ViewAlignment = ViewAlignment.NONE, @@ -35,8 +35,8 @@ data class StackViewProperties( override val fillMaxHeight: Boolean = false, override val clickable: String = "false", override val visible: String = "true", - val opacity: Float = 0f, - val size: Int? = 0, + override val opacity: Float? = null, + 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/configuration/view/ViewProperties.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ViewProperties.kt index 30c1059b21..b9028dd1ec 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ViewProperties.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/view/ViewProperties.kt @@ -36,6 +36,7 @@ abstract class ViewProperties : java.io.Serializable { abstract val fillMaxHeight: Boolean abstract val clickable: String abstract val visible: String + abstract val opacity: Float? abstract fun interpolate(computedValuesMap: Map): ViewProperties } 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 6124b286ad..7ed10158d1 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 @@ -24,12 +24,13 @@ 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 -import org.smartregister.fhircore.engine.configuration.view.ListResource +import org.smartregister.fhircore.engine.configuration.view.ListResourceConfig import org.smartregister.fhircore.engine.domain.model.RepositoryResourceData 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 org.smartregister.fhircore.engine.util.extension.interpolate /** * This class is used to fire rules used to extract and manipulate data from FHIR resources. @@ -73,17 +74,18 @@ class ResourceDataRulesExecutor @Inject constructor(val rulesFactory: RulesFacto listResourceDataStateMap: SnapshotStateMap>, ) { listProperties.resources.forEach { listResource -> - // Initialize to be updated incrementally as resources are transformed into ResourceData + // A new list is required on each iteration val resourceDataSnapshotStateList = mutableStateListOf() listResourceDataStateMap[listProperties.id] = resourceDataSnapshotStateList - filteredListResources(relatedResourcesMap, listResource) + filteredListResources(relatedResourcesMap, listResource, computedValuesMap) .mapToResourceData( - listResource = listResource, + listResourceConfig = listResource, relatedResourcesMap = relatedResourcesMap, ruleConfigs = listProperties.registerCard.rules, computedValuesMap = computedValuesMap, resourceDataSnapshotStateList = resourceDataSnapshotStateList, + listResourceDataStateMap = listResourceDataStateMap, ) } } @@ -107,36 +109,64 @@ class ResourceDataRulesExecutor @Inject constructor(val rulesFactory: RulesFacto } private fun List.mapToResourceData( - listResource: ListResource, + listResourceConfig: ListResourceConfig, relatedResourcesMap: Map>, ruleConfigs: List, computedValuesMap: Map, resourceDataSnapshotStateList: SnapshotStateList, + listResourceDataStateMap: SnapshotStateMap>, ) { - this.forEach { resource -> + this.forEach { baseListResource -> + val relatedResourcesQueue = + ArrayDeque>>().apply { + addFirst(Pair(baseListResource, listResourceConfig.relatedResources)) + } + val listItemRelatedResources = mutableMapOf>() - listResource.relatedResources.forEach { relatedListResource -> - val retrieveRelatedResources: List? = - relatedListResource.fhirPathExpression.let { + while (relatedResourcesQueue.isNotEmpty()) { + val (currentResource, currentListResourceConfig) = relatedResourcesQueue.removeFirst() + currentListResourceConfig.forEach { relatedListResourceConfig -> + val retrievedRelatedResources: List = rulesFactory.rulesEngineService.retrieveRelatedResources( - resource = resource, + resource = currentResource, relatedResourceKey = - relatedListResource.relatedResourceId ?: relatedListResource.resourceType.name, - referenceFhirPathExpression = it, + relatedListResourceConfig.relatedResourceId + ?: relatedListResourceConfig.resourceType.name, + referenceFhirPathExpression = relatedListResourceConfig.fhirPathExpression, relatedResourcesMap = relatedResourcesMap, ) - } - if (!retrieveRelatedResources.isNullOrEmpty()) { - listItemRelatedResources[ - relatedListResource.id ?: relatedListResource.resourceType.name, - ] = - if (!relatedListResource.conditionalFhirPathExpression.isNullOrEmpty()) { - rulesFactory.rulesEngineService.filterResources( - retrieveRelatedResources, - relatedListResource.conditionalFhirPathExpression, - ) - } else { - retrieveRelatedResources + + val interpolatedConditionalFhirPathExpression = + relatedListResourceConfig.conditionalFhirPathExpression?.interpolate(computedValuesMap) + + rulesFactory.rulesEngineService + .filterResources( + resources = retrievedRelatedResources, + conditionalFhirPathExpression = interpolatedConditionalFhirPathExpression, + ) + .also { filteredResources -> + // Add to queue for processing + filteredResources.forEach { + relatedResourcesQueue.addLast(Pair(it, relatedListResourceConfig.relatedResources)) + } + + // Apply configurable sorting to related resources + val sortConfig = relatedListResourceConfig.sortConfig + if (sortConfig == null || sortConfig.fhirPathExpression.isBlank()) { + listItemRelatedResources[ + relatedListResourceConfig.id ?: relatedListResourceConfig.resourceType.name, + ] = filteredResources + } else { + listItemRelatedResources[ + relatedListResourceConfig.id ?: relatedListResourceConfig.resourceType.name, + ] = + rulesFactory.rulesEngineService.sortResources( + resources = filteredResources, + fhirPathExpression = sortConfig.fhirPathExpression, + dataType = sortConfig.dataType.name, + order = sortConfig.order.name, + ) ?: filteredResources + } } } } @@ -146,8 +176,8 @@ class ResourceDataRulesExecutor @Inject constructor(val rulesFactory: RulesFacto ruleConfigs = ruleConfigs, repositoryResourceData = RepositoryResourceData( - resourceRulesEngineFactId = null, - resource = resource, + resourceRulesEngineFactId = listResourceConfig.id, + resource = baseListResource, relatedResourcesMap = listItemRelatedResources, ), params = emptyMap(), @@ -155,10 +185,10 @@ class ResourceDataRulesExecutor @Inject constructor(val rulesFactory: RulesFacto resourceDataSnapshotStateList.add( ResourceData( - baseResourceId = resource.logicalId.extractLogicalIdUuid(), - baseResourceType = resource.resourceType, - computedValuesMap = - computedValuesMap.plus(listComputedValuesMap), // Reuse computed values + baseResourceId = baseListResource.logicalId, + baseResourceType = baseListResource.resourceType, + computedValuesMap = computedValuesMap.plus(listComputedValuesMap), + listResourceDataMap = listResourceDataStateMap, ), ) } @@ -167,41 +197,36 @@ class ResourceDataRulesExecutor @Inject constructor(val rulesFactory: RulesFacto /** * This function returns a list of filtered resources. The required list is obtained from * [relatedResourceMap], then a filter is applied based on the condition returned from the - * extraction of the [ListResource] conditional FHIR path expression + * extraction of the [ListResourceConfig] conditional FHIR path expression. The list is sorted if + * configurations for sorting are provided. */ private fun filteredListResources( relatedResourceMap: Map>, - listResource: ListResource, + listResource: ListResourceConfig, + computedValuesMap: Map, ): List { val relatedResourceKey = listResource.relatedResourceId ?: listResource.resourceType.name - val newListRelatedResources = relatedResourceMap[relatedResourceKey] + val interpolatedConditionalFhirPathExpression = + listResource.conditionalFhirPathExpression?.interpolate(computedValuesMap) - // conditionalFhirPath expression e.g. "Task.status == 'ready'" to filter tasks that are due + // Filter by condition derived from fhirPathExpression otherwise return original or empty list val resources = - if ( - newListRelatedResources != null && - !listResource.conditionalFhirPathExpression.isNullOrEmpty() - ) { - rulesFactory.rulesEngineService.filterResources( - resources = newListRelatedResources, - conditionalFhirPathExpression = listResource.conditionalFhirPathExpression, - ) - } else { - newListRelatedResources ?: listOf() - } + rulesFactory.rulesEngineService.filterResources( + resources = relatedResourceMap[relatedResourceKey], + conditionalFhirPathExpression = interpolatedConditionalFhirPathExpression, + ) + // Sort resources if valid sort configuration is provided val sortConfig = listResource.sortConfig - - // Sort resources if sort configuration is provided - return if (sortConfig != null && sortConfig.fhirPathExpression.isNotEmpty()) { + return if (sortConfig == null || sortConfig.fhirPathExpression.isEmpty()) { + resources + } else { rulesFactory.rulesEngineService.sortResources( resources = resources, fhirPathExpression = sortConfig.fhirPathExpression, dataType = sortConfig.dataType.name, order = sortConfig.order.name, ) ?: resources - } else { - resources } } } 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 242a6eb4a1..ce6af663ca 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,23 +183,33 @@ constructor( relatedResourceKey: String, referenceFhirPathExpression: String?, relatedResourcesMap: Map>? = null, + isRevInclude: Boolean = true, ): List { val value: List = relatedResourcesMap?.get(relatedResourceKey) ?: if (facts.getFact(relatedResourceKey) != null) { - facts.getFact(relatedResourceKey).value as List + facts.getFact(relatedResourceKey).value as List? ?: emptyList() } else { 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() + } } } } @@ -425,14 +435,15 @@ constructor( /** * This function filters resources provided the condition extracted from the - * [conditionalFhirPathExpression] is met + * [conditionalFhirPathExpression] is met. Returns the original source or empty resources list + * if FHIR path expression is null. */ fun filterResources( resources: List?, - conditionalFhirPathExpression: String, + conditionalFhirPathExpression: String?, ): List { - if (conditionalFhirPathExpression.isEmpty()) { - return emptyList() + if (conditionalFhirPathExpression.isNullOrBlank()) { + return resources ?: emptyList() } return resources?.filter { fhirPathDataExtractor.extractValue(it, conditionalFhirPathExpression).toBoolean() diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutorTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutorTest.kt index af180c121b..1a0626c36d 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutorTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/rulesengine/ResourceDataRulesExecutorTest.kt @@ -41,7 +41,7 @@ import org.smartregister.fhircore.engine.app.fakes.Faker import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.configuration.register.RegisterCardConfig import org.smartregister.fhircore.engine.configuration.view.ListProperties -import org.smartregister.fhircore.engine.configuration.view.ListResource +import org.smartregister.fhircore.engine.configuration.view.ListResourceConfig import org.smartregister.fhircore.engine.data.local.DefaultRepository import org.smartregister.fhircore.engine.domain.model.RepositoryResourceData import org.smartregister.fhircore.engine.domain.model.ResourceData @@ -158,7 +158,7 @@ class ResourceDataRulesExecutorTest : RobolectricTest() { val anotherPatient = Faker.buildPatient(id = "anotherPatient", given = "Abel", family = "Mandela") val listResource = - ListResource( + ListResourceConfig( "id", resourceType = ResourceType.Patient, sortConfig = @@ -211,7 +211,7 @@ class ResourceDataRulesExecutorTest : RobolectricTest() { val viewType = ViewType.CARD val patient = Faker.buildPatient() val listResource = - ListResource( + ListResourceConfig( "id", resourceType = ResourceType.Patient, conditionalFhirPathExpression = "Patient.active", @@ -254,13 +254,13 @@ class ResourceDataRulesExecutorTest : RobolectricTest() { val viewType = ViewType.CARD val patient = Faker.buildPatient() val listResource = - ListResource( + ListResourceConfig( "id", resourceType = ResourceType.Patient, conditionalFhirPathExpression = "Patient.active", relatedResources = listOf( - ListResource( + ListResourceConfig( null, resourceType = ResourceType.Task, fhirPathExpression = "Task.for.reference", @@ -320,12 +320,12 @@ class ResourceDataRulesExecutorTest : RobolectricTest() { val patient = Faker.buildPatient() val anotherPatient = Faker.buildPatient("anotherId") val listResource = - ListResource( + ListResourceConfig( resourceType = ResourceType.Patient, conditionalFhirPathExpression = "Patient.active", relatedResources = listOf( - ListResource( + ListResourceConfig( id = patientReadyTasks, resourceType = ResourceType.Task, conditionalFhirPathExpression = "Task.status = 'ready'", 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/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..5fe85fdad5 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,9 @@ class ProfileScreenTest { ProfileScreen( navController = rememberNavController(), profileUiState = profileUiState, - onEvent = {}, snackStateFlow = snackBarStateFlow, + onEvent = {}, + decodeImage = null, ) } } 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/RegisterScreenTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/RegisterScreenTest.kt index 62627c8e33..2cf5fd089a 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/RegisterScreenTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/register/RegisterScreenTest.kt @@ -151,6 +151,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } composeTestRule.waitUntil(5_000) { true } @@ -192,6 +193,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } @@ -236,6 +238,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } @@ -285,6 +288,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } composeTestRule.onNodeWithTag(FIRST_TIME_SYNC_DIALOG, useUnmergedTree = true) @@ -325,6 +329,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } composeTestRule.waitUntil(5_000) { true } @@ -371,6 +376,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } @@ -463,6 +469,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } @@ -511,6 +518,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + decodeImage = null, ) } @@ -562,6 +570,7 @@ class RegisterScreenTest { currentPage = currentPage, pagingItems = pagingItems, navController = rememberNavController(), + 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..8d3967f60a 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 = null, ) } @@ -85,6 +86,7 @@ class RegisterCardListTest { registerUiState = RegisterUiState(), currentPage = mutableStateOf(1), onSearchByQrSingleResultAction = {}, + decodeImage = null, ) } @@ -117,6 +119,7 @@ class RegisterCardListTest { currentPage = mutableStateOf(1), showPagination = true, onSearchByQrSingleResultAction = {}, + decodeImage = null, ) } @@ -143,6 +146,7 @@ class RegisterCardListTest { registerUiState = RegisterUiState(), currentPage = mutableStateOf(1), onSearchByQrSingleResultAction = {}, + decodeImage = null, ) } 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/CardViewTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CardViewTest.kt index 109195cdde..33ebed83fe 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CardViewTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CardViewTest.kt @@ -16,7 +16,6 @@ package org.smartregister.fhircore.quest.integration.ui.shared.components -import androidx.compose.runtime.mutableStateMapOf import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.test.assertHasClickAction import androidx.compose.ui.test.assertIsDisplayed @@ -45,7 +44,7 @@ class CardViewTest { viewProperties = viewProperties, resourceData = resourceData, navController = TestNavHostController(LocalContext.current), - decodedImageMap = mutableStateMapOf(), + decodeImage = null, ) } composeTestRule.onNodeWithText("IMMUNIZATIONS").assertIsDisplayed() diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CompoundTextTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CompoundTextTest.kt index c3bf43c61a..1cf8311dfd 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CompoundTextTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/CompoundTextTest.kt @@ -48,6 +48,7 @@ class CompoundTextTest { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap(), emptyMap()), navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeTestRule.onNodeWithText("Full Name, Age").assertExists().assertIsDisplayed() @@ -69,6 +70,7 @@ class CompoundTextTest { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap(), emptyMap()), navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeTestRule.onNodeWithText("Yesterday").assertExists().assertIsDisplayed() 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..ffa1f77c18 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 = null, ) } composeRule @@ -62,6 +63,7 @@ class ServiceCardTest { initTestServiceCardProperties(serviceStatus = ServiceStatus.OVERDUE.name, text = "1"), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule.onNodeWithText("1", useUnmergedTree = true).assertExists().assertIsDisplayed() @@ -74,6 +76,7 @@ class ServiceCardTest { serviceCardProperties = initTestServiceCardProperties(visible = "false"), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } 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 = null, ) } composeRule.onNodeWithTag(DIVIDER_TEST_TAG).assertDoesNotExist() diff --git a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/StackViewTest.kt b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/StackViewTest.kt index d203e9f434..13e40fc3f4 100644 --- a/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/StackViewTest.kt +++ b/android/quest/src/androidTest/java/org/smartregister/fhircore/quest/integration/ui/shared/components/StackViewTest.kt @@ -41,6 +41,7 @@ class StackViewTest { stackViewProperties = stackViewProperties, resourceData = ResourceData("", ResourceType.Patient, emptyMap()), navController = rememberNavController(), + decodeImage = null, ) } composeTestRule.onNodeWithTag(STACK_VIEW_TEST_TAG).assertExists() 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..60b73e7995 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 @@ -89,6 +89,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule.onNodeWithText("Upcoming household service").assertExists().assertIsDisplayed() @@ -114,6 +115,7 @@ class ViewGeneratorTest { ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule @@ -140,6 +142,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule @@ -172,6 +175,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule @@ -204,6 +208,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule.onNodeWithTag(COLUMN_DIVIDER_TEST_TAG).assertExists() @@ -257,6 +262,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule.onNodeWithText("Richard Brown, M, 29", useUnmergedTree = true).assertDoesNotExist() @@ -284,6 +290,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule @@ -316,6 +323,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule @@ -342,6 +350,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule.onNodeWithText("Sex").assertIsDisplayed() @@ -359,6 +368,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule @@ -383,7 +393,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), - decodedImageMap = decodedImageMap, + decodeImage = { reference -> decodedImageMap[reference] }, ) } composeRule @@ -410,6 +420,7 @@ class ViewGeneratorTest { ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), + decodeImage = null, ) } composeRule 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/AppMainViewModel.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainViewModel.kt index b8092861f7..63dea06500 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 @@ -88,7 +87,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 @@ -135,23 +133,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 14fbb078c0..f1578c313f 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, @@ -522,6 +546,7 @@ private fun SideMenuItem( imageProperties = ImageProperties(imageConfig = imageConfig, size = 32), tint = MenuItemColor, navController = rememberNavController(), + decodeImage = decodeImage, ) } Column { @@ -598,6 +623,7 @@ fun AppDrawerPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -636,6 +662,7 @@ fun AppDrawerWithUnSyncedDataPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(10) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -678,6 +705,7 @@ fun AppDrawerOnSyncCompletePreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -720,6 +748,7 @@ fun AppDrawerOnSyncFailedPreview() { appVersionPair = Pair(1, "0.0.1"), unSyncedResourceCount = remember { mutableIntStateOf(0) }, onCountUnSyncedResources = {}, + decodeImage = null, ) } } @@ -763,6 +792,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 58ef9685aa..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)) @@ -287,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 { @@ -307,6 +310,7 @@ fun SetupToolbarIcons( navController = navController, modifier = modifier, onClick = onClick, + decodeImage = decodeImage, ) } } @@ -318,6 +322,7 @@ fun RenderMenuIcons( navController: NavController, modifier: Modifier, onClick: (ToolbarClickEvent) -> Unit, + decodeImage: ((String) -> Bitmap?)?, ) { LazyRow(horizontalArrangement = Arrangement.spacedBy(16.dp)) { items(menuIcons) { @@ -329,6 +334,7 @@ fun RenderMenuIcons( modifier .clickable { onClick(ToolbarClickEvent.Actions(it.actions)) } .testTag(TOP_ROW_TOGGLE_ICON_TEST_tAG), + decodeImage = decodeImage, ) } } @@ -359,6 +365,7 @@ fun TopScreenSectionWithFilterItemOverNinetyNinePreview() { ), ), navController = rememberNavController(), + decodeImage = null, ) } @@ -375,6 +382,7 @@ fun TopScreenSectionWithFilterCountNinetyNinePreview() { onClick = {}, isSearchBarVisible = true, navController = rememberNavController(), + decodeImage = null, ) } @@ -399,6 +407,7 @@ fun TopScreenSectionNoFilterIconPreview() { ImageProperties(imageConfig = ImageConfig(reference = "ic_service_points")), ), ), + decodeImage = null, ) } @@ -424,6 +433,7 @@ fun TopScreenSectionWithFilterIconAndToggleIconPreview() { ImageProperties(imageConfig = ImageConfig(reference = "ic_service_points")), ), ), + decodeImage = null, ) } @@ -440,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/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 be92daf11f..515739ff58 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 82389aa8f4..3d8aca437c 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.CodeType import org.hl7.fhir.r4.model.CodeableConcept import org.hl7.fhir.r4.model.Coding @@ -75,6 +78,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 @@ -106,6 +110,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 @@ -684,4 +689,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 d360810766..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 @@ -51,7 +49,7 @@ import org.smartregister.fhircore.engine.configuration.register.RegisterCardConf import org.smartregister.fhircore.engine.configuration.view.CompoundTextProperties import org.smartregister.fhircore.engine.configuration.view.ListOrientation import org.smartregister.fhircore.engine.configuration.view.ListProperties -import org.smartregister.fhircore.engine.configuration.view.ListResource +import org.smartregister.fhircore.engine.configuration.view.ListResourceConfig import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.domain.model.ViewType import org.smartregister.fhircore.engine.ui.theme.DefaultColor @@ -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, ) } } @@ -187,7 +185,7 @@ private fun ListWithHorizontalOrientationPreview() { borderRadius = 10, emptyList = NoResultsConfig(message = ""), resources = - listOf(ListResource(id = "carePlanList", resourceType = ResourceType.CarePlan)), + listOf(ListResourceConfig(id = "carePlanList", resourceType = ResourceType.CarePlan)), fillMaxHeight = true, registerCard = RegisterCardConfig( @@ -231,6 +229,7 @@ private fun ListWithHorizontalOrientationPreview() { baseResourceType = ResourceType.Patient, computedValuesMap = emptyMap(), ), + decodeImage = null, ) } } @@ -251,7 +250,7 @@ private fun ListWithVerticalOrientationPreview() { borderRadius = 10, emptyList = NoResultsConfig(message = "No care Plans"), resources = - listOf(ListResource(id = "carePlanList", resourceType = ResourceType.CarePlan)), + listOf(ListResourceConfig(id = "carePlanList", resourceType = ResourceType.CarePlan)), fillMaxWidth = true, registerCard = RegisterCardConfig( @@ -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..ce960e62e7 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 @@ -17,12 +17,9 @@ package org.smartregister.fhircore.quest.ui.shared.components import android.graphics.Bitmap -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 @@ -34,7 +31,6 @@ import org.smartregister.fhircore.engine.configuration.view.StackViewProperties import org.smartregister.fhircore.engine.configuration.view.ViewAlignment import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.util.annotation.PreviewWithBackgroundExcludeGenerated -import org.smartregister.fhircore.engine.util.extension.parseColor const val STACK_VIEW_TEST_TAG = "stackViewTestTag" @@ -44,16 +40,10 @@ 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) - .testTag(STACK_VIEW_TEST_TAG), + modifier.size(stackViewProperties.size.dp).testTag(STACK_VIEW_TEST_TAG), contentAlignment = castViewAlignment(stackViewProperties.alignment), ) { stackViewProperties.children.forEach { child -> @@ -62,7 +52,7 @@ fun StackView( properties = child.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -101,5 +91,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 4b2b7c805f..7575605050 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, ) } } @@ -135,11 +134,7 @@ fun GenerateView( .conditional( properties.clickable.toBoolean(), { - clickable { - (properties as RowProperties) - .actions - .handleClickEvent(navController, resourceData) - } + clickable { properties.actions.handleClickEvent(navController, resourceData) } }, ), verticalArrangement = @@ -155,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( @@ -189,7 +184,7 @@ fun GenerateView( properties = properties.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -212,11 +207,7 @@ fun GenerateView( .conditional( properties.clickable.toBoolean(), { - clickable { - (properties as RowProperties) - .actions - .handleClickEvent(navController, resourceData) - } + clickable { properties.actions.handleClickEvent(navController, resourceData) } }, ), horizontalArrangement = @@ -232,7 +223,7 @@ fun GenerateView( properties = child.interpolate(resourceData.computedValuesMap), resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -244,6 +235,7 @@ fun GenerateView( serviceCardProperties = properties as ServiceCardProperties, resourceData = resourceData, navController = navController, + decodeImage = decodeImage, ) ViewType.CARD -> CardView( @@ -251,7 +243,7 @@ fun GenerateView( viewProperties = properties as CardViewProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) ViewType.PERSONAL_DATA -> PersonalDataView( @@ -270,7 +262,7 @@ fun GenerateView( viewProperties = properties as ListProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) ViewType.IMAGE -> Image( @@ -278,7 +270,7 @@ fun GenerateView( imageProperties = properties as ImageProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) ViewType.STACK -> StackView( @@ -286,7 +278,7 @@ fun GenerateView( stackViewProperties = properties as StackViewProperties, resourceData = resourceData, navController = navController, - decodedImageMap = decodedImageMap, + decodeImage = decodeImage, ) } } @@ -337,5 +329,13 @@ fun generateModifier(viewProperties: ViewProperties): Modifier = private fun Modifier.applyCommonProperties(viewProperties: ViewProperties): Modifier = this.conditional(viewProperties.fillMaxWidth, { fillMaxWidth() }) .conditional(viewProperties.fillMaxHeight, { fillMaxHeight() }) - .background(viewProperties.backgroundColor.parseColor()) + .background( + viewProperties.backgroundColor.parseColor().let { baseColor -> + if (viewProperties.opacity != null) { + baseColor.copy(alpha = viewProperties.opacity!!.toFloat()) + } else { + baseColor + } + }, + ) .clip(RoundedCornerShape(viewProperties.borderRadius.dp)) 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/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"))