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 2d9c8daffb..4773f73c32 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 @@ -414,7 +414,7 @@ constructor( } // is focus.identifier a necessary check .groupBy { section -> section.focus.reference.substringBefore( - ConfigurationRegistry.TYPE_REFERENCE_DELIMITER, + TYPE_REFERENCE_DELIMITER, missingDelimiterValue = "", ) } @@ -586,7 +586,7 @@ constructor( ?: "${openSrpApplication?.getFhirServerHost().toString()?.trimEnd { it == '/' }}/${this.referenceValue()}" } - private fun writeToFile(resource: Resource): File { + fun writeToFile(resource: Resource): File { val fileName = if (resource is MetadataResource && resource.name != null) { resource.name diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/navigation/NavigationMenuConfig.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/navigation/NavigationMenuConfig.kt index b42dd77154..935e9fad56 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/navigation/NavigationMenuConfig.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/navigation/NavigationMenuConfig.kt @@ -42,6 +42,7 @@ data class NavigationMenuConfig( data class ImageConfig( val type: String = ICON_TYPE_LOCAL, val reference: String? = null, + val color: String? = null, @Contextual var decodedBitmap: Bitmap? = null, ) : Parcelable, java.io.Serializable { fun interpolate(computedValuesMap: Map): ImageConfig { 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 c50f88466f..3d01242baa 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 @@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.Shape import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable import org.smartregister.fhircore.engine.configuration.navigation.ImageConfig +import org.smartregister.fhircore.engine.domain.model.ActionConfig import org.smartregister.fhircore.engine.domain.model.ViewType import org.smartregister.fhircore.engine.util.extension.interpolate @@ -40,9 +41,12 @@ data class ImageProperties( override val clickable: String = "false", override val visible: String = "true", val tint: String? = null, + val text: String? = null, val imageConfig: ImageConfig? = null, val size: Int? = null, val shape: ImageShape? = null, + val textColor: String? = null, + val actions: List = emptyList(), ) : ViewProperties(), Parcelable { override fun interpolate(computedValuesMap: Map): ViewProperties { return this.copy( @@ -53,6 +57,7 @@ data class ImageProperties( ), tint = this.tint?.interpolate(computedValuesMap), backgroundColor = this.backgroundColor?.interpolate(computedValuesMap), + text = this.text?.interpolate(computedValuesMap), ) } } diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/workflow/ApplicationWorkflow.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/workflow/ApplicationWorkflow.kt index c26b6995bb..fe5a3108ca 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/workflow/ApplicationWorkflow.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/configuration/workflow/ApplicationWorkflow.kt @@ -53,4 +53,7 @@ enum class ApplicationWorkflow { /** A workflow that launches user insight screen */ LAUNCH_INSIGHT_SCREEN, + + /** A workflow that copies text to keyboard */ + COPY_TEXT, } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistryTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistryTest.kt index ddc3f40076..883f463fa6 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistryTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistryTest.kt @@ -32,8 +32,10 @@ import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltTestApplication import io.mockk.coEvery import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk import io.mockk.spyk +import java.io.File import java.net.URL import javax.inject.Inject import kotlinx.coroutines.test.runTest @@ -50,9 +52,11 @@ import org.hl7.fhir.r4.model.Reference import org.hl7.fhir.r4.model.Resource import org.hl7.fhir.r4.model.ResourceType import org.junit.Assert +import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.ArgumentMatchers import org.smartregister.fhircore.engine.OpenSrpApplication import org.smartregister.fhircore.engine.app.AppConfigService import org.smartregister.fhircore.engine.app.fakes.Faker @@ -68,6 +72,7 @@ import org.smartregister.fhircore.engine.rule.CoroutineTestRule import org.smartregister.fhircore.engine.util.DispatcherProvider import org.smartregister.fhircore.engine.util.SharedPreferenceKey import org.smartregister.fhircore.engine.util.SharedPreferencesHelper +import org.smartregister.fhircore.engine.util.extension.encodeResourceToString import org.smartregister.fhircore.engine.util.extension.getPayload import org.smartregister.fhircore.engine.util.extension.second @@ -86,6 +91,8 @@ class ConfigurationRegistryTest : RobolectricTest() { private val fhirResourceService = mockk() private lateinit var fhirResourceDataSource: FhirResourceDataSource private lateinit var configRegistry: ConfigurationRegistry + private lateinit var mockedContext: Context + private lateinit var mockedJsonParser: IParser @Before @kotlinx.coroutines.ExperimentalCoroutinesApi @@ -110,6 +117,8 @@ class ConfigurationRegistryTest : RobolectricTest() { } }, ) + mockedContext = mockk() + mockedJsonParser = mockk() configRegistry.setNonProxy(false) Assert.assertNotNull(configRegistry) } @@ -126,8 +135,8 @@ class ConfigurationRegistryTest : RobolectricTest() { configRegistry.configsJsonMap["strings"] = "name.title=Mr.\n" + "gender.male=Male" val resource = configRegistry.retrieveResourceBundleConfiguration("strings_en") Assert.assertNotNull(resource) - Assert.assertEquals("Mr.", resource?.getString("name.title")) - Assert.assertEquals("Male", resource?.getString("gender.male")) + assertEquals("Mr.", resource?.getString("name.title")) + assertEquals("Male", resource?.getString("gender.male")) } @Test @@ -135,8 +144,8 @@ class ConfigurationRegistryTest : RobolectricTest() { configRegistry.configsJsonMap["stringsSw"] = "name.title=Bwana.\n" + "gender.male=Kijana" val resource = configRegistry.retrieveResourceBundleConfiguration("strings_sw") Assert.assertNotNull(resource) - Assert.assertEquals("Bwana.", resource?.getString("name.title")) - Assert.assertEquals("Kijana", resource?.getString("gender.male")) + assertEquals("Bwana.", resource?.getString("name.title")) + assertEquals("Kijana", resource?.getString("gender.male")) } @Test @@ -144,8 +153,8 @@ class ConfigurationRegistryTest : RobolectricTest() { configRegistry.configsJsonMap["stringsSw"] = "name.title=Bwana.\n" + "gender.male=Kijana" val resource = configRegistry.retrieveResourceBundleConfiguration("strings_sw_KE") Assert.assertNotNull(resource) - Assert.assertEquals("Bwana.", resource?.getString("name.title")) - Assert.assertEquals("Kijana", resource?.getString("gender.male")) + assertEquals("Bwana.", resource?.getString("name.title")) + assertEquals("Kijana", resource?.getString("gender.male")) } @Test @@ -154,7 +163,7 @@ class ConfigurationRegistryTest : RobolectricTest() { configRegistry.configsJsonMap[ConfigType.Application.name] = "{\"appId\": \"${appId}\"}" val appConfig = configRegistry.retrieveConfiguration(ConfigType.Application) - Assert.assertEquals(appId, appConfig.appId) + assertEquals(appId, appConfig.appId) } @Test @@ -166,8 +175,8 @@ class ConfigurationRegistryTest : RobolectricTest() { "{\"appId\": \"${appId}\", \"id\": \"${id}\", \"fhirResource\": {\"baseResource\": { \"resource\": \"Patient\"}}}" val registerConfig = configRegistry.retrieveConfiguration(ConfigType.Register) - Assert.assertEquals(appId, registerConfig.appId) - Assert.assertEquals(id, registerConfig.id) + assertEquals(appId, registerConfig.appId) + assertEquals(id, registerConfig.id) } @Test @@ -181,8 +190,8 @@ class ConfigurationRegistryTest : RobolectricTest() { val registerConfig = configRegistry.retrieveConfiguration(ConfigType.Register, configId) Assert.assertTrue(configRegistry.configCacheMap.containsKey(configId)) - Assert.assertEquals(appId, registerConfig.appId) - Assert.assertEquals(id, registerConfig.id) + assertEquals(appId, registerConfig.appId) + assertEquals(id, registerConfig.id) } @Test @@ -202,8 +211,8 @@ class ConfigurationRegistryTest : RobolectricTest() { mapOf(appId to paramAppId, id to paramId), ) Assert.assertTrue(configRegistry.configCacheMap.containsKey(configId)) - Assert.assertEquals(paramAppId, registerConfig.appId) - Assert.assertEquals(paramId, registerConfig.id) + assertEquals(paramAppId, registerConfig.appId) + assertEquals(paramId, registerConfig.id) } @Test @@ -282,8 +291,8 @@ class ConfigurationRegistryTest : RobolectricTest() { val requestPathArgumentSlot = mutableListOf() coVerify(exactly = 1) { fhirEngine.create(capture(requestPathArgumentSlot)) } - Assert.assertEquals("composition-id-1", requestPathArgumentSlot.first().id) - Assert.assertEquals(ResourceType.Composition, requestPathArgumentSlot.first().resourceType) + assertEquals("composition-id-1", requestPathArgumentSlot.first().id) + assertEquals(ResourceType.Composition, requestPathArgumentSlot.first().resourceType) } @Test @@ -562,7 +571,7 @@ class ConfigurationRegistryTest : RobolectricTest() { ) Assert.assertNotNull(applicationConfiguration) - Assert.assertEquals("thisApp", applicationConfiguration.appId) + assertEquals("thisApp", applicationConfiguration.appId) Assert.assertNotNull(ConfigType.Application.name, applicationConfiguration.configType) // Config cache map now contains application config Assert.assertTrue(configRegistry.configCacheMap.containsKey(ConfigType.Application.name)) @@ -573,7 +582,7 @@ class ConfigurationRegistryTest : RobolectricTest() { ) Assert.assertTrue(configRegistry.configCacheMap.containsKey(ConfigType.Application.name)) Assert.assertNotNull(anotherApplicationConfig) - Assert.assertEquals("thisApp", anotherApplicationConfig.appId) + assertEquals("thisApp", anotherApplicationConfig.appId) Assert.assertNotNull(ConfigType.Application.name, anotherApplicationConfig.configType) } @@ -620,7 +629,7 @@ class ConfigurationRegistryTest : RobolectricTest() { ) Assert.assertNotNull(applicationConfiguration) - Assert.assertEquals("thisApp", applicationConfiguration.appId) + assertEquals("thisApp", applicationConfiguration.appId) Assert.assertNotNull(ConfigType.Application.name, applicationConfiguration.configType) // Config cache map now contains application config @@ -643,7 +652,7 @@ class ConfigurationRegistryTest : RobolectricTest() { ) } - Assert.assertEquals(21, compositionSections.size) + assertEquals(21, compositionSections.size) val composition = Composition().apply { @@ -666,12 +675,12 @@ class ConfigurationRegistryTest : RobolectricTest() { fhirResourceDataSource.post(capture(urlArgumentSlot), capture(requestPathArgumentSlot)) } - Assert.assertEquals(2, requestPathArgumentSlot.size) - Assert.assertEquals( + assertEquals(2, requestPathArgumentSlot.size) + assertEquals( "{\"resourceType\":\"Bundle\",\"type\":\"batch\",\"entry\":[{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-1\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-2\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-3\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-4\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-5\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-6\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-7\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-8\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-9\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-10\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-11\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-12\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-13\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-14\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-15\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-16\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-17\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-18\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-19\"}},{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-20\"}}]}", requestPathArgumentSlot.first().getPayload(), ) - Assert.assertEquals( + assertEquals( "{\"resourceType\":\"Bundle\",\"type\":\"batch\",\"entry\":[{\"request\":{\"method\":\"GET\",\"url\":\"StructureMap/id-21\"}}]}", requestPathArgumentSlot.last().getPayload(), ) @@ -723,16 +732,16 @@ class ConfigurationRegistryTest : RobolectricTest() { fhirEngine.create(capture(requestPathArgumentSlot), isLocalOnly = true) } - Assert.assertEquals(3, requestPathArgumentSlot.size) + assertEquals(3, requestPathArgumentSlot.size) - Assert.assertEquals("Group/1000001", requestPathArgumentSlot.first().id) - Assert.assertEquals(ResourceType.Group, requestPathArgumentSlot.first().resourceType) + assertEquals("Group/1000001", requestPathArgumentSlot.first().id) + assertEquals(ResourceType.Group, requestPathArgumentSlot.first().resourceType) - Assert.assertEquals("Group/2000001", requestPathArgumentSlot.second().id) - Assert.assertEquals(ResourceType.Group, requestPathArgumentSlot.second().resourceType) + assertEquals("Group/2000001", requestPathArgumentSlot.second().id) + assertEquals(ResourceType.Group, requestPathArgumentSlot.second().resourceType) - Assert.assertEquals("composition-id-1", requestPathArgumentSlot.last().id) - Assert.assertEquals(ResourceType.Composition, requestPathArgumentSlot.last().resourceType) + assertEquals("composition-id-1", requestPathArgumentSlot.last().id) + assertEquals(ResourceType.Composition, requestPathArgumentSlot.last().resourceType) } @Test @@ -793,19 +802,19 @@ class ConfigurationRegistryTest : RobolectricTest() { fhirEngine.create(capture(requestPathArgumentSlot), isLocalOnly = true) } - Assert.assertEquals(4, requestPathArgumentSlot.size) + assertEquals(4, requestPathArgumentSlot.size) - Assert.assertEquals("Bundle/the-commodities-bundle-id", requestPathArgumentSlot.first().id) - Assert.assertEquals(ResourceType.Bundle, requestPathArgumentSlot.first().resourceType) + assertEquals("Bundle/the-commodities-bundle-id", requestPathArgumentSlot.first().id) + assertEquals(ResourceType.Bundle, requestPathArgumentSlot.first().resourceType) - Assert.assertEquals("Group/1000001", requestPathArgumentSlot.second().id) - Assert.assertEquals(ResourceType.Group, requestPathArgumentSlot.second().resourceType) + assertEquals("Group/1000001", requestPathArgumentSlot.second().id) + assertEquals(ResourceType.Group, requestPathArgumentSlot.second().resourceType) - Assert.assertEquals("Group/2000001", requestPathArgumentSlot[2].id) - Assert.assertEquals(ResourceType.Group, requestPathArgumentSlot[2].resourceType) + assertEquals("Group/2000001", requestPathArgumentSlot[2].id) + assertEquals(ResourceType.Group, requestPathArgumentSlot[2].resourceType) - Assert.assertEquals("composition-id-1", requestPathArgumentSlot.last().id) - Assert.assertEquals(ResourceType.Composition, requestPathArgumentSlot.last().resourceType) + assertEquals("composition-id-1", requestPathArgumentSlot.last().id) + assertEquals(ResourceType.Composition, requestPathArgumentSlot.last().resourceType) } @Test @@ -827,8 +836,22 @@ class ConfigurationRegistryTest : RobolectricTest() { listResourceTypeToken, ) - Assert.assertEquals(2, savedSyncResourceTypes.size) - Assert.assertEquals(ResourceType.Task, savedSyncResourceTypes.first()) - Assert.assertEquals(ResourceType.Patient, savedSyncResourceTypes.last()) + assertEquals(2, savedSyncResourceTypes.size) + assertEquals(ResourceType.Task, savedSyncResourceTypes.first()) + assertEquals(ResourceType.Patient, savedSyncResourceTypes.last()) + } + + @Test + fun writeToFileWithMetadataResourceWithNameShouldCreateFileWithResourceName() { + val resource = Faker.buildPatient().apply { id = "1661662881" } + val expectedFileName = "1661662881.json" + every { mockedContext.filesDir } returns File(ArgumentMatchers.anyString()) + every { mockedJsonParser.encodeResourceToString(any()) } returns + resource.encodeResourceToString() + val expectedEncodedResource = mockedJsonParser.encodeResourceToString(resource) + + val resultFile = configRegistry.writeToFile(resource) + assertEquals(expectedFileName, resultFile.name) + assertEquals(expectedEncodedResource, resultFile.readText()) } } 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 0cec8f28ef..8a84deab04 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 @@ -26,7 +26,6 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.unit.dp import androidx.navigation.testing.TestNavHostController -import androidx.test.core.app.ApplicationProvider import org.hl7.fhir.r4.model.ResourceType import org.junit.Rule import org.junit.Test @@ -43,7 +42,6 @@ import org.smartregister.fhircore.quest.ui.shared.components.ActionableButton class ActionableButtonTest { @get:Rule val composeRule = createComposeRule() - private val navController = TestNavHostController(ApplicationProvider.getApplicationContext()) @Test fun testActionableButtonRendersAncClickWorksCorrectlyWithStatusDue() { 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 6cee76013f..ca2e8b7fcd 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 @@ -350,12 +350,19 @@ class ViewGeneratorTest { fun testImageIsRenderedFromLocalAsset() { composeRule.setContent { GenerateView( - properties = ImageProperties(imageConfig = ImageConfig(ICON_TYPE_LOCAL, "ic_walk")), + properties = + ImageProperties( + imageConfig = ImageConfig(ICON_TYPE_LOCAL, "ic_walk", color = "#FFF000"), + text = "Copy text", + ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), ) } - composeRule.onNodeWithTag(SIDE_MENU_ITEM_LOCAL_ICON_TEST_TAG).assertExists().assertIsDisplayed() + composeRule + .onNodeWithTag(SIDE_MENU_ITEM_LOCAL_ICON_TEST_TAG, useUnmergedTree = true) + .assertExists() + .assertIsDisplayed() } @Test @@ -375,7 +382,7 @@ class ViewGeneratorTest { ) } composeRule - .onNodeWithTag(SIDE_MENU_ITEM_REMOTE_ICON_TEST_TAG) + .onNodeWithTag(SIDE_MENU_ITEM_REMOTE_ICON_TEST_TAG, useUnmergedTree = true) .assertExists() .assertIsDisplayed() } 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 3306ee593c..ed16704139 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 @@ -98,7 +98,6 @@ constructor( val workManager: WorkManager, val fhirCarePlanGenerator: FhirCarePlanGenerator, ) : ViewModel() { - val appMainUiState: MutableState = mutableStateOf( appMainUiStateOf( 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 92225589d0..2645b9773d 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 @@ -356,6 +356,7 @@ private fun SideMenuItem( paddingEnd = 10, imageProperties = ImageProperties(imageConfig = imageConfig, size = 32), tint = MenuItemColor, + navController = rememberNavController(), ) SideMenuItemText(title = title, textColor = Color.White) } 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 38ad7f5295..3f47fa0359 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 @@ -355,6 +355,8 @@ private fun ProfileTopAppBarMenuAction( Image( imageProperties = ImageProperties(imageConfig = overflowMenuItemConfig.icon), tint = contentColor, + navController = navController, + resourceData = profileUiState.resourceData!!, ) 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/register/RegisterFragment.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/register/RegisterFragment.kt index 43745be40e..6054dffa15 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 @@ -133,7 +133,6 @@ class RegisterFragment : Fragment(), OnSyncListener { .collectAsState(emptyFlow()) .value .collectAsLazyPagingItems() - // Register screen provides access to the side navigation Scaffold( drawerGesturesEnabled = scaffoldState.drawerState.isOpen, 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 9d561188b2..7fdf5069fe 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 @@ -35,6 +35,7 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight @@ -80,6 +81,7 @@ fun ActionableButton( val clickable = buttonProperties.clickable.toBoolean() val backgroundOpacity = buttonProperties.backgroundOpacity val colorOpacity = buttonProperties.colorOpacity + val context = LocalContext.current OutlinedButton( onClick = { if ( @@ -89,6 +91,7 @@ fun ActionableButton( buttonProperties.actions.handleClickEvent( navController = navController, resourceData = resourceData, + context = context, ) } }, @@ -154,6 +157,8 @@ fun ActionableButton( Image( imageProperties = ImageProperties(imageConfig = buttonProperties.startIcon, size = 16), tint = iconTintColor, + resourceData = resourceData, + navController = navController, ) } else { Icon( 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 6ef0842db9..a52477b88d 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 @@ -51,55 +51,58 @@ fun CardView( resourceData: ResourceData, navController: NavController, ) { - val headerActionVisible = viewProperties.headerAction?.visible.toBoolean() - Column(modifier = modifier.background(viewProperties.headerBackgroundColor.parseColor())) { - // Header section - Row( - modifier = - modifier - .fillMaxWidth() - .conditional(viewProperties.header != null, { padding(top = 24.dp, bottom = 8.dp) }), - verticalAlignment = Alignment.Top, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - if (viewProperties.header != null) { - CompoundText( - modifier = - modifier - .conditional(headerActionVisible, { weight(if (headerActionVisible) 0.6f else 1f) }) - .wrapContentWidth(Alignment.Start), - compoundTextProperties = viewProperties.header!!.copy(textCase = TextCase.UPPER_CASE), - resourceData = resourceData, - navController = navController, - ) - if (viewProperties.headerAction != null && headerActionVisible) { + // Check if card is visible + if (viewProperties.visible.toBoolean()) { + val headerActionVisible = viewProperties.headerAction?.visible.toBoolean() + Column(modifier = modifier.background(viewProperties.headerBackgroundColor.parseColor())) { + // Header section + Row( + modifier = + modifier + .fillMaxWidth() + .conditional(viewProperties.header != null, { padding(top = 24.dp, bottom = 8.dp) }), + verticalAlignment = Alignment.Top, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + if (viewProperties.header != null) { CompoundText( - modifier = modifier.wrapContentWidth(Alignment.End).weight(0.4f), - compoundTextProperties = viewProperties.headerAction!!, + modifier = + modifier + .conditional(headerActionVisible, { weight(if (headerActionVisible) 0.6f else 1f) }) + .wrapContentWidth(Alignment.Start), + compoundTextProperties = viewProperties.header!!.copy(textCase = TextCase.UPPER_CASE), resourceData = resourceData, navController = navController, ) + if (viewProperties.headerAction != null && headerActionVisible) { + CompoundText( + modifier = modifier.wrapContentWidth(Alignment.End).weight(0.4f), + compoundTextProperties = viewProperties.headerAction!!, + resourceData = resourceData, + navController = navController, + ) + } } } - } - // Card section - Card( - elevation = viewProperties.elevation.dp, - modifier = - modifier - .padding( - start = viewProperties.padding.dp, - end = viewProperties.padding.dp, + // Card section + Card( + elevation = viewProperties.elevation.dp, + modifier = + modifier + .padding( + start = viewProperties.padding.dp, + end = viewProperties.padding.dp, + ) + .fillMaxWidth() + .clip(RoundedCornerShape(viewProperties.cornerSize.dp)), + ) { + Column(modifier = modifier.padding(viewProperties.contentPadding.dp)) { + ViewRenderer( + viewProperties = viewProperties.content, + resourceData = resourceData, + navController = navController, ) - .fillMaxWidth() - .clip(RoundedCornerShape(viewProperties.cornerSize.dp)), - ) { - Column(modifier = modifier.padding(viewProperties.contentPadding.dp)) { - ViewRenderer( - viewProperties = viewProperties.content, - resourceData = resourceData, - navController = navController, - ) + } } } } 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 d297dc589c..ffccca67fd 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 @@ -88,6 +88,8 @@ fun ExtendedFab( modifier = modifier.testTag(FAB_BUTTON_ROW_ICON_TEST_TAG), imageProperties = ImageProperties(imageConfig = firstMenuIconConfig), tint = if (firstFabEnabled) Color.White else DefaultColor, + navController = navController, + resourceData = resourceData!!, ) } if (text.isNotEmpty()) { 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 8703388610..5641bee585 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 @@ -16,30 +16,41 @@ package org.smartregister.fhircore.quest.ui.shared.components +import android.content.Context import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import org.hl7.fhir.r4.model.ResourceType import org.smartregister.fhircore.engine.configuration.navigation.ICON_TYPE_LOCAL import org.smartregister.fhircore.engine.configuration.navigation.ICON_TYPE_REMOTE import org.smartregister.fhircore.engine.configuration.navigation.ImageConfig import org.smartregister.fhircore.engine.configuration.view.ImageProperties import org.smartregister.fhircore.engine.configuration.view.ImageShape +import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.domain.model.ViewType import org.smartregister.fhircore.engine.ui.theme.DangerColor import org.smartregister.fhircore.engine.util.annotation.PreviewWithBackgroundExcludeGenerated @@ -47,6 +58,7 @@ 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 import org.smartregister.fhircore.quest.util.extensions.conditional +import org.smartregister.fhircore.quest.util.extensions.handleClickEvent const val SIDE_MENU_ITEM_LOCAL_ICON_TEST_TAG = "sideMenuItemLocalIconTestTag" const val SIDE_MENU_ITEM_REMOTE_ICON_TEST_TAG = "sideMenuItemBinaryIconTestTag" @@ -57,57 +69,124 @@ fun Image( paddingEnd: Int? = null, tint: Color? = null, imageProperties: ImageProperties = ImageProperties(viewType = ViewType.IMAGE, size = 24), + navController: NavController, + resourceData: ResourceData? = null, ) { val imageConfig = imageProperties.imageConfig + val colorTint = tint ?: imageProperties.imageConfig?.color.parseColor() + val cotext = LocalContext.current if (imageConfig != null) { - Box( - contentAlignment = Alignment.Center, - modifier = - modifier - .conditional( - imageProperties.shape != null, - { clip(imageProperties.shape!!.composeShape) }, - { clip(RoundedCornerShape(imageProperties.borderRadius.dp)) }, - ) - .conditional( - imageProperties.size != null, - { size(imageProperties.size!!.dp) }, - { size(24.dp) }, - ) - .conditional( - !imageProperties.backgroundColor.isNullOrEmpty(), - { background(imageProperties.backgroundColor.parseColor()) }, - ) - .conditional(imageProperties.padding >= 0, { padding(imageProperties.padding.dp) }), - ) { - 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 ?: imageProperties.tint.parseColor(), - ) - } - ICON_TYPE_REMOTE -> - if (imageConfig.decodedBitmap != null) { - Image( - modifier = - Modifier.testTag(SIDE_MENU_ITEM_REMOTE_ICON_TEST_TAG) - .conditional(paddingEnd != null, { padding(end = paddingEnd!!.dp) }) - .align(Alignment.Center) - .fillMaxSize(0.9f), - bitmap = imageConfig.decodedBitmap!!.asImageBitmap(), - contentDescription = null, - contentScale = ContentScale.Crop, + if (imageProperties.text != null) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + Text( + text = imageProperties.text!!, + textAlign = TextAlign.Center, + modifier = Modifier.padding(end = 8.dp), + color = imageProperties.textColor?.parseColor() ?: Color.Gray, + ) + ClickableImageIcon( + imageProperties = imageProperties, + imageConfig = imageConfig, + tint = colorTint, + paddingEnd = paddingEnd, + navController = navController, + resourceData = resourceData, + modifier = modifier, + context = cotext, + ) + } + } else { + ClickableImageIcon( + imageProperties = imageProperties, + imageConfig = imageConfig, + tint = colorTint, + paddingEnd = paddingEnd, + navController = navController, + resourceData = resourceData, + modifier = modifier, + context = cotext, + ) + } + } +} + +@Composable +fun ClickableImageIcon( + modifier: Modifier = Modifier, + imageProperties: ImageProperties, + imageConfig: ImageConfig, + tint: Color, + paddingEnd: Int?, + navController: NavController, + resourceData: ResourceData? = null, + context: Context? = null, +) { + Box( + contentAlignment = Alignment.Center, + modifier = + modifier + .conditional( + imageProperties.shape != null, + { clip(imageProperties.shape!!.composeShape) }, + { clip(RoundedCornerShape(imageProperties.borderRadius.dp)) }, + ) + .conditional( + imageProperties.size != null, + { size(imageProperties.size!!.dp) }, + { size(24.dp) }, + ) + .conditional( + !imageProperties.backgroundColor.isNullOrEmpty(), + { background(imageProperties.backgroundColor.parseColor()) }, + ) + .conditional(imageProperties.padding >= 0, { padding(imageProperties.padding.dp) }) + .conditional( + imageProperties.clickable.toBoolean() && imageProperties.visible.toBoolean(), + { + clickable( + onClick = { + imageProperties.actions.handleClickEvent( + navController = navController, + resourceData = resourceData, + context = context, + ) + }, ) - } + }, + ), + ) { + 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 (imageConfig.decodedBitmap != null) { + Image( + modifier = + Modifier.testTag(SIDE_MENU_ITEM_REMOTE_ICON_TEST_TAG) + .conditional(paddingEnd != null, { padding(end = paddingEnd?.dp!!) }) + .align(Alignment.Center) + .fillMaxSize(0.9f), + bitmap = imageConfig.decodedBitmap!!.asImageBitmap(), + contentDescription = null, + contentScale = ContentScale.Crop, + colorFilter = ColorFilter.tint(tint ?: imageProperties.imageConfig?.color.parseColor()), + ) + } } } } @@ -125,5 +204,27 @@ fun ImagePreview() { shape = ImageShape.CIRCLE, ), tint = DangerColor.copy(0.1f), + resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + navController = rememberNavController(), + ) +} + +@PreviewWithBackgroundExcludeGenerated +@Composable +fun ClickableImageWithTextPreview() { + Image( + modifier = Modifier, + imageProperties = + ImageProperties( + imageConfig = ImageConfig(ICON_TYPE_LOCAL, "ic_copy", color = "#FFF000"), + backgroundColor = Color.White.toString(), + size = 24, + shape = ImageShape.RECTANGLE, + clickable = "true", + visible = "true", + text = "Click on the icon to copy your text", + ), + resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + navController = rememberNavController(), ) } 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 bc17a4418b..95877fe9bb 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 @@ -254,8 +254,8 @@ private fun RowScope.RenderActionButtons( ) { ActionableButton( buttonProperties = serviceCardProperties.serviceButton!!, - navController = navController, resourceData = resourceData, + navController = navController, ) } } @@ -276,8 +276,8 @@ private fun RowScope.RenderActionButtons( serviceCardProperties.services?.forEach { buttonProperties -> ActionableButton( buttonProperties = buttonProperties, - navController = navController, resourceData = resourceData, + navController = navController, ) } } 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 b081274791..12d1be7f3a 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 @@ -83,8 +83,8 @@ fun GenerateView( ActionableButton( modifier = modifier, buttonProperties = properties as ButtonProperties, - navController = navController, resourceData = resourceData, + navController = navController, ) ViewType.COLUMN -> { val children = (properties as ColumnProperties).children @@ -255,7 +255,13 @@ fun GenerateView( resourceData = resourceData, navController = navController, ) - ViewType.IMAGE -> Image(modifier = modifier, imageProperties = properties as ImageProperties) + ViewType.IMAGE -> + Image( + modifier = modifier, + imageProperties = properties as ImageProperties, + resourceData = resourceData, + navController = navController, + ) } } } 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 3c749e6472..c2079d4ffa 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 @@ -16,8 +16,12 @@ package org.smartregister.fhircore.quest.util.extensions +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context import android.content.Intent import android.net.Uri +import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.navigation.NavController @@ -34,6 +38,8 @@ import org.smartregister.fhircore.engine.util.extension.encodeJson import org.smartregister.fhircore.engine.util.extension.extractLogicalIdUuid import org.smartregister.fhircore.engine.util.extension.interpolate import org.smartregister.fhircore.engine.util.extension.isIn +import org.smartregister.fhircore.engine.util.extension.showToast +import org.smartregister.fhircore.quest.R import org.smartregister.fhircore.quest.navigation.MainNavigationScreen import org.smartregister.fhircore.quest.navigation.NavigationArg import org.smartregister.fhircore.quest.ui.shared.QuestionnaireHandler @@ -45,9 +51,11 @@ fun List.handleClickEvent( navController: NavController, resourceData: ResourceData? = null, navMenu: NavigationMenuConfig? = null, + context: Context? = null, ) { val onClickAction = this.find { it.trigger.isIn(ActionTrigger.ON_CLICK, ActionTrigger.ON_QUESTIONNAIRE_SUBMISSION) } + onClickAction?.let { theConfig -> val computedValuesMap = resourceData?.computedValuesMap ?: emptyMap() val actionConfig = theConfig.interpolate(computedValuesMap) @@ -154,6 +162,17 @@ fun List.handleClickEvent( intent.data = Uri.parse("tel:$patientPhoneNumber") ContextCompat.startActivity(navController.context, intent, null) } + ApplicationWorkflow.COPY_TEXT -> { + val copyTextActionParameter = interpolatedParams.first() + val clipboardManager = + context?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = ClipData.newPlainText(null, copyTextActionParameter.value) + clipboardManager.setPrimaryClip(clipData) + context.showToast( + context.getString(R.string.copy_text_success_message, copyTextActionParameter.value), + Toast.LENGTH_LONG, + ) + } else -> return } } diff --git a/android/quest/src/main/res/drawable/ic_copy.xml b/android/quest/src/main/res/drawable/ic_copy.xml new file mode 100644 index 0000000000..2fef70dcd4 --- /dev/null +++ b/android/quest/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/quest/src/main/res/values/strings.xml b/android/quest/src/main/res/values/strings.xml index 921621d752..65c7366a0e 100644 --- a/android/quest/src/main/res/values/strings.xml +++ b/android/quest/src/main/res/values/strings.xml @@ -109,5 +109,6 @@ Processing questionnaire data… Loading questionnaire… Clear All + Link %1$s copied successfully diff --git a/android/quest/src/test/assets/configs/app/profiles/household_profile_config.json b/android/quest/src/test/assets/configs/app/profiles/household_profile_config.json index a84df876cb..f3abf4bc3e 100644 --- a/android/quest/src/test/assets/configs/app/profiles/household_profile_config.json +++ b/android/quest/src/test/assets/configs/app/profiles/household_profile_config.json @@ -169,7 +169,6 @@ { "viewType": "LIST", "listResource": "Patient", - "padding": 16, "relatedResources": [ { "id": "householdPatients", diff --git a/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsTest.kt b/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsTest.kt index 0ea861177d..0f9a6fb7bd 100644 --- a/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsTest.kt +++ b/android/quest/src/test/java/org/smartregister/fhircore/quest/util/extensions/ConfigExtensionsTest.kt @@ -16,11 +16,12 @@ package org.smartregister.fhircore.quest.util.extensions +import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle -import androidx.core.content.ContextCompat.startActivity +import android.widget.Toast import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.NavOptions @@ -46,6 +47,8 @@ import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig import org.smartregister.fhircore.engine.domain.model.ResourceConfig import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.domain.model.ToolBarHomeNavigation +import org.smartregister.fhircore.engine.util.extension.showToast +import org.smartregister.fhircore.quest.R import org.smartregister.fhircore.quest.app.fakes.Faker import org.smartregister.fhircore.quest.navigation.MainNavigationScreen import org.smartregister.fhircore.quest.navigation.NavigationArg @@ -514,4 +517,32 @@ class ConfigExtensionsTest : RobolectricTest() { arrayOf(ActionParameter(key = "k", value = "v", paramType = ActionParameterType.PARAMDATA)) Assert.assertEquals(mapOf("k" to "v"), array.toParamDataMap()) } + + @Test + fun testShowToastWhenAnImageWithActionParamsIsPressed() { + val context = mockk(relaxed = true) + val navController = NavController(context) + val mockClipboardManager = mockk() + val clickAction = + ActionConfig( + trigger = ActionTrigger.ON_CLICK, + workflow = ApplicationWorkflow.COPY_TEXT.name, + params = + listOf( + ActionParameter( + key = "copyText", + paramType = ActionParameterType.PARAMDATA, + value = "https://my-url", + ), + ), + ) + val text = "Link ${clickAction.params.first().value} copied successfully" + every { context.getSystemService(Context.CLIPBOARD_SERVICE) } returns mockClipboardManager + every { + context.getString(R.string.copy_text_success_message, clickAction.params.first().value) + } returns text + every { mockClipboardManager.setPrimaryClip(any()) } returns Unit + listOf(clickAction).handleClickEvent(navController, resourceData, context = context) + verify { context.showToast(text, Toast.LENGTH_LONG) } + } } diff --git a/docs/engineering/android-app/configuring/config-types/widget.mdx b/docs/engineering/android-app/configuring/config-types/widget.mdx index 3abf899faf..202b36c42d 100644 --- a/docs/engineering/android-app/configuring/config-types/widget.mdx +++ b/docs/engineering/android-app/configuring/config-types/widget.mdx @@ -176,6 +176,7 @@ Defines the configuration properties of a Button view, which is a view that can ] ``` + ### Config properties of BUTTON |Property | Description | Required | Default | Values | |--|--|:--:|:--:|:--:| @@ -198,6 +199,39 @@ letterSpacing | The amount of space to be added between each letter of the butto buttonType | Specify the button size | | ButtonType.MEDIUM | `TINY`, `MEDIUM`, `BIG` | contentColor | The content color of the view, specified as a string in the format "#RRGGBB" or "#AARRGGBB". If this property is null, the view will use its parent's content color. | No | Null | | +#### How to use button with icon and copy ability +On top of the above properties, you need to add Image config properties see a `startIcon config`*** IMAGE widget ***. This displays the icon before the button text + + ``` json + { + "viewType": "BUTTON", + "smallSized": "true", + "text": "Button text", + "status": "DUE", + "visible": "visibility condition", + "enabled": "enable condition", + "startIcon": { + "type": "local", + "reference": "icon_name if local", + "color": "icon color" + }, + "actions": [ + { + "trigger": "ON_CLICK", + "workflow": "COPY_TEXT", + "params": [ + { + "paramType": "PARAMDATA", + "key": "your copy key e.g myKey", + "value": "The value to be copied" + } + ] + } + ] + } +``` + The data in the params section is transformed into resourceData and the value read in key-value and copied to the clipboard/keyboard + ## SERVICE_CARD widgets Displays Patient's details ,other related services and tasks. The details field within the SERVICE_CARD display patient's name and ID. @@ -535,4 +569,4 @@ After defining the above JSON example a view will be rendered with the following viewType | The type of widget that will be rendered in this case it is the `SPACER` view. | Yes | [ViewType.SPACER]| height | The height of the Spacer. | Yes | _ | backgroundColor | The background color of the view, specified as a string in the format "#RRGGBB" or "#AARRGGBB". If this property is null, the view will use its parent's background color. | No | #FFFFFF | -padding | Offsets the content of the view by a specific number of pixels. This should be a number | No | 0 | +padding | Offsets the content of the view by a specific number of pixels. This should be a number | No | 0 | \ No newline at end of file