diff --git a/form/src/main/java/org/dhis2/form/model/FormSection.kt b/form/src/main/java/org/dhis2/form/model/FormSection.kt index 302c3b3b4b..83325b54a4 100644 --- a/form/src/main/java/org/dhis2/form/model/FormSection.kt +++ b/form/src/main/java/org/dhis2/form/model/FormSection.kt @@ -8,6 +8,7 @@ data class FormSection( val description: String? = null, val state: SectionState, val fields: List, + var warningMessage: Int? = null, ) { fun completedFields() = fields.count { it.value != null } fun errorCount() = fields.count { it.error != null } diff --git a/form/src/main/java/org/dhis2/form/ui/Form.kt b/form/src/main/java/org/dhis2/form/ui/Form.kt index 81fa048149..96f0459d26 100644 --- a/form/src/main/java/org/dhis2/form/ui/Form.kt +++ b/form/src/main/java/org/dhis2/form/ui/Form.kt @@ -6,13 +6,19 @@ import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -21,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.graphics.Color @@ -29,13 +36,19 @@ import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.dhis2.commons.resources.ResourceManager +import org.dhis2.form.R import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.FormSection import org.dhis2.form.ui.event.RecyclerViewUiEvents import org.dhis2.form.ui.intent.FormIntent import org.dhis2.form.ui.provider.inputfield.FieldProvider +import org.hisp.dhis.mobile.ui.designsystem.component.InfoBar +import org.hisp.dhis.mobile.ui.designsystem.component.InfoBarData import org.hisp.dhis.mobile.ui.designsystem.component.Section import org.hisp.dhis.mobile.ui.designsystem.component.SectionState +import org.hisp.dhis.mobile.ui.designsystem.theme.Radius +import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing +import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor @OptIn(ExperimentalFoundationApi::class) @Composable @@ -100,49 +113,92 @@ fun Form( Section( title = section.title, isLastSection = getNextSection(section, sections) == null, - description = section.description, + description = if (section.fields.isNotEmpty()) section.description else null, completedFields = section.completedFields(), totalFields = section.fields.size, state = section.state, errorCount = section.errorCount(), warningCount = section.warningCount(), + warningMessage = section.warningMessage?.let { resources.getString(it) }, onNextSection = onNextSection, onSectionClick = { intentHandler.invoke(FormIntent.OnSection(section.uid)) }, content = { - section.fields.forEachIndexed { index, fieldUiModel -> - fieldUiModel.setCallback(callback) - FieldProvider( - modifier = Modifier.animateItemPlacement( - animationSpec = tween( - durationMillis = 500, - easing = LinearOutSlowInEasing, + if (section.fields.isNotEmpty()) { + section.fields.forEachIndexed { index, fieldUiModel -> + fieldUiModel.setCallback(callback) + FieldProvider( + modifier = Modifier.animateItemPlacement( + animationSpec = tween( + durationMillis = 500, + easing = LinearOutSlowInEasing, + ), ), - ), - fieldUiModel = fieldUiModel, - uiEventHandler = uiEventHandler, - intentHandler = intentHandler, - resources = resources, - focusManager = focusManager, - onNextClicked = { - if (index == section.fields.size - 1) { - onNextSection() - focusNext.value = true - } else { - focusManager.moveFocus(FocusDirection.Down) - } - }, - ) + fieldUiModel = fieldUiModel, + uiEventHandler = uiEventHandler, + intentHandler = intentHandler, + resources = resources, + focusManager = focusManager, + onNextClicked = { + if (index == section.fields.size - 1) { + onNextSection() + focusNext.value = true + } else { + focusManager.moveFocus(FocusDirection.Down) + } + }, + ) + } } }, ) } item(sections.size - 1) { - Spacer(modifier = Modifier.height(120.dp)) + Spacer(modifier = Modifier.height(Spacing.Spacing120)) } } } + if (shouldDisplayNoFieldsWarning(sections)) { + NoFieldsWarning(resources) + } +} + +fun shouldDisplayNoFieldsWarning(sections: List): Boolean { + return if (sections.size == 1) { + val section = sections.first() + section.state == SectionState.NO_HEADER && section.fields.isEmpty() + } else { + false + } +} + +@Composable +fun NoFieldsWarning(resources: ResourceManager) { + Column( + modifier = Modifier + .padding(Spacing.Spacing16), + ) { + InfoBar( + infoBarData = InfoBarData( + text = resources.getString(R.string.form_without_fields), + icon = { + Icon( + imageVector = Icons.Outlined.ErrorOutline, + contentDescription = "no fields", + tint = SurfaceColor.Warning, + ) + }, + color = SurfaceColor.Warning, + backgroundColor = SurfaceColor.WarningContainer, + actionText = null, + onClick = null, + ), + modifier = Modifier + .clip(shape = RoundedCornerShape(Radius.Full)) + .background(SurfaceColor.WarningContainer), + ) + } } private fun FocusManager.moveFocusNext(focusNext: MutableState) { diff --git a/form/src/main/java/org/dhis2/form/ui/mapper/FormSectionMapper.kt b/form/src/main/java/org/dhis2/form/ui/mapper/FormSectionMapper.kt index 06f12753f1..118e47ae2a 100644 --- a/form/src/main/java/org/dhis2/form/ui/mapper/FormSectionMapper.kt +++ b/form/src/main/java/org/dhis2/form/ui/mapper/FormSectionMapper.kt @@ -1,5 +1,6 @@ package org.dhis2.form.ui.mapper +import org.dhis2.form.R import org.dhis2.form.model.FieldUiModel import org.dhis2.form.model.FieldUiModelImpl import org.dhis2.form.model.FormSection @@ -13,6 +14,8 @@ class FormSectionMapper { if (hasSections(items)) { items.forEach { item -> if (item is SectionUiModelImpl) { + val fields = items.filterIsInstance() + .filter { it.programStageSection == item.uid } sections.add( FormSection( uid = item.uid, @@ -23,8 +26,12 @@ class FormSectionMapper { false -> SectionState.CLOSE null -> SectionState.FIXED }, - fields = items.filterIsInstance() - .filter { it.programStageSection == item.uid }, + fields = fields, + warningMessage = if (fields.isEmpty()) { + R.string.form_without_fields + } else { + null + }, ), ) } @@ -40,7 +47,6 @@ class FormSectionMapper { ), ) } - return sections } diff --git a/form/src/main/res/values/strings.xml b/form/src/main/res/values/strings.xml index 2ecc71fb38..99f959679e 100644 --- a/form/src/main/res/values/strings.xml +++ b/form/src/main/res/values/strings.xml @@ -103,4 +103,5 @@ QR code Bar code Add location + This form has no fields configured \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index baa59ad61f..69f79b77c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ kotlin = '1.9.21' hilt = '2.47' hiltCompiler = '1.0.0' jacoco = '0.8.10' -designSystem = "0.2-20240208.105715-18" +designSystem = "0.2-20240213.152311-21" dhis2sdk = "1.10.0-20240207.110936-11" ruleEngine = "2.1.9" appcompat = "1.6.1" @@ -102,7 +102,7 @@ hiltPlugin = { group = "com.google.dagger", name = "hilt-android-gradle-plugin", jacoco = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacoco" } dhis2-android-sdk = { group = "org.hisp.dhis", name = "android-core", version.ref = "dhis2sdk" } dhis2-ruleengine = { group = "org.hisp.dhis.rules", name = "rule-engine", version.ref = "ruleEngine" } -dhis2-mobile-designsystem = { group = "org.hisp.dhis.mobile", name = "designsystem", version.ref = "designSystem" } +dhis2-mobile-designsystem = { group = "org.hisp.dhis.mobile", name = "designsystem-android", version.ref = "designSystem" } desugar = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugar_jdk_libs" } androidx-activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityCompose" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }