From a4c4c85d0657961ba3301e6ea41cdd93a47bded0 Mon Sep 17 00:00:00 2001 From: Siddharth Agarwal Date: Tue, 16 Jan 2024 16:47:45 +0530 Subject: [PATCH 001/327] Androapp 5724 remove organization unit in tei event ds card if user only has access to one org unit (#3459) * add logic to show organisation unit organisation unit in TEI list, DataSet and tracker program list will only be shown when user has access to more than one organisation unit. * fix lint error * move `displayOrgUnitName` logic to ui layer --------- Co-authored-by: Siddharth Agarwal --- .../ProgramEventDetailLiveAdapter.kt | 2 ++ .../ProgramEventDetailRepository.kt | 1 + .../ProgramEventDetailRepositoryImpl.kt | 6 ++++++ .../ProgramEventDetailViewModel.kt | 6 ++++++ .../programEventDetail/ProgramEventMapper.kt | 7 ------- .../eventList/ui/mapper/EventCardMapper.kt | 14 +++++++++----- .../searchTrackEntity/SearchRepositoryImpl.java | 7 +++++++ .../searchTrackEntity/ui/mapper/TEICardMapper.kt | 11 +++++++---- .../programEventDetail/ProgramEventMapperTest.kt | 4 +--- .../eventList/mapper/EventCardMapperTest.kt | 1 + .../ui/mapper/TEICardMapperTest.kt | 1 + .../org/dhis2/commons/data/SearchTeiModel.java | 10 ++++++++++ 12 files changed, 51 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailLiveAdapter.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailLiveAdapter.kt index 715543632c..0a820d8993 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailLiveAdapter.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailLiveAdapter.kt @@ -51,6 +51,8 @@ class ProgramEventDetailLiveAdapter( event = it, editable = it.event?.uid() ?.let { eventViewModel.isEditable(it) } ?: true, + displayOrgUnit = it.event?.program() + ?.let { program -> eventViewModel.displayOrganisationUnit(program) } ?: true, onSyncIconClick = { eventViewModel.eventSyncClicked.value = it.event?.uid() }, diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepository.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepository.kt index b7ef01401c..8f8b505d3c 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepository.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepository.kt @@ -26,4 +26,5 @@ interface ProgramEventDetailRepository { fun programHasCoordinates(): Boolean fun programHasAnalytics(): Boolean fun isEventEditable(eventUid: String): Boolean + fun displayOrganisationUnit(programUid: String): Boolean } diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepositoryImpl.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepositoryImpl.kt index 603c3d5fc1..6e463a8bac 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepositoryImpl.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailRepositoryImpl.kt @@ -177,4 +177,10 @@ class ProgramEventDetailRepositoryImpl internal constructor( override fun isEventEditable(eventUid: String): Boolean { return d2.eventModule().eventService().blockingIsEditable(eventUid) } + + override fun displayOrganisationUnit(programUid: String): Boolean { + return d2.organisationUnitModule().organisationUnits() + .byProgramUids(listOf(programUid)) + .blockingGet().size > 1 + } } diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailViewModel.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailViewModel.kt index a1c5037498..3c1a111b34 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailViewModel.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventDetailViewModel.kt @@ -17,9 +17,11 @@ class ProgramEventDetailViewModel( val eventClicked = MutableLiveData?>(null) var updateEvent: String? = null var recreationActivity: Boolean = false + enum class EventProgramScreen { LIST, MAP, ANALYTICS } + private val _currentScreen = MutableLiveData(EventProgramScreen.LIST) val currentScreen: LiveData get() = _currentScreen.distinctUntilChanged() @@ -60,4 +62,8 @@ class ProgramEventDetailViewModel( fun isEditable(eventUid: String): Boolean { return eventRepository.isEventEditable(eventUid) } + + fun displayOrganisationUnit(programUid: String): Boolean { + return eventRepository.displayOrganisationUnit(programUid) + } } diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventMapper.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventMapper.kt index 9f5ef60bc8..0782f8008b 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventMapper.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/ProgramEventMapper.kt @@ -5,7 +5,6 @@ import org.dhis2.commons.data.EventViewModel import org.dhis2.commons.data.EventViewModelType import org.dhis2.commons.data.ProgramEventViewModel import org.dhis2.commons.data.tuples.Pair -import org.dhis2.commons.reporting.CrashReportController import org.dhis2.data.dhislogic.DhisPeriodUtils import org.dhis2.utils.DateUtils import org.hisp.dhis.android.core.D2 @@ -25,18 +24,12 @@ import javax.inject.Inject class ProgramEventMapper @Inject constructor( val d2: D2, val periodUtils: DhisPeriodUtils, - val crashReportController: CrashReportController, ) { fun eventToEventViewModel(event: Event): EventViewModel { val programStage = d2.programModule().programStages().uid(event.programStage()).blockingGet() - crashReportController.addBreadCrumb( - "ProgramEventMapper.eventToEventViewModel", - "Event: $event", - ) - val eventDate = event.eventDate() ?: event.dueDate() return EventViewModel( diff --git a/app/src/main/java/org/dhis2/usescases/programEventDetail/eventList/ui/mapper/EventCardMapper.kt b/app/src/main/java/org/dhis2/usescases/programEventDetail/eventList/ui/mapper/EventCardMapper.kt index 22b506635e..309a05642c 100644 --- a/app/src/main/java/org/dhis2/usescases/programEventDetail/eventList/ui/mapper/EventCardMapper.kt +++ b/app/src/main/java/org/dhis2/usescases/programEventDetail/eventList/ui/mapper/EventCardMapper.kt @@ -38,13 +38,14 @@ class EventCardMapper( fun map( event: EventViewModel, editable: Boolean, + displayOrgUnit: Boolean, onSyncIconClick: () -> Unit, onCardClick: () -> Unit, ): ListCardUiModel { return ListCardUiModel( title = event.displayDate ?: "", lastUpdated = event.lastUpdate.toDateSpan(context), - additionalInfo = getAdditionalInfoList(event, editable), + additionalInfo = getAdditionalInfoList(event, editable, displayOrgUnit), actionButton = { ProvideSyncButton( state = event.event?.aggregatedSyncState(), @@ -60,6 +61,7 @@ class EventCardMapper( private fun getAdditionalInfoList( event: EventViewModel, editable: Boolean, + displayOrgUnit: Boolean, ): List { val list = event.dataElementValues?.filter { !it.second.isNullOrEmpty() @@ -70,10 +72,12 @@ class EventCardMapper( ) }?.toMutableList() ?: mutableListOf() - checkRegisteredIn( - list = list, - orgUnit = event.orgUnitName, - ) + if (displayOrgUnit) { + checkRegisteredIn( + list = list, + orgUnit = event.orgUnitName, + ) + } checkCategoryCombination( list = list, diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java index 967ffa2c3a..fe74b6c659 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/SearchRepositoryImpl.java @@ -804,6 +804,7 @@ private SearchTeiModel transform(TrackedEntitySearchItem searchItem, @Nullable P searchTei.setHeader(searchItem.getHeader()); searchTei.setSortingValue(sortingValueSetter.setSortingItem(searchTei, sortingItem)); searchTei.setTEType(searchItem.getType().displayName()); + searchTei.setDisplayOrgUnit(displayOrgUnit()); return searchTei; } @@ -948,4 +949,10 @@ public boolean canCreateInProgramWithoutSearch() { return programConfiguration != null && Boolean.TRUE.equals(programConfiguration.optionalSearch()); } } + + private boolean displayOrgUnit() { + return d2.organisationUnitModule().organisationUnits() + .byProgramUids(Collections.singletonList(currentProgram)) + .blockingGet().size() > 1; + } } diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt index 77b8bd251b..613307f91b 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapper.kt @@ -118,10 +118,13 @@ class TEICardMapper( attributeList.removeIf { it.value.isEmpty() || it.value == "-" } return attributeList.also { list -> - checkEnrolledIn( - list = list, - enrolledOrgUnit = searchTEIModel.enrolledOrgUnit, - ) + if (searchTEIModel.displayOrgUnit) { + checkEnrolledIn( + list = list, + enrolledOrgUnit = searchTEIModel.enrolledOrgUnit, + ) + } + checkEnrolledPrograms( list = list, enrolledPrograms = searchTEIModel.programInfo, diff --git a/app/src/test/java/org/dhis2/usescases/programEventDetail/ProgramEventMapperTest.kt b/app/src/test/java/org/dhis2/usescases/programEventDetail/ProgramEventMapperTest.kt index 871332b0f6..2da233445f 100644 --- a/app/src/test/java/org/dhis2/usescases/programEventDetail/ProgramEventMapperTest.kt +++ b/app/src/test/java/org/dhis2/usescases/programEventDetail/ProgramEventMapperTest.kt @@ -1,6 +1,5 @@ package org.dhis2.usescases.programEventDetail -import org.dhis2.commons.reporting.CrashReportController import org.dhis2.data.dhislogic.DhisPeriodUtils import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.category.CategoryOptionCombo @@ -25,11 +24,10 @@ class ProgramEventMapperTest { private val d2: D2 = Mockito.mock(D2::class.java, RETURNS_DEEP_STUBS) private val periodUtil: DhisPeriodUtils = mock() - private val crashReportController: CrashReportController = mock() @Before fun setUp() { - mapper = ProgramEventMapper(d2, periodUtil, crashReportController) + mapper = ProgramEventMapper(d2, periodUtil) } @Test diff --git a/app/src/test/java/org/dhis2/usescases/programEventDetail/eventList/mapper/EventCardMapperTest.kt b/app/src/test/java/org/dhis2/usescases/programEventDetail/eventList/mapper/EventCardMapperTest.kt index 4a2190381a..f4796e63a5 100644 --- a/app/src/test/java/org/dhis2/usescases/programEventDetail/eventList/mapper/EventCardMapperTest.kt +++ b/app/src/test/java/org/dhis2/usescases/programEventDetail/eventList/mapper/EventCardMapperTest.kt @@ -47,6 +47,7 @@ class EventCardMapperTest { val result = mapper.map( event = model, editable = true, + displayOrgUnit = true, onSyncIconClick = {}, onCardClick = {}, ) diff --git a/app/src/test/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapperTest.kt b/app/src/test/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapperTest.kt index 94b9d08f01..2e4dcb6df9 100644 --- a/app/src/test/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapperTest.kt +++ b/app/src/test/java/org/dhis2/usescases/searchTrackEntity/ui/mapper/TEICardMapperTest.kt @@ -95,6 +95,7 @@ class TEICardMapperTest { .aggregatedSyncState(State.SYNCED) .build() enrolledOrgUnit = "OrgUnit" + displayOrgUnit = true setCurrentEnrollment( Enrollment.builder() .uid("EnrollmentUid") diff --git a/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java b/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java index 475f12f888..96c4990542 100644 --- a/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java +++ b/commons/src/main/java/org/dhis2/commons/data/SearchTeiModel.java @@ -40,6 +40,8 @@ public class SearchTeiModel implements CarouselItemModel { private String sortingValue; private String teTypeName; private String enrolledOrgUnit; + + private Boolean displayOrgUnit; private boolean showNavigationButton = false; @Nullable public String onlineErrorMessage; @Nullable public D2ErrorCode onlineErrorCode; @@ -245,6 +247,14 @@ public String getEnrolledOrgUnit() { return enrolledOrgUnit; } + public void setDisplayOrgUnit(Boolean display) { + displayOrgUnit = display; + } + + public Boolean getDisplayOrgUnit() { + return displayOrgUnit; + } + public void setShowNavigationButton(boolean showNavigationButton) { this.showNavigationButton = showNavigationButton; } From eb60c96278c3e8b8d6bc3ea6f9669d8943cfab06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Miguel=20Rubio?= Date: Tue, 23 Jan 2024 12:28:16 +0100 Subject: [PATCH 002/327] [ANDROAPP-5887] update SDK (#3471) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0961d859f5..5aeba73972 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ hilt = '2.47' hiltCompiler = '1.0.0' jacoco = '0.8.10' designSystem = "1.0-20231116.084101-124" -dhis2sdk = "1.9.1-20231228.075857-13" +dhis2sdk = "1.10.0-20240119.100209-6" ruleEngine = "2.1.9" appcompat = "1.6.1" annotation = "1.6.0" From 50342fb5a6492bf6c5c3a17a88b6a557cdd435aa Mon Sep 17 00:00:00 2001 From: Xavier Molloy <44061143+xavimolloy@users.noreply.github.com> Date: Wed, 24 Jan 2024 09:20:58 +0100 Subject: [PATCH 003/327] feat: [ANDROAPP-5830] update mobile UI version to 0.2-20240103.112017-2 (#3461) * fix: [ANDROAPP-5830] update mobile-ui version 0.2-20240103.112017-2 * feat: [ANDROAPP-5830] add to do's and bare minimun for compilation * ANDROAPP-5829-adapt-input-image-to-new-mobile-ui-functionality (#3462) * Handle `Intent.ACTION_SEND` in form view for sharing files * Open chooser intent when share button is clicked in `ImageInput` * Open chooser intent when share button is clicked in `InputSignature`` * Move `getBitmap` extension to commons module * Move `FormFileProvider` to commons module * Add image detail activity Currently we don't have a working "save image" functionality. So, it didn't make sense to copy the implementation that is not working. So for the time being I marked it as TODO * Launch `ImageDetailActivity` instead of `ImageDetailBottomDialog` * Remove `ImageDetailBottomDialog` * feat: [ANDROAPP-5848] Adapt Input dropdown to new functionality (#3465) * feat: [ANDROAPP-5848] remove old option set dialog, recycler event and deprecated code. refactor OptionSetConfiguration to default adapt input dropdown to new functionality * feat: [ANDROAPP-5848] remove deprecated code * fix: [ANDROAPP-5848] remove deprecated code * fix: [ANDROAPP-5848] ktlint fix * fix: [ANDROAPP-5848] dismiss keyboard on dismiss popup * fix: [ANDROAPP-5848] add old pop in again to maintain old form logic * feat: [ANDROAPP-5848] update mobile ui to version 0.2-20240123.112704-7 * fix: [ANDROAPP-5848] sonar fix * fix: [ANDROAPP-5848] sonar fix --------- Co-authored-by: Sasikanth Miriyampalli --- app/src/main/AndroidManifest.xml | 10 +- .../enrollment/EnrollmentActivity.kt | 14 +-- .../providers/InputFieldsProvider.kt | 104 +++++------------- .../eventDetails/ui/EventDetailsFragment.kt | 18 --- .../EventInitialRepositoryImpl.java | 1 - .../listView/SearchTEList.kt | 12 +- .../searchTrackEntity/mapView/SearchTEMap.kt | 17 ++- .../teidata/TEIDataFragment.kt | 24 ++-- commons/src/main/AndroidManifest.xml | 5 +- .../dhis2/commons}/data/FormFileProvider.kt | 2 +- .../imagedetail/ImageDetailActivity.kt | 75 +++++++++++++ .../imagedetail/ImageDetailBottomDialog.kt | 83 -------------- .../commons/extensions/PictureBindings.kt | 9 ++ .../workingLists/WorkingListChipGroup.kt | 4 +- commons/src/main/res/values/strings.xml | 1 + form/src/main/AndroidManifest.xml | 1 + .../dhis2/form/data/EnrollmentRepository.kt | 4 +- .../org/dhis2/form/data/EventRepository.kt | 4 +- .../form/model/OptionSetConfiguration.kt | 4 +- .../main/java/org/dhis2/form/ui/FormView.kt | 26 ++++- .../dhis2/form/ui/binding/PictureBindings.kt | 8 +- .../form/ui/dialog/QRDetailBottomDialog.kt | 2 +- .../provider/inputfield/DropdownProvider.kt | 99 ++++------------- .../provider/inputfield/ImageInputProvider.kt | 15 ++- .../provider/inputfield/SignatureProvider.kt | 11 +- gradle/libs.versions.toml | 2 +- 26 files changed, 235 insertions(+), 320 deletions(-) rename {form/src/main/java/org/dhis2/form => commons/src/main/java/org/dhis2/commons}/data/FormFileProvider.kt (88%) create mode 100644 commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt delete mode 100644 commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailBottomDialog.kt create mode 100644 commons/src/main/java/org/dhis2/commons/extensions/PictureBindings.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3e5e952e1..0a64207c11 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -89,7 +89,9 @@ + android:configChanges="keyboardHidden|screenSize" + android:windowSoftInputMode="stateAlwaysHidden" + /> @@ -98,7 +100,7 @@ + android:windowSoftInputMode="stateAlwaysHidden" /> @@ -125,7 +127,7 @@ + android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> + android:windowSoftInputMode="adjustResize|stateAlwaysHidden" /> Unit, onClearCatCombo: (EventCategory) -> Unit, onOptionSelected: (CategoryOption?) -> Unit, required: Boolean = false, @@ -218,74 +207,31 @@ fun ProvideCategorySelector( ) } - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = {}, - ) { - InputDropDown( - modifier = modifier, - title = category.name, - state = getInputState(detailsEnabled), - selectedItem = selectedItem, - onResetButtonClicked = { - selectedItem = null - onClearCatCombo(category) - }, - onArrowDropDownButtonClicked = { - expanded = !expanded - }, - isRequiredField = required, - ) - - if (expanded) { - if (category.optionsSize > DEFAULT_COUNT_LIMIT) { - onShowCategoryDialog(category) - expanded = false - } else { - DropdownMenu( - modifier = modifier.exposedDropdownSize(), - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - val selectableOptions = category.options - .filter { option -> - option.access().data().write() - }.filter { option -> - option.inDateRange(currentDate) - }.filter { option -> - option.inOrgUnit(selectedOrgUnit) - } - selectableOptions.forEach { option -> - val isSelected = option.displayName() == selectedItem - DropdownMenuItem( - modifier = Modifier.background( - when { - isSelected -> SurfaceColor.PrimaryContainer - else -> Color.Transparent - }, - ), - content = { - Text( - text = option.displayName() ?: option.code() ?: "", - color = when { - isSelected -> TextColor.OnPrimaryContainer - else -> TextColor.OnSurface - }, - ) - }, - onClick = { - expanded = false - selectedItem = option.displayName() - onOptionSelected(option) - }, - ) - } - } - } + val selectableOptions = category.options + .filter { option -> + option.access().data().write() + }.filter { option -> + option.inDateRange(currentDate) + }.filter { option -> + option.inOrgUnit(selectedOrgUnit) } - } + val dropdownItems = selectableOptions.map { DropdownItem(it.displayName() ?: it.code() ?: "") } + InputDropDown( + modifier = modifier, + title = category.name, + state = getInputState(detailsEnabled), + selectedItem = DropdownItem(selectedItem ?: ""), + onResetButtonClicked = { + selectedItem = null + onClearCatCombo(category) + }, + onItemSelected = { newSelectedDropdownItem -> + selectedItem = newSelectedDropdownItem.label + onOptionSelected(selectableOptions.firstOrNull { it.displayName() == newSelectedDropdownItem.label }) + }, + dropdownItems = dropdownItems, + isRequiredField = required, + ) } private fun getInputState(enabled: Boolean) = if (enabled) { 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 74561abba0..5b50ec90b2 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 @@ -39,7 +39,6 @@ import org.dhis2.databinding.EventDetailsFragmentBinding import org.dhis2.maps.views.MapSelectorActivity import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.EventDetailsComponentProvider import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.EventDetailsModule -import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.models.EventCategory import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.models.EventDetails import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.ProvideCategorySelector import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.ProvideCoordinates @@ -47,8 +46,6 @@ import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.Prov import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.ProvideOrgUnit import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.providers.ProvideRadioButtons import org.dhis2.usescases.general.FragmentGlobalAbstract -import org.dhis2.utils.category.CategoryDialog -import org.dhis2.utils.category.CategoryDialog.Companion.TAG import org.dhis2.utils.customviews.PeriodDialog import org.hisp.dhis.android.core.common.FeatureType import org.hisp.dhis.android.core.enrollment.EnrollmentStatus @@ -179,9 +176,6 @@ class EventDetailsFragment : FragmentGlobalAbstract() { detailsEnabled = details.enabled, currentDate = date.currentDate, selectedOrgUnit = details.selectedOrgUnit, - onShowCategoryDialog = { - showCategoryDialog(it) - }, onClearCatCombo = { viewModel.onClearCatCombo() }, @@ -346,18 +340,6 @@ class EventDetailsFragment : FragmentGlobalAbstract() { showInfoDialog(getString(R.string.error), getString(R.string.no_org_units)) } - private fun showCategoryDialog(category: EventCategory) { - CategoryDialog( - CategoryDialog.Type.CATEGORY_OPTIONS, - category.uid, - true, - viewModel.eventDate.value.currentDate, - ) { categoryOption -> - val selectedOption = Pair(category.uid, categoryOption) - viewModel.setUpCategoryCombo(selectedOption) - }.show(requireActivity().supportFragmentManager, TAG) - } - private fun getEventCreationType(typeString: String?): EventCreationType { return typeString?.let { EventCreationType.valueOf(it) diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java index 7b3d36fbf0..f9c53b58f1 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventInitial/EventInitialRepositoryImpl.java @@ -40,7 +40,6 @@ import java.util.Date; import java.util.List; -import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Observable; import timber.log.Timber; diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/listView/SearchTEList.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/listView/SearchTEList.kt index c585a11183..58d186a0e6 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/listView/SearchTEList.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/listView/SearchTEList.kt @@ -21,7 +21,7 @@ import androidx.paging.PagedList import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.RecyclerView import org.dhis2.bindings.dp -import org.dhis2.commons.dialogs.imagedetail.ImageDetailBottomDialog +import org.dhis2.commons.dialogs.imagedetail.ImageDetailActivity import org.dhis2.commons.filters.workingLists.WorkingListViewModel import org.dhis2.commons.filters.workingLists.WorkingListViewModelFactory import org.dhis2.commons.resources.ColorUtils @@ -35,7 +35,6 @@ import org.dhis2.usescases.searchTrackEntity.ui.CreateNewButton import org.dhis2.usescases.searchTrackEntity.ui.FullSearchButtonAndWorkingList import org.dhis2.usescases.searchTrackEntity.ui.mapper.TEICardMapper import org.dhis2.utils.isLandscape -import java.io.File import javax.inject.Inject const val ARG_FROM_RELATIONSHIP = "ARG_FROM_RELATIONSHIP" @@ -231,8 +230,13 @@ class SearchTEList : FragmentGlobalAbstract() { } private fun displayImageDetail(imagePath: String) { - ImageDetailBottomDialog(null, File(imagePath)) - .show(childFragmentManager, ImageDetailBottomDialog.TAG) + val intent = ImageDetailActivity.intent( + context = requireContext(), + title = null, + imagePath = imagePath, + ) + + startActivity(intent) } private fun observeNewData() { diff --git a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/mapView/SearchTEMap.kt b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/mapView/SearchTEMap.kt index 33b28e34d5..bd8ac8e108 100644 --- a/app/src/main/java/org/dhis2/usescases/searchTrackEntity/mapView/SearchTEMap.kt +++ b/app/src/main/java/org/dhis2/usescases/searchTrackEntity/mapView/SearchTEMap.kt @@ -14,7 +14,7 @@ import org.dhis2.animations.CarouselViewAnimations import org.dhis2.bindings.dp import org.dhis2.commons.bindings.clipWithRoundedCorners import org.dhis2.commons.data.RelationshipOwnerType -import org.dhis2.commons.dialogs.imagedetail.ImageDetailBottomDialog +import org.dhis2.commons.dialogs.imagedetail.ImageDetailActivity import org.dhis2.commons.locationprovider.LocationSettingLauncher import org.dhis2.commons.resources.ColorType import org.dhis2.commons.resources.ColorUtils @@ -33,7 +33,6 @@ import org.dhis2.usescases.searchTrackEntity.SearchTEIViewModel import org.dhis2.usescases.searchTrackEntity.SearchTeiViewModelFactory import org.dhis2.utils.NetworkUtils import org.dhis2.utils.isPortrait -import java.io.File import javax.inject.Inject const val ARG_FROM_RELATIONSHIP = "ARG_FROM_RELATIONSHIP" @@ -259,14 +258,14 @@ class SearchTEMap : FragmentGlobalAbstract(), MapboxMap.OnMapClickListener { true } .addOnProfileImageClickListener { path: String? -> - if (binding.mapCarousel.carouselEnabled) { - ImageDetailBottomDialog( - null, - File(path), - ).show( - childFragmentManager, - ImageDetailBottomDialog.TAG, + if (binding.mapCarousel.carouselEnabled && !path.isNullOrBlank()) { + val intent = ImageDetailActivity.intent( + context = requireContext(), + title = null, + imagePath = path, ) + + startActivity(intent) } Unit } 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 78d3e1941b..22e458dbf3 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 @@ -31,7 +31,7 @@ import org.dhis2.commons.data.EventViewModel import org.dhis2.commons.data.StageSection import org.dhis2.commons.dialogs.CustomDialog import org.dhis2.commons.dialogs.DialogClickListener -import org.dhis2.commons.dialogs.imagedetail.ImageDetailBottomDialog +import org.dhis2.commons.dialogs.imagedetail.ImageDetailActivity import org.dhis2.commons.filters.FilterItem import org.dhis2.commons.filters.FilterManager import org.dhis2.commons.filters.FilterManager.PeriodRequest @@ -269,10 +269,13 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { val card = teiDashboardCardMapper.map( dashboardModel = dashboardModel, onImageClick = { fileToShow -> - ImageDetailBottomDialog( - null, - fileToShow, - ).show(childFragmentManager, ImageDetailBottomDialog.TAG) + val intent = ImageDetailActivity.intent( + context = requireActivity(), + title = null, + imagePath = fileToShow.path, + ) + + startActivity(intent) }, phoneCallback = { openChooser(it, Intent.ACTION_DIAL) }, emailCallback = { openChooser(it, Intent.ACTION_SENDTO) }, @@ -537,10 +540,13 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { binding.cardFront.teiImage.setOnClickListener { val fileToShow = File(filePath) if (fileToShow.exists()) { - ImageDetailBottomDialog( - null, - fileToShow, - ).show(childFragmentManager, ImageDetailBottomDialog.TAG) + val intent = ImageDetailActivity.intent( + context = requireActivity(), + title = null, + imagePath = fileToShow.path, + ) + + startActivity(intent) } } } diff --git a/commons/src/main/AndroidManifest.xml b/commons/src/main/AndroidManifest.xml index d1a58dce21..6c8c73cf91 100644 --- a/commons/src/main/AndroidManifest.xml +++ b/commons/src/main/AndroidManifest.xml @@ -16,6 +16,9 @@ android:name="org.dhis2.commons.featureconfig.ui.FeatureConfigView" android:exported="false"/> + + - \ No newline at end of file + diff --git a/form/src/main/java/org/dhis2/form/data/FormFileProvider.kt b/commons/src/main/java/org/dhis2/commons/data/FormFileProvider.kt similarity index 88% rename from form/src/main/java/org/dhis2/form/data/FormFileProvider.kt rename to commons/src/main/java/org/dhis2/commons/data/FormFileProvider.kt index f62191993e..e807a84461 100644 --- a/form/src/main/java/org/dhis2/form/data/FormFileProvider.kt +++ b/commons/src/main/java/org/dhis2/commons/data/FormFileProvider.kt @@ -1,4 +1,4 @@ -package org.dhis2.form.data +package org.dhis2.commons.data import android.content.Context diff --git a/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt b/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt new file mode 100644 index 0000000000..5d0a0e9594 --- /dev/null +++ b/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailActivity.kt @@ -0,0 +1,75 @@ +package org.dhis2.commons.dialogs.imagedetail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.core.content.FileProvider +import org.dhis2.commons.R +import org.dhis2.commons.data.FormFileProvider +import org.dhis2.commons.extensions.getBitmap +import org.hisp.dhis.mobile.ui.designsystem.component.FullScreenImage +import timber.log.Timber +import java.io.File +import java.io.IOException + +class ImageDetailActivity : AppCompatActivity() { + + companion object { + + private const val ARG_IMAGE_TITLE = "arg_image_title" + private const val ARG_IMAGE_PATH = "arg_image_path" + + fun intent(context: Context, title: String?, imagePath: String): Intent { + return Intent(context, ImageDetailActivity::class.java).apply { + putExtra(ARG_IMAGE_TITLE, title) + putExtra(ARG_IMAGE_PATH, imagePath) + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val title = intent.getStringExtra(ARG_IMAGE_TITLE) + val imagePath = intent.getStringExtra(ARG_IMAGE_PATH)!! + + setContent { + val painter = remember(imagePath) { + imagePath.getBitmap()?.let { BitmapPainter(it.asImageBitmap()) } + } + + FullScreenImage( + painter = painter!!, + title = title.orEmpty(), + onDismiss = { finish() }, + onDownloadButtonClick = { }, + onShareButtonClick = { shareImage(imagePath) }, + ) + } + } + + private fun shareImage(image: String) { + val intent = Intent(Intent.ACTION_SEND).apply { + val contentUri = FileProvider.getUriForFile( + this@ImageDetailActivity, + FormFileProvider.fileProviderAuthority, + File(image), + ) + setDataAndType(contentUri, contentResolver.getType(contentUri)) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + putExtra(Intent.EXTRA_STREAM, contentUri) + } + + val title = resources.getString(R.string.open_with) + val chooser = Intent.createChooser(intent, title) + try { + startActivity(chooser) + } catch (e: IOException) { + Timber.e(e) + } + } +} diff --git a/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailBottomDialog.kt b/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailBottomDialog.kt deleted file mode 100644 index 09a95bd288..0000000000 --- a/commons/src/main/java/org/dhis2/commons/dialogs/imagedetail/ImageDetailBottomDialog.kt +++ /dev/null @@ -1,83 +0,0 @@ -package org.dhis2.commons.dialogs.imagedetail - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.databinding.DataBindingUtil -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialog -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import org.dhis2.commons.R -import org.dhis2.commons.bindings.dp -import org.dhis2.commons.bindings.widthAndHeight -import org.dhis2.commons.databinding.DetailImageBottomDialogBinding -import org.dhis2.commons.resources.ColorType -import org.dhis2.commons.resources.ColorUtils -import java.io.File - -class ImageDetailBottomDialog( - val label: String?, - private val fileToShow: File, -) : BottomSheetDialogFragment() { - companion object { - const val TAG: String = "IMG_DETAIL_DIALOG" - } - - private lateinit var binding: DetailImageBottomDialogBinding - - val colorUtils: ColorUtils = ColorUtils() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - binding = - DataBindingUtil.inflate(inflater, R.layout.detail_image_bottom_dialog, container, false) - binding.title = label - binding.closeButton.setImageDrawable( - colorUtils.tintDrawableWithColor( - binding.closeButton.drawable, - colorUtils.getPrimaryColor(requireContext(), ColorType.PRIMARY), - ), - ) - binding.closeButton.setOnClickListener { dismiss() } - - return binding.root - } - - // This is necessary to show the bottomSheet dialog with full height on landscape - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - view.viewTreeObserver.addOnGlobalLayoutListener { - val dialog = dialog as BottomSheetDialog - - val bottomSheet = - dialog.findViewById( - com.google.android.material.R.id.design_bottom_sheet, - ) - val behavior = BottomSheetBehavior.from(bottomSheet!!) - behavior.state = BottomSheetBehavior.STATE_EXPANDED - behavior.setPeekHeight(0) - } - } - - override fun onResume() { - super.onResume() - val (width, height) = fileToShow.widthAndHeight(300.dp) - Glide.with(this) - .load(fileToShow) - .apply(RequestOptions.skipMemoryCacheOf(true)) - .apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.NONE)) - .apply(RequestOptions.bitmapTransform(RoundedCorners(40))) - .apply(RequestOptions().override(width, height)) - .skipMemoryCache(true) - .into(binding.fullImage) - } -} diff --git a/commons/src/main/java/org/dhis2/commons/extensions/PictureBindings.kt b/commons/src/main/java/org/dhis2/commons/extensions/PictureBindings.kt new file mode 100644 index 0000000000..88bf020036 --- /dev/null +++ b/commons/src/main/java/org/dhis2/commons/extensions/PictureBindings.kt @@ -0,0 +1,9 @@ +package org.dhis2.commons.extensions + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import java.io.File + +fun String.getBitmap(): Bitmap? = File(this) + .takeIf { it.exists() } + ?.let { BitmapFactory.decodeFile(it.absolutePath) } diff --git a/commons/src/main/java/org/dhis2/commons/filters/workingLists/WorkingListChipGroup.kt b/commons/src/main/java/org/dhis2/commons/filters/workingLists/WorkingListChipGroup.kt index 176adc638f..125ea08aed 100644 --- a/commons/src/main/java/org/dhis2/commons/filters/workingLists/WorkingListChipGroup.kt +++ b/commons/src/main/java/org/dhis2/commons/filters/workingLists/WorkingListChipGroup.kt @@ -21,7 +21,7 @@ import org.dhis2.commons.databinding.ItemFilterWorkingListChipBinding import org.dhis2.commons.filters.FilterManager import org.dhis2.commons.filters.WorkingListFilter import org.dhis2.commons.filters.data.EmptyWorkingList -import org.hisp.dhis.mobile.ui.designsystem.component.Chip +import org.hisp.dhis.mobile.ui.designsystem.component.FilterChip import org.hisp.dhis.mobile.ui.designsystem.theme.Spacing class WorkingListChipGroup @JvmOverloads constructor( @@ -88,7 +88,7 @@ fun WorkingListChipGroup( workingListFilterState.value?.let { workingListFilter -> LazyRow(modifier) { itemsIndexed(workingListFilter.workingLists) { index, workingList -> - Chip( + FilterChip( modifier = Modifier.padding( start = if (index == 0) Spacing.Spacing16 else Spacing.Spacing0, end = if (index == workingListFilter.workingLists.size - 1) { diff --git a/commons/src/main/res/values/strings.xml b/commons/src/main/res/values/strings.xml index 7a4741b683..537fd500d8 100644 --- a/commons/src/main/res/values/strings.xml +++ b/commons/src/main/res/values/strings.xml @@ -239,4 +239,5 @@ %d year overdue %d years overdue + Open with diff --git a/form/src/main/AndroidManifest.xml b/form/src/main/AndroidManifest.xml index d63ef3cb31..7a7f0d1e0a 100644 --- a/form/src/main/AndroidManifest.xml +++ b/form/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ diff --git a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt index 7fc03cc015..03e3dd0b91 100644 --- a/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/EnrollmentRepository.kt @@ -175,9 +175,7 @@ class EnrollmentRepository( if (!optionSet.isNullOrEmpty()) { val optionCount = d2.optionModule().options().byOptionSetUid().eq(optionSet).blockingCount() - optionSetConfig = OptionSetConfiguration.config( - optionCount, - ) { + optionSetConfig = OptionSetConfiguration.config(optionCount) { d2.optionModule().options().byOptionSetUid().eq(optionSet) .orderBySortOrder(RepositoryScope.OrderByDirection.ASC) .blockingGet() diff --git a/form/src/main/java/org/dhis2/form/data/EventRepository.kt b/form/src/main/java/org/dhis2/form/data/EventRepository.kt index 09414a55c3..d452cf7d0b 100644 --- a/form/src/main/java/org/dhis2/form/data/EventRepository.kt +++ b/form/src/main/java/org/dhis2/form/data/EventRepository.kt @@ -146,9 +146,7 @@ class EventRepository( val optionCount = d2.optionModule().options().byOptionSetUid().eq(optionSet) .blockingCount() - optionSetConfig = OptionSetConfiguration.config( - optionCount, - ) { + optionSetConfig = OptionSetConfiguration.config(optionCount) { d2.optionModule().options().byOptionSetUid().eq(optionSet) .orderBySortOrder(RepositoryScope.OrderByDirection.ASC).blockingGet() } diff --git a/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt b/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt index 77607a14e7..2b80108554 100644 --- a/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt +++ b/form/src/main/java/org/dhis2/form/model/OptionSetConfiguration.kt @@ -18,9 +18,11 @@ sealed class OptionSetConfiguration( ) data class BigOptionSet( + override val options: List