From 8dd298422a59e0848c85814746951690b9163259 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 6 Feb 2024 17:02:08 +0300 Subject: [PATCH 01/16] Enhance Actionable Button to add ability to copy text --- .../workflow/ApplicationWorkflow.kt | 3 + .../shared/components/ActionableButtonTest.kt | 52 +++++++++++++++++- android/quest/src/main/ic_copy-playstore.png | Bin 0 -> 32536 bytes .../ui/shared/components/ActionableButton.kt | 10 +++- .../quest/util/extensions/ConfigExtensions.kt | 13 +++++ .../quest/src/main/res/drawable/ic_copy.xml | 22 ++++++++ 6 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 android/quest/src/main/ic_copy-playstore.png create mode 100644 android/quest/src/main/res/drawable/ic_copy.xml 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/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..d8b51fc39e 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 @@ -37,13 +36,14 @@ import org.smartregister.fhircore.engine.configuration.view.ButtonProperties import org.smartregister.fhircore.engine.configuration.workflow.ActionTrigger import org.smartregister.fhircore.engine.configuration.workflow.ApplicationWorkflow import org.smartregister.fhircore.engine.domain.model.ActionConfig +import org.smartregister.fhircore.engine.domain.model.ActionParameter +import org.smartregister.fhircore.engine.domain.model.ActionParameterType import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.domain.model.ServiceStatus 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() { @@ -100,6 +100,17 @@ class ActionableButtonTest { .performClick() } + @Test + fun testActionableButtonRendersAndCopyText() { + setCopyButtonContent("false") + + composeRule + .onNodeWithText("Copy Button Text", useUnmergedTree = true) + .assertExists() + .assertIsDisplayed() + .performClick() + } + private fun setContent( serviceStatus: String, enabled: String = "true", @@ -129,4 +140,41 @@ class ActionableButtonTest { } } } + + private fun setCopyButtonContent( + serviceStatus: String, + enabled: String = "true", + computedValuesMap: Map = emptyMap(), + ) { + composeRule.setContent { + Column(modifier = Modifier.height(50.dp)) { + ActionableButton( + buttonProperties = + ButtonProperties( + status = serviceStatus, + text = "Copy Button Text", + actions = + listOf( + ActionConfig( + trigger = ActionTrigger.ON_CLICK, + workflow = ApplicationWorkflow.COPY_TEXT.name, + params = + listOf( + ActionParameter( + key = "copyText", + value = "https://my-url", + paramType = ActionParameterType.PARAMDATA, + ), + ), + ), + ), + enabled = enabled, + startIcon = ImageConfig("ic_copy", ICON_TYPE_LOCAL), + ), + resourceData = ResourceData("id", ResourceType.Patient, computedValuesMap), + navController = TestNavHostController(LocalContext.current), + ) + } + } + } } diff --git a/android/quest/src/main/ic_copy-playstore.png b/android/quest/src/main/ic_copy-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..acf26e8f2c8d341d0c5ace73dc1dfafd57fdf5e6 GIT binary patch literal 32536 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 Ij&.handleClickEvent( navController: NavController, resourceData: ResourceData? = null, navMenu: NavigationMenuConfig? = null, + context: Context? = null, + copyText: String? = 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 +160,13 @@ 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) + } 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..3a5e827ed5 --- /dev/null +++ b/android/quest/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,22 @@ + + + + + + From b653da581ff13dfd29bfaec56dbecb35716b658f Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 6 Feb 2024 17:09:22 +0300 Subject: [PATCH 02/16] Remove unnecessary icon --- android/quest/src/main/ic_copy-playstore.png | Bin 32536 -> 0 bytes .../fhircore/quest/ui/main/AppMainViewModel.kt | 6 +++--- .../quest/ui/profile/ProfileFragment.kt | 4 ++-- .../quest/ui/shared/components/ExtendedFab.kt | 4 ++-- .../quest/ui/shared/components/ServiceCard.kt | 12 ++++++------ .../quest/util/extensions/ConfigExtensions.kt | 1 - .../util/extensions/ConfigExtensionsTest.kt | 1 - 7 files changed, 13 insertions(+), 15 deletions(-) delete mode 100644 android/quest/src/main/ic_copy-playstore.png diff --git a/android/quest/src/main/ic_copy-playstore.png b/android/quest/src/main/ic_copy-playstore.png deleted file mode 100644 index acf26e8f2c8d341d0c5ace73dc1dfafd57fdf5e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32536 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 Ij& event.navMenu.actions?.handleClickEvent( - navController = event.navController, - resourceData = null, - navMenu = event.navMenu, + navController = event.navController, + resourceData = null, + navMenu = event.navMenu, ) is AppMainEvent.OpenProfile -> { val args = 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 1c0feb5515..0924c2a593 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 @@ -130,8 +130,8 @@ class ProfileFragment : Fragment() { if (onSubmitActions != null) { appMainViewModel.retrieveAppMainUiState(refreshAll = false) onSubmitActions.handleClickEvent( - navController = findNavController(), - resourceData = profileViewModel.profileUiState.value.resourceData, + navController = findNavController(), + resourceData = profileViewModel.profileUiState.value.resourceData, ) } } 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..7a83400a4a 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 @@ -67,8 +67,8 @@ fun ExtendedFab( onClick = { if (firstFabEnabled) { firstFabAction.actions?.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, 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..0069d55c04 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 @@ -96,8 +96,8 @@ fun ServiceCard( { clickable { serviceCardProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, @@ -137,8 +137,8 @@ fun ServiceCard( { clickable { serviceCardProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, @@ -325,8 +325,8 @@ private fun BigServiceButton( .clickable { if (isButtonEnabled && (status == ServiceStatus.DUE.name || buttonClickable)) { buttonProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, 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 eb46307faf..5890713dbb 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 @@ -49,7 +49,6 @@ fun List.handleClickEvent( resourceData: ResourceData? = null, navMenu: NavigationMenuConfig? = null, context: Context? = null, - copyText: String? = null, ) { val onClickAction = this.find { it.trigger.isIn(ActionTrigger.ON_CLICK, ActionTrigger.ON_QUESTIONNAIRE_SUBMISSION) } 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..c26766273a 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 @@ -20,7 +20,6 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle -import androidx.core.content.ContextCompat.startActivity import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.NavOptions From 37f8b5665310f629514c1b3750ada3998df7b34c Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 6 Feb 2024 17:10:43 +0300 Subject: [PATCH 03/16] Run spotlessApply --- .../fhircore/quest/ui/main/AppMainViewModel.kt | 6 +++--- .../fhircore/quest/ui/profile/ProfileFragment.kt | 4 ++-- .../quest/ui/shared/components/ExtendedFab.kt | 4 ++-- .../quest/ui/shared/components/ServiceCard.kt | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) 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 d063d0603b..04c9128536 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 @@ -193,9 +193,9 @@ constructor( } is AppMainEvent.TriggerWorkflow -> event.navMenu.actions?.handleClickEvent( - navController = event.navController, - resourceData = null, - navMenu = event.navMenu, + navController = event.navController, + resourceData = null, + navMenu = event.navMenu, ) is AppMainEvent.OpenProfile -> { val args = 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 0924c2a593..1c0feb5515 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 @@ -130,8 +130,8 @@ class ProfileFragment : Fragment() { if (onSubmitActions != null) { appMainViewModel.retrieveAppMainUiState(refreshAll = false) onSubmitActions.handleClickEvent( - navController = findNavController(), - resourceData = profileViewModel.profileUiState.value.resourceData, + navController = findNavController(), + resourceData = profileViewModel.profileUiState.value.resourceData, ) } } 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 7a83400a4a..d297dc589c 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 @@ -67,8 +67,8 @@ fun ExtendedFab( onClick = { if (firstFabEnabled) { firstFabAction.actions?.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, 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 0069d55c04..bc17a4418b 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 @@ -96,8 +96,8 @@ fun ServiceCard( { clickable { serviceCardProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, @@ -137,8 +137,8 @@ fun ServiceCard( { clickable { serviceCardProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, @@ -325,8 +325,8 @@ private fun BigServiceButton( .clickable { if (isButtonEnabled && (status == ServiceStatus.DUE.name || buttonClickable)) { buttonProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, + navController = navController, + resourceData = resourceData, ) } }, From ffdad0f09c5aa8cf05bfc2264884f927e258e1e8 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 6 Feb 2024 17:17:32 +0300 Subject: [PATCH 04/16] Remove context as param from Composable function --- .../fhircore/quest/ui/shared/components/ActionableButton.kt | 3 +-- .../fhircore/quest/ui/shared/components/ServiceCard.kt | 4 ++-- .../fhircore/quest/ui/shared/components/ViewGenerator.kt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) 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 162cf02104..c50ff3aaa4 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,7 +16,6 @@ package org.smartregister.fhircore.quest.ui.shared.components -import android.content.Context import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -69,7 +68,6 @@ fun ActionableButton( buttonProperties: ButtonProperties, resourceData: ResourceData, navController: NavController, - context: Context = LocalContext.current, ) { if (buttonProperties.visible.toBoolean()) { val status = buttonProperties.status @@ -83,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 ( 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 e629a32226..0a740f6492 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 @@ -81,8 +81,8 @@ fun GenerateView( ActionableButton( modifier = modifier, buttonProperties = properties as ButtonProperties, - navController = navController, resourceData = resourceData, + navController = navController, ) ViewType.COLUMN -> { val children = (properties as ColumnProperties).children From 4028f405838138658c51250974cec3989cb7eb0c Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Thu, 8 Feb 2024 11:11:19 +0300 Subject: [PATCH 05/16] Refactor Image Widget to enable on click --- .../navigation/NavigationMenuConfig.kt | 1 + .../configuration/view/ImageProperties.kt | 2 + .../quest/ui/main/AppMainViewModel.kt | 1 - .../quest/ui/main/components/AppDrawer.kt | 5 ++ .../quest/ui/profile/ProfileScreen.kt | 2 + .../quest/ui/register/RegisterFragment.kt | 1 - .../ui/shared/components/ActionableButton.kt | 11 ++--- .../quest/ui/shared/components/ExtendedFab.kt | 2 + .../quest/ui/shared/components/Image.kt | 47 ++++++++++++++++++- .../ui/shared/components/ViewGenerator.kt | 8 +++- .../quest/util/extensions/ConfigExtensions.kt | 7 +++ android/quest/src/main/res/values/strings.xml | 1 + 12 files changed, 75 insertions(+), 13 deletions(-) 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..2c30fca9c8 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 @@ -43,6 +44,7 @@ data class ImageProperties( val imageConfig: ImageConfig? = null, val size: Int? = null, val shape: ImageShape? = null, + val actions: List = emptyList(), ) : ViewProperties(), Parcelable { override fun interpolate(computedValuesMap: Map): ViewProperties { return this.copy( 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 04c9128536..0812ba682a 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 bd9be2b2fe..62d47f5e00 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 @@ -59,6 +59,7 @@ import org.smartregister.fhircore.engine.configuration.navigation.NavigationConf import org.smartregister.fhircore.engine.configuration.navigation.NavigationMenuConfig import org.smartregister.fhircore.engine.configuration.view.ImageProperties import org.smartregister.fhircore.engine.domain.model.Language +import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.ui.theme.AppTitleColor import org.smartregister.fhircore.engine.ui.theme.MenuActionButtonTextColor import org.smartregister.fhircore.engine.ui.theme.MenuItemColor @@ -93,6 +94,7 @@ fun AppDrawer( modifier: Modifier = Modifier, appUiState: AppMainUiState, navController: NavController, + resourceData: ResourceData? = null, openDrawer: (Boolean) -> Unit, onSideMenuClick: (AppMainEvent) -> Unit, appVersionPair: Pair? = null, @@ -160,6 +162,7 @@ fun AppDrawer( onSideMenuClick = onSideMenuClick, openDrawer = openDrawer, navController = navController, + resourceData = resourceData, ) if (navigationConfiguration.staticMenu.isNotEmpty()) Divider(color = DividerColor) } @@ -220,6 +223,7 @@ private fun OtherPatientsItem( onSideMenuClick: (AppMainEvent) -> Unit, openDrawer: (Boolean) -> Unit, navController: NavController, + resourceData: ResourceData? = null, ) { val context = LocalContext.current SideMenuItem( @@ -355,6 +359,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 521636b454..5af6f46063 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 c1a8d89edc..3dab038a5d 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 c50ff3aaa4..d26a4d64b2 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,7 +35,6 @@ 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 @@ -81,7 +80,6 @@ fun ActionableButton( val clickable = buttonProperties.clickable.toBoolean() val backgroundOpacity = buttonProperties.backgroundOpacity val colorOpacity = buttonProperties.colorOpacity - val context = LocalContext.current OutlinedButton( onClick = { if ( @@ -91,7 +89,6 @@ fun ActionableButton( buttonProperties.actions.handleClickEvent( navController = navController, resourceData = resourceData, - context = context, ) } }, @@ -155,12 +152,10 @@ fun ActionableButton( } if (buttonProperties.startIcon != null) { Image( - imageProperties = - ImageProperties( - imageConfig = buttonProperties.startIcon, - size = 16, - ), + 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/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..788f0725dd 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 @@ -18,6 +18,7 @@ package org.smartregister.fhircore.quest.ui.shared.components import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -29,17 +30,22 @@ 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.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 +53,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,8 +64,11 @@ 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 context = LocalContext.current if (imageConfig != null) { Box( contentAlignment = Alignment.Center, @@ -78,7 +88,18 @@ fun Image( !imageProperties.backgroundColor.isNullOrEmpty(), { background(imageProperties.backgroundColor.parseColor()) }, ) - .conditional(imageProperties.padding >= 0, { padding(imageProperties.padding.dp) }), + .conditional(imageProperties.padding >= 0, { padding(imageProperties.padding.dp) }) + .clickable( + onClick = { + if (imageProperties.visible.toBoolean() && imageProperties.clickable.toBoolean()) { + imageProperties.actions.handleClickEvent( + navController = navController, + resourceData = resourceData, + context = context, + ) + } + } + ), ) { when (imageConfig.type) { ICON_TYPE_LOCAL -> @@ -91,7 +112,7 @@ fun Image( .fillMaxSize(0.9f), painter = painterResource(id = drawableId), contentDescription = SIDE_MENU_ICON, - tint = tint ?: imageProperties.tint.parseColor(), + tint = tint ?: imageProperties.imageConfig?.color.parseColor(), ) } ICON_TYPE_REMOTE -> @@ -105,6 +126,7 @@ fun Image( bitmap = imageConfig.decodedBitmap!!.asImageBitmap(), contentDescription = null, contentScale = ContentScale.Crop, + colorFilter = ColorFilter.tint(tint!!), ) } } @@ -125,5 +147,26 @@ fun ImagePreview() { shape = ImageShape.CIRCLE, ), tint = DangerColor.copy(0.1f), + resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + navController = rememberNavController(), + ) +} + +@PreviewWithBackgroundExcludeGenerated +@Composable +fun ClickableImagePreview() { + 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", + ), + resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), + navController = rememberNavController(), ) } 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 0a740f6492..e0983c0e7b 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 @@ -253,7 +253,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 5890713dbb..fdbafa1f98 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 @@ -21,6 +21,7 @@ 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 @@ -37,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 @@ -165,6 +168,10 @@ fun List.handleClickEvent( 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/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 From 0e97195d4bfa99a8da37788e21e17abe36d7301b Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Thu, 8 Feb 2024 11:47:53 +0300 Subject: [PATCH 06/16] Remove unnecessary ActionableButton previews --- .../shared/components/ActionableButtonTest.kt | 48 ------------------- .../quest/ui/main/components/AppDrawer.kt | 3 -- 2 files changed, 51 deletions(-) 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 d8b51fc39e..939b9065a6 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 @@ -100,17 +100,6 @@ class ActionableButtonTest { .performClick() } - @Test - fun testActionableButtonRendersAndCopyText() { - setCopyButtonContent("false") - - composeRule - .onNodeWithText("Copy Button Text", useUnmergedTree = true) - .assertExists() - .assertIsDisplayed() - .performClick() - } - private fun setContent( serviceStatus: String, enabled: String = "true", @@ -140,41 +129,4 @@ class ActionableButtonTest { } } } - - private fun setCopyButtonContent( - serviceStatus: String, - enabled: String = "true", - computedValuesMap: Map = emptyMap(), - ) { - composeRule.setContent { - Column(modifier = Modifier.height(50.dp)) { - ActionableButton( - buttonProperties = - ButtonProperties( - status = serviceStatus, - text = "Copy Button Text", - actions = - listOf( - ActionConfig( - trigger = ActionTrigger.ON_CLICK, - workflow = ApplicationWorkflow.COPY_TEXT.name, - params = - listOf( - ActionParameter( - key = "copyText", - value = "https://my-url", - paramType = ActionParameterType.PARAMDATA, - ), - ), - ), - ), - enabled = enabled, - startIcon = ImageConfig("ic_copy", ICON_TYPE_LOCAL), - ), - resourceData = ResourceData("id", ResourceType.Patient, computedValuesMap), - navController = TestNavHostController(LocalContext.current), - ) - } - } - } } 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 b5f177271b..3cc4ab8326 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 @@ -94,7 +94,6 @@ fun AppDrawer( modifier: Modifier = Modifier, appUiState: AppMainUiState, navController: NavController, - resourceData: ResourceData? = null, openDrawer: (Boolean) -> Unit, onSideMenuClick: (AppMainEvent) -> Unit, appVersionPair: Pair? = null, @@ -162,7 +161,6 @@ fun AppDrawer( onSideMenuClick = onSideMenuClick, openDrawer = openDrawer, navController = navController, - resourceData = resourceData, ) if (navigationConfiguration.staticMenu.isNotEmpty()) Divider(color = DividerColor) } @@ -223,7 +221,6 @@ private fun OtherPatientsItem( onSideMenuClick: (AppMainEvent) -> Unit, openDrawer: (Boolean) -> Unit, navController: NavController, - resourceData: ResourceData? = null, ) { val context = LocalContext.current SideMenuItem( From 84182efe4a102219e934975ba5df33803e046b09 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Thu, 8 Feb 2024 18:54:25 +0300 Subject: [PATCH 07/16] Add text property --- .../configuration/ConfigurationRegistry.kt | 2 +- .../configuration/view/ImageProperties.kt | 2 + .../quest/ui/shared/components/Image.kt | 175 ++++++++++++------ 3 files changed, 119 insertions(+), 60 deletions(-) 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 390b8a6aa9..eeb80bb4a8 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 @@ -416,7 +416,7 @@ constructor( } // is focus.identifier a necessary check .groupBy { section -> section.focus.reference.substringBefore( - ConfigurationRegistry.TYPE_REFERENCE_DELIMITER, + TYPE_REFERENCE_DELIMITER, missingDelimiterValue = "", ) } 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 2c30fca9c8..611e75ae4c 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 @@ -41,6 +41,7 @@ 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, @@ -55,6 +56,7 @@ data class ImageProperties( ), tint = this.tint?.interpolate(computedValuesMap), backgroundColor = this.backgroundColor?.interpolate(computedValuesMap), + text = this.text?.interpolate(computedValuesMap) ) } } 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 788f0725dd..91d7fb13e0 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,15 +16,19 @@ 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 @@ -36,6 +40,8 @@ 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.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController @@ -70,70 +76,120 @@ fun Image( val imageConfig = imageProperties.imageConfig val context = 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) }) - .clickable( - onClick = { - if (imageProperties.visible.toBoolean() && imageProperties.clickable.toBoolean()) { - 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 ?: imageProperties.imageConfig?.color.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, - colorFilter = ColorFilter.tint(tint!!), + 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.imageConfig?.color.parseColor(), + textDecoration = TextDecoration.Underline + ) + ClickableImageIcon( + imageProperties =imageProperties , + imageConfig =imageConfig , + tint = tint, + paddingEnd = paddingEnd, + navController =navController , + resourceData = resourceData, + context = context, + modifier = modifier + ) + } + } + else { + ClickableImageIcon( + imageProperties =imageProperties , + imageConfig =imageConfig , + tint = tint, + paddingEnd = paddingEnd, + navController =navController , + resourceData = resourceData, + context = context, + modifier = modifier + ) + + }} + + +} +@Composable +fun ClickableImageIcon( + modifier: Modifier = Modifier, + imageProperties: ImageProperties, + imageConfig: ImageConfig, + tint: Color?, + paddingEnd: Int?, + navController: NavController, + resourceData: ResourceData?=null, + context: Context +) { + 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) }) + .clickable( + onClick = { + if (imageProperties.visible.toBoolean() && imageProperties.clickable.toBoolean()) { + 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 ?: imageProperties.imageConfig?.color.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, + colorFilter = ColorFilter.tint(tint!!), + ) + } } } } + @PreviewWithBackgroundExcludeGenerated @Composable fun ImagePreview() { @@ -154,7 +210,7 @@ fun ImagePreview() { @PreviewWithBackgroundExcludeGenerated @Composable -fun ClickableImagePreview() { +fun ClickableImageWithTextPreview() { Image( modifier = Modifier, imageProperties = @@ -165,6 +221,7 @@ fun ClickableImagePreview() { shape = ImageShape.RECTANGLE, clickable = "true", visible = "true", + text = "Click on the icon to copy your text" ), resourceData = ResourceData("id", ResourceType.Patient, emptyMap()), navController = rememberNavController(), From 5723b56900a1761ed4f7823377fbc6c1ed8c9890 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Thu, 8 Feb 2024 19:07:19 +0300 Subject: [PATCH 08/16] Run spotlessApply --- .../configuration/view/ImageProperties.kt | 4 +- .../shared/components/ActionableButtonTest.kt | 2 - .../quest/ui/main/components/AppDrawer.kt | 1 - .../quest/ui/shared/components/Image.kt | 114 +++++++++--------- .../ui/shared/components/ViewGenerator.kt | 2 +- .../quest/util/extensions/ConfigExtensions.kt | 2 +- 6 files changed, 60 insertions(+), 65 deletions(-) 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 611e75ae4c..d4f9ff7475 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 @@ -41,7 +41,7 @@ data class ImageProperties( override val clickable: String = "false", override val visible: String = "true", val tint: String? = null, - val text:String?=null, + val text: String? = null, val imageConfig: ImageConfig? = null, val size: Int? = null, val shape: ImageShape? = null, @@ -56,7 +56,7 @@ data class ImageProperties( ), tint = this.tint?.interpolate(computedValuesMap), backgroundColor = this.backgroundColor?.interpolate(computedValuesMap), - text = this.text?.interpolate(computedValuesMap) + text = this.text?.interpolate(computedValuesMap), ) } } 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 939b9065a6..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 @@ -36,8 +36,6 @@ import org.smartregister.fhircore.engine.configuration.view.ButtonProperties import org.smartregister.fhircore.engine.configuration.workflow.ActionTrigger import org.smartregister.fhircore.engine.configuration.workflow.ApplicationWorkflow import org.smartregister.fhircore.engine.domain.model.ActionConfig -import org.smartregister.fhircore.engine.domain.model.ActionParameter -import org.smartregister.fhircore.engine.domain.model.ActionParameterType import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.domain.model.ServiceStatus import org.smartregister.fhircore.quest.ui.shared.components.ActionableButton 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 3cc4ab8326..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 @@ -59,7 +59,6 @@ import org.smartregister.fhircore.engine.configuration.navigation.NavigationConf import org.smartregister.fhircore.engine.configuration.navigation.NavigationMenuConfig import org.smartregister.fhircore.engine.configuration.view.ImageProperties import org.smartregister.fhircore.engine.domain.model.Language -import org.smartregister.fhircore.engine.domain.model.ResourceData import org.smartregister.fhircore.engine.ui.theme.AppTitleColor import org.smartregister.fhircore.engine.ui.theme.MenuActionButtonTextColor import org.smartregister.fhircore.engine.ui.theme.MenuItemColor 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 91d7fb13e0..02262482b0 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 @@ -76,46 +76,44 @@ fun Image( val imageConfig = imageProperties.imageConfig val context = LocalContext.current if (imageConfig != null) { - if(imageProperties.text!=null) { + if (imageProperties.text != null) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Text( - text = imageProperties.text !!, + text = imageProperties.text!!, textAlign = TextAlign.Center, modifier = Modifier.padding(end = 8.dp), color = imageProperties.imageConfig?.color.parseColor(), - textDecoration = TextDecoration.Underline + textDecoration = TextDecoration.Underline, ) ClickableImageIcon( - imageProperties =imageProperties , - imageConfig =imageConfig , + imageProperties = imageProperties, + imageConfig = imageConfig, tint = tint, paddingEnd = paddingEnd, - navController =navController , + navController = navController, resourceData = resourceData, context = context, - modifier = modifier + modifier = modifier, ) } - } - else { + } else { ClickableImageIcon( - imageProperties =imageProperties , - imageConfig =imageConfig , + imageProperties = imageProperties, + imageConfig = imageConfig, tint = tint, paddingEnd = paddingEnd, - navController =navController , + navController = navController, resourceData = resourceData, context = context, - modifier = modifier + modifier = modifier, ) - - }} - - + } + } } + @Composable fun ClickableImageIcon( modifier: Modifier = Modifier, @@ -124,48 +122,49 @@ fun ClickableImageIcon( tint: Color?, paddingEnd: Int?, navController: NavController, - resourceData: ResourceData?=null, - context: Context + resourceData: ResourceData? = null, + context: Context, ) { 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) }) - .clickable( - onClick = { - if (imageProperties.visible.toBoolean() && imageProperties.clickable.toBoolean()) { - imageProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, - context = context, - ) - } - } - ) + 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) }) + .clickable( + onClick = { + if (imageProperties.visible.toBoolean() && imageProperties.clickable.toBoolean()) { + 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), + 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.imageConfig?.color.parseColor(), @@ -174,11 +173,11 @@ fun ClickableImageIcon( 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), + 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, @@ -189,7 +188,6 @@ fun ClickableImageIcon( } } - @PreviewWithBackgroundExcludeGenerated @Composable fun ImagePreview() { @@ -221,7 +219,7 @@ fun ClickableImageWithTextPreview() { shape = ImageShape.RECTANGLE, clickable = "true", visible = "true", - text = "Click on the icon to copy your text" + 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/ViewGenerator.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/ViewGenerator.kt index 343d740584..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 @@ -260,7 +260,7 @@ fun GenerateView( modifier = modifier, imageProperties = properties as ImageProperties, resourceData = resourceData, - navController = navController + 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 c673f1e588..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 @@ -170,7 +170,7 @@ fun List.handleClickEvent( clipboardManager.setPrimaryClip(clipData) context.showToast( context.getString(R.string.copy_text_success_message, copyTextActionParameter.value), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) } else -> return From 43fcd0fd22ca5dc7e10f50c614dca297de4116dc Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Fri, 9 Feb 2024 12:17:35 +0300 Subject: [PATCH 09/16] Fix failing tests and add test to confirm toast is shown --- .../ui/shared/components/ViewGeneratorTest.kt | 13 ++++++-- .../quest/ui/shared/components/Image.kt | 16 +++++----- .../util/extensions/ConfigExtensionsTest.kt | 32 +++++++++++++++++++ 3 files changed, 50 insertions(+), 11 deletions(-) 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..99ffeaeb94 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/shared/components/Image.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/Image.kt index 02262482b0..6ea616cb13 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 @@ -41,7 +41,6 @@ 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.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController @@ -75,6 +74,7 @@ fun Image( ) { val imageConfig = imageProperties.imageConfig val context = LocalContext.current + val colorTint = tint ?: imageProperties.imageConfig?.color.parseColor() if (imageConfig != null) { if (imageProperties.text != null) { Row( @@ -86,12 +86,11 @@ fun Image( textAlign = TextAlign.Center, modifier = Modifier.padding(end = 8.dp), color = imageProperties.imageConfig?.color.parseColor(), - textDecoration = TextDecoration.Underline, ) ClickableImageIcon( imageProperties = imageProperties, imageConfig = imageConfig, - tint = tint, + tint = colorTint, paddingEnd = paddingEnd, navController = navController, resourceData = resourceData, @@ -103,7 +102,7 @@ fun Image( ClickableImageIcon( imageProperties = imageProperties, imageConfig = imageConfig, - tint = tint, + tint = colorTint, paddingEnd = paddingEnd, navController = navController, resourceData = resourceData, @@ -119,7 +118,7 @@ fun ClickableImageIcon( modifier: Modifier = Modifier, imageProperties: ImageProperties, imageConfig: ImageConfig, - tint: Color?, + tint: Color, paddingEnd: Int?, navController: NavController, resourceData: ResourceData? = null, @@ -157,7 +156,7 @@ fun ClickableImageIcon( ), ) { when (imageConfig.type) { - ICON_TYPE_LOCAL -> + ICON_TYPE_LOCAL -> { LocalContext.current.retrieveResourceId(imageConfig.reference)?.let { drawableId -> Icon( modifier = @@ -167,9 +166,10 @@ fun ClickableImageIcon( .fillMaxSize(0.9f), painter = painterResource(id = drawableId), contentDescription = SIDE_MENU_ICON, - tint = tint ?: imageProperties.imageConfig?.color.parseColor(), + tint = tint, ) } + } ICON_TYPE_REMOTE -> if (imageConfig.decodedBitmap != null) { Image( @@ -181,7 +181,7 @@ fun ClickableImageIcon( bitmap = imageConfig.decodedBitmap!!.asImageBitmap(), contentDescription = null, contentScale = ContentScale.Crop, - colorFilter = ColorFilter.tint(tint!!), + colorFilter = ColorFilter.tint(tint ?: imageProperties.imageConfig?.color.parseColor()), ) } } 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 c26766273a..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,10 +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 android.widget.Toast import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.NavOptions @@ -45,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 @@ -513,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) } + } } From e70a6051b6f2df24d4f72a50839478371ac860ca Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Fri, 9 Feb 2024 12:25:44 +0300 Subject: [PATCH 10/16] Run spotlessApply --- .../quest/integration/ui/shared/components/ViewGeneratorTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 99ffeaeb94..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 @@ -353,7 +353,7 @@ class ViewGeneratorTest { properties = ImageProperties( imageConfig = ImageConfig(ICON_TYPE_LOCAL, "ic_walk", color = "#FFF000"), - text = "Copy text" + text = "Copy text", ), resourceData = resourceData, navController = TestNavHostController(LocalContext.current), From 870e97a0e0a3f5e131ee8df6cc0b55a29911b281 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Mon, 12 Feb 2024 11:51:20 +0300 Subject: [PATCH 11/16] Add text color --- .../fhircore/engine/configuration/view/ImageProperties.kt | 1 + .../smartregister/fhircore/quest/ui/shared/components/Image.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 d4f9ff7475..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 @@ -45,6 +45,7 @@ data class ImageProperties( 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 { 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 6ea616cb13..b230532f17 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 @@ -85,7 +85,7 @@ fun Image( text = imageProperties.text!!, textAlign = TextAlign.Center, modifier = Modifier.padding(end = 8.dp), - color = imageProperties.imageConfig?.color.parseColor(), + color = imageProperties.textColor?.parseColor() ?: Color.Gray, ) ClickableImageIcon( imageProperties = imageProperties, From fc5091df638cf8b8b03385a63d1c76c328fc091a Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 13 Feb 2024 18:32:48 +0300 Subject: [PATCH 12/16] Update button as per design requirements --- .../ui/shared/components/ActionableButton.kt | 3 + .../quest/ui/shared/components/CardView.kt | 87 ++++++++++--------- .../quest/ui/shared/components/Image.kt | 7 +- .../quest/src/main/res/drawable/ic_copy.xml | 25 ++---- 4 files changed, 55 insertions(+), 67 deletions(-) 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 d26a4d64b2..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, ) } }, 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/Image.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/shared/components/Image.kt index b230532f17..7e3d2582e3 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,7 +16,6 @@ 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 @@ -73,7 +72,6 @@ fun Image( resourceData: ResourceData? = null, ) { val imageConfig = imageProperties.imageConfig - val context = LocalContext.current val colorTint = tint ?: imageProperties.imageConfig?.color.parseColor() if (imageConfig != null) { if (imageProperties.text != null) { @@ -94,7 +92,6 @@ fun Image( paddingEnd = paddingEnd, navController = navController, resourceData = resourceData, - context = context, modifier = modifier, ) } @@ -106,7 +103,6 @@ fun Image( paddingEnd = paddingEnd, navController = navController, resourceData = resourceData, - context = context, modifier = modifier, ) } @@ -122,7 +118,6 @@ fun ClickableImageIcon( paddingEnd: Int?, navController: NavController, resourceData: ResourceData? = null, - context: Context, ) { Box( contentAlignment = Alignment.Center, @@ -149,7 +144,7 @@ fun ClickableImageIcon( imageProperties.actions.handleClickEvent( navController = navController, resourceData = resourceData, - context = context, + context = null, ) } }, diff --git a/android/quest/src/main/res/drawable/ic_copy.xml b/android/quest/src/main/res/drawable/ic_copy.xml index 3a5e827ed5..2fef70dcd4 100644 --- a/android/quest/src/main/res/drawable/ic_copy.xml +++ b/android/quest/src/main/res/drawable/ic_copy.xml @@ -1,22 +1,9 @@ + android:width="17dp" + android:height="16dp" + android:viewportWidth="17" + android:viewportHeight="16"> - - - + android:pathData="M6.5,12C6.133,12 5.819,11.869 5.558,11.608C5.297,11.347 5.167,11.033 5.167,10.666V2.666C5.167,2.3 5.297,1.986 5.558,1.725C5.819,1.464 6.133,1.333 6.5,1.333H12.5C12.867,1.333 13.181,1.464 13.442,1.725C13.703,1.986 13.833,2.3 13.833,2.666V10.666C13.833,11.033 13.703,11.347 13.442,11.608C13.181,11.869 12.867,12 12.5,12H6.5ZM6.5,10.666H12.5V2.666H6.5V10.666ZM3.833,14.666C3.467,14.666 3.153,14.536 2.892,14.275C2.631,14.014 2.5,13.7 2.5,13.333V4.666C2.5,4.477 2.564,4.319 2.692,4.191C2.819,4.064 2.978,4 3.167,4C3.356,4 3.514,4.064 3.642,4.191C3.769,4.319 3.833,4.477 3.833,4.666V13.333H10.5C10.689,13.333 10.847,13.397 10.975,13.525C11.103,13.653 11.167,13.811 11.167,14C11.167,14.189 11.103,14.347 10.975,14.475C10.847,14.602 10.689,14.666 10.5,14.666H3.833Z" + android:fillColor="#0077CC"/> From e2b9fb672c0b6abdeea22f3ea08a6bce7601ab5c Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 13 Feb 2024 19:09:08 +0300 Subject: [PATCH 13/16] Add condition when clicking an image --- .../quest/ui/shared/components/Image.kt | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) 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 7e3d2582e3..ee689505c9 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,6 +16,7 @@ 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 @@ -73,6 +74,7 @@ fun Image( ) { val imageConfig = imageProperties.imageConfig val colorTint = tint ?: imageProperties.imageConfig?.color.parseColor() + val cotext = LocalContext.current if (imageConfig != null) { if (imageProperties.text != null) { Row( @@ -93,6 +95,7 @@ fun Image( navController = navController, resourceData = resourceData, modifier = modifier, + context = cotext, ) } } else { @@ -104,6 +107,7 @@ fun Image( navController = navController, resourceData = resourceData, modifier = modifier, + context = cotext, ) } } @@ -118,6 +122,7 @@ fun ClickableImageIcon( paddingEnd: Int?, navController: NavController, resourceData: ResourceData? = null, + context: Context? = null, ) { Box( contentAlignment = Alignment.Center, @@ -138,16 +143,19 @@ fun ClickableImageIcon( { background(imageProperties.backgroundColor.parseColor()) }, ) .conditional(imageProperties.padding >= 0, { padding(imageProperties.padding.dp) }) - .clickable( - onClick = { - if (imageProperties.visible.toBoolean() && imageProperties.clickable.toBoolean()) { - imageProperties.actions.handleClickEvent( - navController = navController, - resourceData = resourceData, - context = null, - ) - } - }, + .conditional( + imageProperties.clickable.toBoolean() && imageProperties.visible.toBoolean(), + { + clickable( + onClick = { + imageProperties.actions.handleClickEvent( + navController = navController, + resourceData = resourceData, + context = context, + ) + }, + ) + } ), ) { when (imageConfig.type) { From 90e585749ef85b0bba3a93173bacd8e4ad2572c6 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 13 Feb 2024 21:08:52 +0300 Subject: [PATCH 14/16] Run spotlessApply on Image.kt --- .../smartregister/fhircore/quest/ui/shared/components/Image.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ee689505c9..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 @@ -155,7 +155,7 @@ fun ClickableImageIcon( ) }, ) - } + }, ), ) { when (imageConfig.type) { From 2d956e9a68a9137f511ae33204995cc8ca3efe66 Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Wed, 14 Feb 2024 15:17:53 +0300 Subject: [PATCH 15/16] Add docs for Copy data on actionable button --- .../configuring/config-types/widget.mdx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) 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 From 16581000a74298a75b6ecae916b2d0edcd255c5b Mon Sep 17 00:00:00 2001 From: SebaMutuku Date: Tue, 20 Feb 2024 12:36:11 +0300 Subject: [PATCH 16/16] Add tests for ConfigurationRegistry.writeFile() --- .../configuration/ConfigurationRegistry.kt | 2 +- .../ConfigurationRegistryTest.kt | 106 +++++++++++------- .../profiles/household_profile_config.json | 1 - 3 files changed, 65 insertions(+), 44 deletions(-) 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 eeb80bb4a8..10669bb7e7 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 @@ -593,7 +593,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/test/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistryTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/configuration/ConfigurationRegistryTest.kt index 30b2fb6b64..995de4d5a6 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 @@ -44,16 +46,17 @@ import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent import org.hl7.fhir.r4.model.Composition import org.hl7.fhir.r4.model.Composition.SectionComponent import org.hl7.fhir.r4.model.Enumerations -import org.hl7.fhir.r4.model.Group import org.hl7.fhir.r4.model.Identifier import org.hl7.fhir.r4.model.ListResource 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 @@ -69,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 @@ -87,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 @@ -111,6 +117,8 @@ class ConfigurationRegistryTest : RobolectricTest() { } }, ) + mockedContext = mockk() + mockedJsonParser = mockk() configRegistry.setNonProxy(false) Assert.assertNotNull(configRegistry) } @@ -127,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 @@ -136,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 @@ -145,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 @@ -155,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 @@ -167,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 @@ -182,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 @@ -203,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 @@ -284,8 +292,8 @@ class ConfigurationRegistryTest : RobolectricTest() { coVerify(exactly = 1) { fhirEngine.get(any(), any()) } 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 @@ -560,7 +568,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)) @@ -571,7 +579,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) } @@ -618,7 +626,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 @@ -641,7 +649,7 @@ class ConfigurationRegistryTest : RobolectricTest() { ) } - Assert.assertEquals(21, compositionSections.size) + assertEquals(21, compositionSections.size) val composition = Composition().apply { @@ -664,12 +672,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(), ) @@ -721,16 +729,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 @@ -791,19 +799,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 @@ -825,8 +833,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/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",