diff --git a/app/src/androidTest/java/org/dhis2/common/filters/FiltersRobot.kt b/app/src/androidTest/java/org/dhis2/common/filters/FiltersRobot.kt index de1e6693b7..15f89715a2 100644 --- a/app/src/androidTest/java/org/dhis2/common/filters/FiltersRobot.kt +++ b/app/src/androidTest/java/org/dhis2/common/filters/FiltersRobot.kt @@ -1,24 +1,16 @@ package org.dhis2.common.filters -import androidx.compose.ui.test.junit4.ComposeTestRule -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollTo import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.TypeTextAction import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.contrib.PickerActions import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.withId import org.dhis2.R import org.dhis2.common.BaseRobot import org.dhis2.common.matchers.DatePickerMatchers.Companion.matchesDate import org.dhis2.commons.filters.FilterHolder -import org.dhis2.ui.dialogs.orgunit.DONE_TEST_TAG -import org.dhis2.ui.dialogs.orgunit.ITEM_CHECK_TEST_TAG -import org.dhis2.ui.dialogs.orgunit.ITEM_TEST_TAG fun filterRobotCommon(robotBody: FiltersRobot.() -> Unit) { FiltersRobot().apply { @@ -56,7 +48,7 @@ class FiltersRobot : BaseRobot() { } fun selectNotSyncedState() { - onView( withId(R.id.stateNotSynced)).perform(click()) + onView(withId(R.id.stateNotSynced)).perform(click()) } fun acceptDateSelected() { @@ -66,6 +58,4 @@ class FiltersRobot : BaseRobot() { fun checkDate(year: Int, monthOfYear: Int, dayOfMonth: Int) { onView(withId(R.id.datePicker)).check(matches(matchesDate(year, monthOfYear, dayOfMonth))) } - - } \ No newline at end of file diff --git a/app/src/androidTest/java/org/dhis2/usescases/orgunitselector/OrgUnitSelectorRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/orgunitselector/OrgUnitSelectorRobot.kt index 0bf70d5177..5fb7fc6452 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/orgunitselector/OrgUnitSelectorRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/orgunitselector/OrgUnitSelectorRobot.kt @@ -2,11 +2,10 @@ package org.dhis2.usescases.orgunitselector import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo import org.dhis2.common.BaseRobot -import org.dhis2.ui.dialogs.orgunit.DONE_TEST_TAG -import org.dhis2.ui.dialogs.orgunit.ITEM_CHECK_TEST_TAG fun orgUnitSelectorRobot( composeTestRule: ComposeTestRule, @@ -19,9 +18,9 @@ fun orgUnitSelectorRobot( class OrgUnitSelectorRobot(private val composeTestRule: ComposeTestRule) : BaseRobot() { fun selectTreeOrgUnit(orgUnitName: String) { - composeTestRule.onNodeWithTag("$ITEM_CHECK_TEST_TAG$orgUnitName") + composeTestRule.onNodeWithTag("ORG_TREE_ITEM_$orgUnitName") .performScrollTo() .performClick() - composeTestRule.onNodeWithTag(DONE_TEST_TAG).performClick() + composeTestRule.onNodeWithText("Done").performClick() } } \ No newline at end of file diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/dialogs/scheduling/SchedulingDialogUiTest.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/dialogs/scheduling/SchedulingDialogUiTest.kt index 4aa70eb56a..a276bdac1e 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/dialogs/scheduling/SchedulingDialogUiTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/dialogs/scheduling/SchedulingDialogUiTest.kt @@ -1,6 +1,10 @@ package org.dhis2.usescases.teidashboard.dialogs.scheduling +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.hasTestTag import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onAllNodesWithTag +import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick @@ -110,6 +114,7 @@ class SchedulingDialogUiTest { composeTestRule.onNodeWithText("Done").assertExists() } + @OptIn(ExperimentalTestApi::class) @Test fun selectProgramStage() { val programStages = listOf( @@ -126,7 +131,8 @@ class SchedulingDialogUiTest { } } - composeTestRule.onNodeWithText("Program stage").performClick() + composeTestRule.onAllNodesWithTag("INPUT_DROPDOWN").onFirst().performClick() + composeTestRule.waitUntilExactlyOneExists(hasTestTag("INPUT_DROPDOWN_MENU_ITEM_1")) composeTestRule.onNodeWithTag("INPUT_DROPDOWN_MENU_ITEM_1").performClick() verify(viewModel).updateStage(programStages[1]) diff --git a/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetSection/DataSetSectionFragment.kt b/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetSection/DataSetSectionFragment.kt index 75d74aa135..97545f1293 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetSection/DataSetSectionFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/datasets/dataSetTable/dataSetSection/DataSetSectionFragment.kt @@ -463,7 +463,6 @@ class DataSetSectionFragment : FragmentGlobalAbstract(), DataValueContract.View updateCellValue: (TableCell) -> Unit, ) { OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .withPreselectedOrgUnits(cell.value?.let { listOf(it) } ?: emptyList()) .onSelection { selectedOrgUnits -> diff --git a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/DataSetDetailActivity.java b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/DataSetDetailActivity.java index e0529c293a..abc9f5523b 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/DataSetDetailActivity.java +++ b/app/src/main/java/org/dhis2/usescases/datasets/datasetDetail/DataSetDetailActivity.java @@ -180,7 +180,6 @@ public void updateFilters(int totalFilters) { @Override public void openOrgUnitTreeSelector() { new OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits(FilterManager.getInstance().getOrgUnitUidsFilters()) .onSelection(selectedOrgUnits -> { presenter.setOrgUnitFilters((List) selectedOrgUnits); diff --git a/app/src/main/java/org/dhis2/usescases/datasets/datasetInitial/DataSetInitialActivity.java b/app/src/main/java/org/dhis2/usescases/datasets/datasetInitial/DataSetInitialActivity.java index a18a4602d6..3244e2463f 100644 --- a/app/src/main/java/org/dhis2/usescases/datasets/datasetInitial/DataSetInitialActivity.java +++ b/app/src/main/java/org/dhis2/usescases/datasets/datasetInitial/DataSetInitialActivity.java @@ -111,7 +111,6 @@ public void showOrgUnitDialog(List data) { preselectedOrgUnits.add(selectedOrgUnit.uid()); } new OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .withPreselectedOrgUnits(preselectedOrgUnits) .orgUnitScope(new OrgUnitSelectorScope.DataSetCaptureScope(dataSetUid)) diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt index 036a3fe9f7..32c814f38a 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventDetails/ui/EventDetailsFragment.kt @@ -338,7 +338,6 @@ class EventDetailsFragment : FragmentGlobalAbstract() { private fun showOrgUnitDialog() { OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits( viewModel.eventOrgUnit.value.selectedOrgUnit ?.let { listOf(it.uid()) } diff --git a/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt b/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt index 4e0d1653d3..8d98cb736f 100644 --- a/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/main/program/ProgramFragment.kt @@ -134,7 +134,6 @@ class ProgramFragment : FragmentGlobalAbstract(), ProgramView { override fun openOrgUnitTreeSelector() { OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits( FilterManager.getInstance().orgUnitFilters.map { it.uid() }.toMutableList(), ) diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt index fed6c83568..8b3355ab3e 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailActivity.kt @@ -320,7 +320,6 @@ class ProgramEventDetailActivity : override fun selectOrgUnitForNewEvent() { enableAddEventButton(false) OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .orgUnitScope( OrgUnitSelectorScope.ProgramCaptureScope(programUid), @@ -373,7 +372,6 @@ class ProgramEventDetailActivity : override fun openOrgUnitTreeSelector() { OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits(FilterManager.getInstance().orgUnitUidsFilters) .onSelection { selectedOrgUnits -> presenter.setOrgUnitFilters(selectedOrgUnits) diff --git a/app/src/main/java/org/dhis2/usescases/programStageSelection/ProgramStageSelectionActivity.kt b/app/src/main/java/org/dhis2/usescases/programStageSelection/ProgramStageSelectionActivity.kt index b791ad64c7..b8f5911524 100644 --- a/app/src/main/java/org/dhis2/usescases/programStageSelection/ProgramStageSelectionActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/programStageSelection/ProgramStageSelectionActivity.kt @@ -97,7 +97,6 @@ class ProgramStageSelectionActivity : ActivityGlobalAbstract(), ProgramStageSele enrollmentUid: String?, ) { OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .orgUnitScope( OrgUnitSelectorScope.ProgramCaptureScope(programUid), diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEActivity.java b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEActivity.java index 579cd99d6a..dafa6802b6 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEActivity.java +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEActivity.java @@ -350,7 +350,6 @@ private void initSearchParameters() { resourceManager, (uid, preselectedOrgUnits, orgUnitScope, label) -> { new OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits(preselectedOrgUnits) .singleSelection() .onSelection(selectedOrgUnits -> { @@ -649,7 +648,6 @@ public void clearFilters() { @Override public void openOrgUnitTreeSelector() { new OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits( FilterManager.getInstance().getOrgUnitUidsFilters() ) diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEPresenter.java b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEPresenter.java index 0bdece83d1..195df328f9 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEPresenter.java +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchTEPresenter.java @@ -297,7 +297,6 @@ public void enroll(String programUid, String uid, HashMap queryD allOrgUnits -> { if (allOrgUnits.size() > 1) { new OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .onSelection(selectedOrgUnits -> { if (!selectedOrgUnits.isEmpty()) diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/indicators/IndicatorsFragment.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/indicators/IndicatorsFragment.kt index a80f613c06..f21235dad5 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/indicators/IndicatorsFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/indicators/IndicatorsFragment.kt @@ -141,7 +141,6 @@ class IndicatorsFragment : FragmentGlobalAbstract(), IndicatorsView { lineListingColumnId: Int?, ) { OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits( chartModel.graph.orgUnitsSelected(lineListingColumnId).toMutableList(), ) diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt index b817c42e0d..cee9fd08fb 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataFragment.kt @@ -520,7 +520,6 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { override fun displayOrgUnitSelectorForNewEvent(programUid: String, programStageUid: String) { OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .orgUnitScope( OrgUnitSelectorScope.ProgramCaptureScope(programUid), diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListInteractor.java b/app/src/main/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListInteractor.java index 41400efdc6..83b3d3e001 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListInteractor.java +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/teiProgramList/TeiProgramListInteractor.java @@ -142,7 +142,6 @@ private void handleCalendarResult( public void enroll(String programUid, String uid) { selectedEnrollmentDate = Calendar.getInstance().getTime(); OUTreeFragment orgUnitDialog = new OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .onSelection(selectedOrgUnits -> { if (!selectedOrgUnits.isEmpty()) diff --git a/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeFragment.kt b/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeFragment.kt index 63bf190731..8f041110db 100644 --- a/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeFragment.kt +++ b/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeFragment.kt @@ -1,12 +1,8 @@ package org.dhis2.commons.orgunitselector -import android.app.Activity -import android.app.Dialog import android.content.Context -import android.graphics.Point import android.os.Build import android.os.Bundle -import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -14,30 +10,25 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.fragment.app.DialogFragment +import androidx.compose.ui.res.stringResource import androidx.fragment.app.viewModels -import com.google.accompanist.themeadapter.material3.Mdc3Theme -import org.dhis2.ui.dialogs.orgunit.OrgUnitSelectorActions -import org.dhis2.ui.dialogs.orgunit.OrgUnitSelectorDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import org.dhis2.commons.R import org.hisp.dhis.android.core.organisationunit.OrganisationUnit +import org.hisp.dhis.mobile.ui.designsystem.component.OrgBottomSheet import javax.inject.Inject -const val ARG_SHOW_AS_DIALOG = "OUTreeFragment.ARG_SHOW_AS_DIALOG" const val ARG_SINGLE_SELECTION = "OUTreeFragment.ARG_SINGLE_SELECTION" const val ARG_SCOPE = "OUTreeFragment.ARG_SCOPE" const val ARG_PRE_SELECTED_OU = "OUTreeFragment.ARG_PRE_SELECTED_OU" -class OUTreeFragment private constructor() : DialogFragment() { +class OUTreeFragment private constructor() : BottomSheetDialogFragment() { class Builder { - private var showAsDialog = false private var preselectedOrgUnits = listOf() private var singleSelection = false private var selectionListener: ((selectedOrgUnits: List) -> Unit) = {} private var orgUnitScope: OrgUnitSelectorScope = OrgUnitSelectorScope.UserSearchScope() - fun showAsDialog() = apply { - showAsDialog = true - } fun withPreselectedOrgUnits(preselectedOrgUnits: List) = apply { if (singleSelection && preselectedOrgUnits.size > 1) { @@ -70,7 +61,6 @@ class OUTreeFragment private constructor() : DialogFragment() { return OUTreeFragment().apply { selectionCallback = selectionListener arguments = Bundle().apply { - putBoolean(ARG_SHOW_AS_DIALOG, showAsDialog) putBoolean(ARG_SINGLE_SELECTION, singleSelection) putParcelable(ARG_SCOPE, orgUnitScope) putStringArrayList(ARG_PRE_SELECTED_OU, ArrayList(preselectedOrgUnits)) @@ -108,17 +98,9 @@ class OUTreeFragment private constructor() : DialogFragment() { )?.inject(this) } - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val dialog = super.onCreateDialog(savedInstanceState) - dialog.window!!.setBackgroundDrawableResource(android.R.color.transparent) - return dialog - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - showAsDialog().let { showAsDialog -> - showsDialog = showAsDialog - } + setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme) } override fun onCreateView( @@ -129,41 +111,21 @@ class OUTreeFragment private constructor() : DialogFragment() { return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { - Mdc3Theme { - val list by presenter.treeNodes.collectAsState() - OrgUnitSelectorDialog( - title = null, - items = list, - actions = object : OrgUnitSelectorActions { - override val onSearch: (String) -> Unit - get() = presenter::searchByName - override val onOrgUnitChecked: - (orgUnitUid: String, isChecked: Boolean) -> Unit - get() = presenter::onOrgUnitCheckChanged - override val onOpenOrgUnit: (orgUnitUid: String) -> Unit - get() = presenter::onOpenChildren - override val onDoneClick: () -> Unit - get() = this@OUTreeFragment::confirmOuSelection - override val onCancelClick: () -> Unit - get() = this@OUTreeFragment::cancelOuSelection - override val onClearClick: () -> Unit - get() = presenter::clearAll - }, - ) - } + val list by presenter.treeNodes.collectAsState() + OrgBottomSheet( + clearAllButtonText = stringResource(id = R.string.action_clear_all), + orgTreeItems = list, + onSearch = presenter::searchByName, + onDismiss = { cancelOuSelection() }, + onItemClick = presenter::onOpenChildren, + onItemSelected = presenter::onOrgUnitCheckChanged, + onClearAll = presenter::clearAll, + onDone = { confirmOuSelection() }, + ) } } } - override fun onResume() { - super.onResume() - showAsDialog().takeIf { it }?.let { - fixDialogSize(0.9, 0.9) - } - } - - private fun showAsDialog() = arguments?.getBoolean(ARG_SHOW_AS_DIALOG, false) ?: false - private fun confirmOuSelection() { selectionCallback(presenter.getOrgUnits()) exitOuSelection() @@ -175,25 +137,6 @@ class OUTreeFragment private constructor() : DialogFragment() { } private fun exitOuSelection() { - if (showAsDialog()) { - dismiss() - } else { - activity?.apply { - setResult(Activity.RESULT_OK) - finish() - } - } - } -} - -fun DialogFragment.fixDialogSize(widthPercent: Double, heightPercent: Double) { - val size = Point() - dialog?.window?.apply { - windowManager.defaultDisplay.getSize(size) - - setLayout(widthPercent of size.x, heightPercent of size.y) - setGravity(Gravity.CENTER) + dismiss() } } - -private infix fun Double.of(value: Int) = (this * value).toInt() diff --git a/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeViewModel.kt b/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeViewModel.kt index fec34f6436..a91b9c60a5 100644 --- a/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeViewModel.kt +++ b/commons/src/main/java/org/dhis2/commons/orgunitselector/OUTreeViewModel.kt @@ -7,8 +7,8 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.dhis2.commons.viewmodel.DispatcherProvider -import org.dhis2.ui.dialogs.orgunit.OrgUnitTreeItem import org.hisp.dhis.android.core.organisationunit.OrganisationUnit +import org.hisp.dhis.mobile.ui.designsystem.component.OrgTreeItem class OUTreeViewModel( private val repository: OUTreeRepository, @@ -16,8 +16,8 @@ class OUTreeViewModel( private val singleSelection: Boolean, private val dispatchers: DispatcherProvider, ) : ViewModel() { - private val _treeNodes = MutableStateFlow(emptyList()) - val treeNodes: StateFlow> = _treeNodes + private val _treeNodes = MutableStateFlow(emptyList()) + val treeNodes: StateFlow> = _treeNodes init { fetchInitialOrgUnits() @@ -26,12 +26,12 @@ class OUTreeViewModel( private fun fetchInitialOrgUnits(name: String? = null) { viewModelScope.launch(dispatchers.io()) { val orgUnits = repository.orgUnits(name) - val treeNodes = ArrayList() + val treeNodes = ArrayList() orgUnits.forEach { org -> val canBeSelected = repository.canBeSelected(org.uid()) treeNodes.add( - OrgUnitTreeItem( + OrgTreeItem( uid = org.uid(), label = org.displayName()!!, isOpen = true, @@ -65,14 +65,14 @@ class OUTreeViewModel( } private fun openChildren( - currentList: List = _treeNodes.value, + currentList: List = _treeNodes.value, parentOrgUnitUid: String, - ): List { + ): List { val parentIndex = currentList.indexOfFirst { it.uid == parentOrgUnitUid } val orgUnits = repository.childrenOrgUnits(parentOrgUnitUid) val treeNodes = orgUnits.map { org -> val hasChildren = repository.orgUnitHasChildren(org.uid()) - OrgUnitTreeItem( + OrgTreeItem( uid = org.uid(), label = org.displayName()!!, isOpen = hasChildren, @@ -130,16 +130,16 @@ class OUTreeViewModel( } private fun rebuildOrgUnitList( - currentList: List, + currentList: List, location: Int, - nodes: List, - ): List { + nodes: List, + ): List { val nodesCopy = ArrayList(currentList) nodesCopy[location] = nodesCopy[location].copy(isOpen = !nodesCopy[location].isOpen) if (!nodesCopy[location].isOpen) { val level = nodesCopy[location].level - val deleteList: MutableList = ArrayList() + val deleteList: MutableList = ArrayList() var sameLevel = true for (i in location + 1 until nodesCopy.size) { if (sameLevel) if (nodesCopy[i].level > level) { @@ -148,7 +148,7 @@ class OUTreeViewModel( sameLevel = false } } - nodesCopy.removeAll(deleteList) + nodesCopy.removeAll(deleteList.toSet()) } else { nodesCopy.addAll(location + 1, nodes) } diff --git a/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/GroupAnalyticsFragment.kt b/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/GroupAnalyticsFragment.kt index cff111b727..6b785bbbad 100644 --- a/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/GroupAnalyticsFragment.kt +++ b/dhis_android_analytics/src/main/java/dhis2/org/analytics/charts/ui/GroupAnalyticsFragment.kt @@ -187,7 +187,6 @@ class GroupAnalyticsFragment : Fragment() { private fun showOUTreeSelector(chartModel: ChartModel, lineListingColumnId: Int?) { OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits( chartModel.graph.orgUnitsSelected(lineListingColumnId).toMutableList(), ) diff --git a/form/src/main/java/org/dhis2/form/ui/FormView.kt b/form/src/main/java/org/dhis2/form/ui/FormView.kt index 535ba3e81f..52cc981f96 100644 --- a/form/src/main/java/org/dhis2/form/ui/FormView.kt +++ b/form/src/main/java/org/dhis2/form/ui/FormView.kt @@ -879,7 +879,6 @@ class FormView : Fragment() { private fun showOrgUnitDialog(uiEvent: RecyclerViewUiEvents.OpenOrgUnitDialog) { OUTreeFragment.Builder() - .showAsDialog() .withPreselectedOrgUnits( uiEvent.value?.let { listOf(it) } ?: emptyList(), ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 61345476ae..272fa47ba4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ gradle = "8.2.2" kotlin = '1.9.21' hilt = '2.47' jacoco = '0.8.10' -designSystem = "0.2" +designSystem = "0.3.0-SNAPSHOT" dhis2sdk = "1.10.0" ruleEngine = "3.0.0" expressionParser = "1.1.0" diff --git a/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/home/screens/components/DropdownComponents.kt b/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/home/screens/components/DropdownComponents.kt index 9c8b3225c4..71dce8436f 100644 --- a/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/home/screens/components/DropdownComponents.kt +++ b/stock-usecase/src/main/java/org/dhis2/android/rtsm/ui/home/screens/components/DropdownComponents.kt @@ -466,7 +466,6 @@ fun openOrgUnitTreeSelector( launchDialog: (msg: Int, (result: EditionDialogResult) -> Unit) -> Unit, ) { OUTreeFragment.Builder() - .showAsDialog() .singleSelection() .orgUnitScope(OrgUnitSelectorScope.ProgramCaptureScope(settingsUiState.programUid)) .withPreselectedOrgUnits( diff --git a/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitSelectorActions.kt b/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitSelectorActions.kt deleted file mode 100644 index 07966c8ef8..0000000000 --- a/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitSelectorActions.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.dhis2.ui.dialogs.orgunit - -interface OrgUnitSelectorActions { - val onSearch: (String) -> Unit - val onOrgUnitChecked: (orgUnitUid: String, isChecked: Boolean) -> Unit - val onOpenOrgUnit: (orgUnitUid: String) -> Unit - val onDoneClick: () -> Unit - val onCancelClick: () -> Unit - val onClearClick: () -> Unit -} diff --git a/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitSelectorDialog.kt b/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitSelectorDialog.kt deleted file mode 100644 index ceb84e1163..0000000000 --- a/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitSelectorDialog.kt +++ /dev/null @@ -1,360 +0,0 @@ -@file:OptIn(ExperimentalFoundationApi::class) - -package org.dhis2.ui.dialogs.orgunit - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Search -import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.Divider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Devices -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import org.dhis2.ui.R -import org.dhis2.ui.theme.defaultFontFamily -import org.hisp.dhis.mobile.ui.designsystem.component.Button -import org.hisp.dhis.mobile.ui.designsystem.component.ButtonStyle - -@Composable -fun OrgUnitSelectorDialog( - title: String?, - items: List, - actions: OrgUnitSelectorActions, -) { - Surface( - modifier = Modifier - .wrapContentHeight() - .fillMaxWidth(), - color = Color.White, - shape = RoundedCornerShape(16.dp), - ) { - Column( - modifier = Modifier.fillMaxSize(), - ) { - title?.let { - DialogTitle(title) - } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = spacedBy(8.dp), - ) { - Search( - modifier = Modifier.weight(1f), - onValueChangeListener = actions.onSearch, - ) - Button( - modifier = Modifier.testTag(CLEAR_TEST_TAG), - text = stringResource(id = R.string.action_clear_all), - onClick = actions.onClearClick, - style = ButtonStyle.TEXT, - ) - } - Divider() - if (items.isEmpty()) { - Box( - modifier = Modifier - .weight(1f) - .fillMaxWidth(), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator() - } - } else { - OrgUnitTree( - modifier = Modifier.weight(1f), - items = items, - onOrgUnitChecked = actions.onOrgUnitChecked, - onOpenOrgUnit = actions.onOpenOrgUnit, - ) - } - Divider() - Row( - modifier = Modifier - .align(Alignment.End) - .padding( - vertical = 8.dp, - horizontal = 16.dp, - ), - horizontalArrangement = spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Button( - modifier = Modifier.testTag(CANCEL_TEST_TAG), - text = stringResource(id = R.string.action_cancel), - style = ButtonStyle.TEXT, - onClick = actions.onCancelClick, - ) - Button( - modifier = Modifier.testTag(DONE_TEST_TAG), - text = stringResource(id = R.string.action_done), - onClick = actions.onDoneClick, - style = ButtonStyle.FILLED, - icon = { - Icon( - imageVector = Icons.Default.Check, - contentDescription = "", - ) - }, - ) - } - } - } -} - -@Composable -private fun DialogTitle(title: String) { - Text(text = title) -} - -@Composable -private fun Search( - modifier: Modifier = Modifier, - hint: String = stringResource(id = R.string.hint_search), - onValueChangeListener: (String) -> Unit, -) { - var currentValue by remember { - mutableStateOf(null) - } - Row( - modifier = modifier - .background( - color = Color(0x0A000000), - shape = RoundedCornerShape(16.dp), - ) - .border( - width = 1.dp, - color = Color(0x61000000), - shape = RoundedCornerShape(16.dp), - ) - .padding( - horizontal = 8.dp, - vertical = 4.dp, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = spacedBy(8.dp), - ) { - Icon( - imageVector = Icons.Default.Search, - contentDescription = "", - tint = Color(0x8A333333), - ) - BasicTextField( - modifier = Modifier.testTag(SEARCH_TEST_TAG), - value = currentValue ?: "", - onValueChange = { - currentValue = it - onValueChangeListener(it) - }, - maxLines = 1, - textStyle = TextStyle( - color = Color(0x8A333333), - fontSize = 16.sp, - fontFamily = defaultFontFamily, - lineHeight = 24.sp, - ), - decorationBox = { innerTextField -> - if (currentValue.isNullOrEmpty()) { - Text( - text = hint, - fontSize = 16.sp, - fontWeight = FontWeight.Normal, - color = Color.LightGray, - style = TextStyle( - color = Color(0x8A333333), - fontSize = 16.sp, - fontFamily = defaultFontFamily, - lineHeight = 24.sp, - - ), - ) - } - innerTextField() - }, - ) - } -} - -@Composable -fun OrgUnitTree( - modifier: Modifier = Modifier, - items: List, - onOrgUnitChecked: (orgUnitUid: String, isChecked: Boolean) -> Unit, - onOpenOrgUnit: (orgUnitUid: String) -> Unit, -) { - LazyColumn( - modifier = modifier - .testTag(ITEM_LIST_TEST_TAG) - .fillMaxWidth(), - ) { - items(items = items, key = { it.uid }) { orgUnitItem -> - OrgUnitSelectorItem( - modifier = Modifier.animateItemPlacement(), - higherLevel = items.minBy { it.level }.level, - orgUnitItem = orgUnitItem, - onOpenOrgUnit = onOpenOrgUnit, - onOrgUnitChecked = onOrgUnitChecked, - ) - } - } -} - -@Preview(device = Devices.PIXEL_3A) -@Composable -fun OrgUnitSelectorDialogPreview() { - val items = listOf( - OrgUnitTreeItem( - "uid", - "orgUnit1", - true, - hasChildren = true, - ), - OrgUnitTreeItem( - "uid", - "orgUnit2", - false, - level = 1, - hasChildren = true, - ), - OrgUnitTreeItem( - "uid", - "orgUnit2", - false, - level = 1, - hasChildren = false, - ), - ) - OrgUnitSelectorDialog( - null, - items, - object : OrgUnitSelectorActions { - override val onSearch: (String) -> Unit - get() = { } - override val onOrgUnitChecked: (orgUnitUid: String, isChecked: Boolean) -> Unit - get() = { _, _ -> } - override val onOpenOrgUnit: (orgUnitUid: String) -> Unit - get() = { } - override val onDoneClick: () -> Unit - get() = { } - override val onCancelClick: () -> Unit - get() = { } - override val onClearClick: () -> Unit - get() = { } - }, - ) -} - -@Composable -fun OrgUnitSelectorItem( - modifier: Modifier = Modifier, - higherLevel: Int, - orgUnitItem: OrgUnitTreeItem, - onOpenOrgUnit: (orgUnitUid: String) -> Unit, - onOrgUnitChecked: (orgUnitUid: String, checked: Boolean) -> Unit, -) { - Row( - modifier = modifier - .testTag("$ITEM_TEST_TAG${orgUnitItem.label}") - .fillMaxWidth() - .heightIn(min = 48.dp) - .background(Color.White) - .clickable( - enabled = orgUnitItem.hasChildren, - interactionSource = remember { - MutableInteractionSource() - }, - indication = rememberRipple(bounded = true), - ) { - onOpenOrgUnit(orgUnitItem.uid) - } - .padding(start = ((orgUnitItem.level - higherLevel + 1) * 16).dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = spacedBy(8.dp), - ) { - Icon( - imageVector = when { - !orgUnitItem.hasChildren -> - ImageVector.vectorResource(id = R.drawable.ic_tree_node_default) - orgUnitItem.isOpen -> - ImageVector.vectorResource(id = R.drawable.ic_tree_node_close) - else -> - ImageVector.vectorResource(id = R.drawable.ic_tree_node_open) - }, - tint = MaterialTheme.colorScheme.primary, - contentDescription = "", - ) - Text( - modifier = Modifier.weight(1f), - text = orgUnitItem.formattedLabel(), - style = TextStyle( - color = Color(0xDE333333), - fontSize = 12.sp, - fontFamily = defaultFontFamily, - lineHeight = 16.sp, - fontWeight = if (orgUnitItem.selectedChildrenCount > 0) { - FontWeight.Bold - } else { - FontWeight.Normal - }, - ), - ) - if (orgUnitItem.canBeSelected) { - Checkbox( - modifier = Modifier.testTag("$ITEM_CHECK_TEST_TAG${orgUnitItem.label}"), - checked = orgUnitItem.selected, - onCheckedChange = { isChecked -> - onOrgUnitChecked(orgUnitItem.uid, isChecked) - }, - ) - } - } -} - -const val DONE_TEST_TAG = "ORG_UNIT_DIALOG_DONE" -const val CANCEL_TEST_TAG = "ORG_UNIT_DIALOG_CANCEL" -const val CLEAR_TEST_TAG = "ORG_UNIT_DIALOG_CLEAR" -const val SEARCH_TEST_TAG = "ORG_UNIT_DIALOG_SEARCH" -const val ITEM_LIST_TEST_TAG = "ORG_UNIT_ITEM_LIST" -const val ITEM_TEST_TAG = "ORG_UNIT_DIALOG_ITEM_" -const val ITEM_CHECK_TEST_TAG = "ORG_UNIT_DIALOG_ITEM_CHECK_" diff --git a/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitTreeItem.kt b/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitTreeItem.kt deleted file mode 100644 index 6e1540cb35..0000000000 --- a/ui-components/src/main/java/org/dhis2/ui/dialogs/orgunit/OrgUnitTreeItem.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.dhis2.ui.dialogs.orgunit - -data class OrgUnitTreeItem( - val uid: String, - val label: String, - var isOpen: Boolean = false, - val hasChildren: Boolean = false, - val selected: Boolean = false, - val level: Int = 0, - val selectedChildrenCount: Int = 0, - val canBeSelected: Boolean = true, -) { - fun formattedLabel() = if (selectedChildrenCount == 0 || !hasChildren) { - label - } else { - "$label ($selectedChildrenCount)" - } -}