Skip to content

Commit

Permalink
feat: [ANDROAPP-6426] quick actions on dashboard
Browse files Browse the repository at this point in the history
Signed-off-by: Manu Muñoz <manu.munoz@dhis2.org>
  • Loading branch information
mmmateos committed Jan 22, 2025
1 parent 64d5a7d commit 8984f0c
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import dhis2.org.analytics.charts.Charts
import io.reactivex.Observable
import org.dhis2.R
import org.dhis2.android.rtsm.utils.NetworkUtils
import org.dhis2.commons.data.ProgramConfigurationRepository
import org.dhis2.commons.featureconfig.data.FeatureConfigRepository
import org.dhis2.commons.filters.FilterManager
import org.dhis2.commons.prefs.PreferenceProvider
import org.dhis2.commons.resources.MetadataIconProvider
Expand Down Expand Up @@ -50,6 +52,8 @@ class TeiDashboardMobileActivityTest {
private val charts: Charts = mock()
private val teiAttributesProvider: TeiAttributesProvider = mock()
private val preferences: PreferenceProvider = mock()
private val programConfigurationRepository: ProgramConfigurationRepository = mock()
private val featureConfigRepository: FeatureConfigRepository = mock()

private val metadataIconProvider: MetadataIconProvider = mock {
on { invoke(any()) }doReturn MetadataIconData.defaultIcon()
Expand Down Expand Up @@ -120,10 +124,10 @@ class TeiDashboardMobileActivityTest {
ENROLLMENT_UID,
teiAttributesProvider,
preferences,
metadataIconProvider
metadataIconProvider,
programConfigurationRepository,
featureConfigRepository,
)


}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ data class DashboardEnrollmentModel(
override val teiHeader: String?,
override val avatarPath: String?,
override val ownerOrgUnit: OrganisationUnit?,
val quickActions: List<String>,
) : DashboardModel(
trackedEntityInstance,
trackedEntityAttributeValues,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.functions.Function
import org.dhis2.bindings.profilePicturePath
import org.dhis2.commons.data.ProgramConfigurationRepository
import org.dhis2.commons.data.tuples.Pair
import org.dhis2.commons.featureconfig.data.FeatureConfigRepository
import org.dhis2.commons.featureconfig.model.Feature
import org.dhis2.commons.prefs.Preference
import org.dhis2.commons.prefs.PreferenceProvider
import org.dhis2.commons.resources.MetadataIconProvider
Expand Down Expand Up @@ -47,6 +50,8 @@ class DashboardRepositoryImpl(
private val teiAttributesProvider: TeiAttributesProvider,
private val preferenceProvider: PreferenceProvider,
private val metadataIconProvider: MetadataIconProvider,
private val programConfigurationRepository: ProgramConfigurationRepository,
private val featureConfigRepository: FeatureConfigRepository,
) : DashboardRepository {
override fun getTeiHeader(): String? {
return d2.trackedEntityModule().trackedEntitySearch()
Expand Down Expand Up @@ -413,10 +418,30 @@ class DashboardRepositoryImpl(
getTeiHeader(),
getTeiProfilePath(),
getOwnerOrgUnit(teiUid),
getQuickActions(programUid),
)
}
}

private fun getQuickActions(programUid: String): List<String> {
return if (featureConfigRepository.isFeatureEnable(Feature.QUICK_ACTIONS)) {
listOf(
"MARK_FOLLOW_UP",
"TRANSFER",
"COMPLETE_ENROLLMENT",
"CANCEL_ENROLLMENT",
"MORE_ENROLLMENTS",
)
} else {
/*programConfigurationRepository
.getConfigurationByProgram(programUid)
?.quickActions()
?.map { it.actionId() }
?:*/
emptyList()
}
}

override fun getTeiActivePrograms(
teiUid: String,
showOnlyActive: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ class DashboardViewModel(
).blockingFirst()

if (result == StatusChangeResultCode.CHANGED) {
_showStatusBar.value = status
_syncNeeded.value = true
_state.value = State.TO_UPDATE
fetchDashboardModel()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package org.dhis2.usescases.teiDashboard
import dagger.Module
import dagger.Provides
import dhis2.org.analytics.charts.Charts
import org.dhis2.commons.data.ProgramConfigurationRepository
import org.dhis2.commons.di.dagger.PerActivity
import org.dhis2.commons.featureconfig.data.FeatureConfigRepository
import org.dhis2.commons.matomo.MatomoAnalyticsController
import org.dhis2.commons.prefs.PreferenceProvider
import org.dhis2.commons.resources.EventResourcesProvider
Expand Down Expand Up @@ -89,6 +91,8 @@ class TeiDashboardModule(
preferenceProvider: PreferenceProvider,
teiAttributesProvider: TeiAttributesProvider,
metadataIconProvider: MetadataIconProvider,
programConfigurationRepository: ProgramConfigurationRepository,
featureConfigRepository: FeatureConfigRepository,
): DashboardRepository {
return DashboardRepositoryImpl(
d2,
Expand All @@ -99,6 +103,8 @@ class TeiDashboardModule(
teiAttributesProvider,
preferenceProvider,
metadataIconProvider,
programConfigurationRepository,
featureConfigRepository,
)
}

Expand Down Expand Up @@ -145,4 +151,11 @@ class TeiDashboardModule(
resourcesManager,
)
}

@Provides
fun provideProgramConfigurationRepository(
d2: D2,
): ProgramConfigurationRepository {
return ProgramConfigurationRepository(d2)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ import org.dhis2.usescases.teiDashboard.dialogs.scheduling.SchedulingDialog.Comp
import org.dhis2.usescases.teiDashboard.dialogs.scheduling.SchedulingDialog.Companion.SCHEDULING_EVENT_SKIPPED
import org.dhis2.usescases.teiDashboard.ui.TeiDetailDashboard
import org.dhis2.usescases.teiDashboard.ui.mapper.InfoBarMapper
import org.dhis2.usescases.teiDashboard.ui.mapper.QuickActionsMapper
import org.dhis2.usescases.teiDashboard.ui.mapper.TeiDashboardCardMapper
import org.dhis2.usescases.teiDashboard.ui.model.InfoBarType
import org.dhis2.usescases.teiDashboard.ui.model.QuickActionType
import org.dhis2.usescases.teiDashboard.ui.model.TimelineEventsHeaderModel
import org.dhis2.utils.extension.setIcon
import org.dhis2.utils.granularsync.SyncStatusDialog
Expand All @@ -91,6 +93,9 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View {
@Inject
lateinit var infoBarMapper: InfoBarMapper

@Inject
lateinit var quickActionsMapper: QuickActionsMapper

@Inject
lateinit var contractHandler: TeiDataContractHandler

Expand Down Expand Up @@ -303,10 +308,35 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View {
timelineOnEventCreationOptionSelected = {
presenter.onAddNewEventOptionSelected(it, null)
},
quickActions = (dashboardModel as? DashboardEnrollmentModel)?.let {
quickActionsMapper.map(it) { quickActionType ->
onQuickAction(quickActionType)
}
} ?: emptyList(),
)
}
}
}
private fun onQuickAction(quickActionType: QuickActionType) {
val teiDashboardActivity = activity as TeiDashboardMobileActivity
when (quickActionType) {
QuickActionType.MARK_FOLLOW_UP -> dashboardViewModel.onFollowUp()
QuickActionType.TRANSFER -> programUid?.let {
teiDashboardActivity.showOrgUnitSelector(it)
}
QuickActionType.COMPLETE_ENROLLMENT -> dashboardViewModel.updateEnrollmentStatus(
EnrollmentStatus.COMPLETED,
)
QuickActionType.CANCEL_ENROLLMENT -> dashboardViewModel.updateEnrollmentStatus(
EnrollmentStatus.CANCELLED,
)
QuickActionType.REOPEN_ENROLLMENT -> dashboardViewModel.updateEnrollmentStatus(
EnrollmentStatus.ACTIVE,
)
QuickActionType.MORE_ENROLLMENTS ->
teiDashboardActivity.goToEnrollmentList()
}
}

private fun showLegacyCard(dashboardModel: DashboardTEIModel?) {
binding.noEnrollmentSelected = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.dhis2.usescases.teiDashboard.DashboardRepository
import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.teievents.ui.mapper.TEIEventCardMapper
import org.dhis2.usescases.teiDashboard.domain.GetNewEventCreationTypeOptions
import org.dhis2.usescases.teiDashboard.ui.mapper.InfoBarMapper
import org.dhis2.usescases.teiDashboard.ui.mapper.QuickActionsMapper
import org.dhis2.usescases.teiDashboard.ui.mapper.TeiDashboardCardMapper
import org.dhis2.utils.analytics.AnalyticsHelper
import org.hisp.dhis.android.core.D2
Expand Down Expand Up @@ -132,13 +133,6 @@ class TEIDataModule(
return GetNewEventCreationTypeOptions(programConfigurationRepository)
}

@Provides
fun provideProgramConfigurationRepository(
d2: D2,
): ProgramConfigurationRepository {
return ProgramConfigurationRepository(d2)
}

@Provides
fun provideEventCreationsOptionsMapper(
resourceManager: ResourceManager,
Expand All @@ -160,6 +154,13 @@ class TEIDataModule(
return InfoBarMapper(resourceManager)
}

@Provides
fun provideQuickActionMapper(
resourceManager: ResourceManager,
): QuickActionsMapper {
return QuickActionsMapper(programUid, resourceManager)
}

@Provides
fun provideContractHandler() = TeiDataContractHandler(registry)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package org.dhis2.usescases.teiDashboard.ui

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import org.dhis2.commons.data.EventCreationType
import org.dhis2.usescases.teiDashboard.ui.model.InfoBarUiModel
import org.dhis2.usescases.teiDashboard.ui.model.QuickActionUiModel
import org.dhis2.usescases.teiDashboard.ui.model.TeiCardUiModel
import org.dhis2.usescases.teiDashboard.ui.model.TimelineEventsHeaderModel
import org.hisp.dhis.mobile.ui.designsystem.component.AssistChip
import org.hisp.dhis.mobile.ui.designsystem.component.CardDetail
import org.hisp.dhis.mobile.ui.designsystem.component.InfoBar
import org.hisp.dhis.mobile.ui.designsystem.component.InfoBarData
Expand All @@ -27,6 +33,7 @@ fun TeiDetailDashboard(
timelineEventHeaderModel: TimelineEventsHeaderModel,
isGrouped: Boolean = true,
timelineOnEventCreationOptionSelected: (EventCreationType) -> Unit,
quickActions: List<QuickActionUiModel>,
) {
Column(
modifier = Modifier
Expand Down Expand Up @@ -99,6 +106,21 @@ fun TeiDetailDashboard(
)
}

if (quickActions.isNotEmpty()) {
LazyRow(
horizontalArrangement = Arrangement.spacedBy(Spacing.Spacing8),
contentPadding = PaddingValues(horizontal = Spacing.Spacing16),
) {
items(quickActions) {
AssistChip(
label = it.label,
icon = it.icon,
onClick = it.onActionClick,
)
}
}
}

if (!isGrouped) {
Spacer(modifier = Modifier.size(Spacing.Spacing16))
TimelineEventsHeader(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.dhis2.usescases.teiDashboard.ui.mapper

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Assignment
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CheckCircle
import androidx.compose.material.icons.outlined.Flag
import androidx.compose.material.icons.outlined.LockReset
import androidx.compose.material.icons.outlined.MoveDown
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import org.dhis2.R
import org.dhis2.commons.resources.ResourceManager
import org.dhis2.usescases.teiDashboard.DashboardEnrollmentModel
import org.dhis2.usescases.teiDashboard.ui.model.QuickActionType
import org.dhis2.usescases.teiDashboard.ui.model.QuickActionUiModel
import org.hisp.dhis.android.core.enrollment.EnrollmentStatus
import org.hisp.dhis.mobile.ui.designsystem.theme.SurfaceColor

class QuickActionsMapper(
val programUid: String?,
val resourceManager: ResourceManager,
) {
fun map(
dashboardEnrollmentModel: DashboardEnrollmentModel,
onActionClick: (QuickActionType) -> Unit,
): List<QuickActionUiModel> {
return dashboardEnrollmentModel.quickActions
.mapNotNull { QuickActionType.valueOf(it).withEnrollmentStatus(dashboardEnrollmentModel) }
.map { quickActionType ->
QuickActionUiModel(
label = getText(quickActionType, programUid),
icon = getIcon(quickActionType),
onActionClick = { onActionClick(quickActionType) },
)
}
}

private fun QuickActionType.withEnrollmentStatus(
dashboardEnrollmentModel: DashboardEnrollmentModel,
): QuickActionType? {
return when (this) {
QuickActionType.MARK_FOLLOW_UP ->
if (dashboardEnrollmentModel.currentEnrollment.followUp() == true) {
null
} else {
this
}
QuickActionType.COMPLETE_ENROLLMENT ->
if (dashboardEnrollmentModel.currentEnrollment.status() == EnrollmentStatus.COMPLETED) {
QuickActionType.REOPEN_ENROLLMENT
} else {
this
}
QuickActionType.CANCEL_ENROLLMENT ->
if (dashboardEnrollmentModel.currentEnrollment.status() == EnrollmentStatus.CANCELLED) {
QuickActionType.REOPEN_ENROLLMENT
} else {
this
}
else -> this
}
}

private fun getText(
quickActionType: QuickActionType,
programUid: String?,
): String {
return when (quickActionType) {
QuickActionType.MARK_FOLLOW_UP -> resourceManager.getString(R.string.mark_follow_up)
QuickActionType.TRANSFER -> resourceManager.getString(R.string.transfer)
QuickActionType.COMPLETE_ENROLLMENT -> resourceManager.formatWithEnrollmentLabel(
programUid,
R.string.complete_enrollment_label,
1,
)
QuickActionType.CANCEL_ENROLLMENT ->
resourceManager.formatWithEnrollmentLabel(
programUid,
R.string.deactivate_enrollment_label,
1,
)
QuickActionType.REOPEN_ENROLLMENT -> resourceManager.formatWithEnrollmentLabel(
programUid,
R.string.reopen_enrollment_label,
1,
)
QuickActionType.MORE_ENROLLMENTS -> resourceManager.getString(R.string.more_enrollments)
}
}

private fun getIcon(quickActionType: QuickActionType) = @Composable {
val iconResource = when (quickActionType) {
QuickActionType.MARK_FOLLOW_UP -> Icons.Outlined.Flag
QuickActionType.TRANSFER -> Icons.Outlined.MoveDown
QuickActionType.COMPLETE_ENROLLMENT -> Icons.Outlined.CheckCircle
QuickActionType.CANCEL_ENROLLMENT -> Icons.Outlined.Block
QuickActionType.REOPEN_ENROLLMENT -> Icons.Outlined.LockReset
QuickActionType.MORE_ENROLLMENTS -> Icons.AutoMirrored.Outlined.Assignment
}
Icon(
imageVector = iconResource,
contentDescription = null,
tint = if (quickActionType == QuickActionType.REOPEN_ENROLLMENT) {
SurfaceColor.Warning
} else {
MaterialTheme.colorScheme.onSurface
},
)
}
}
Loading

0 comments on commit 8984f0c

Please sign in to comment.