diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa604adfc6..c1da539cfb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,7 +115,7 @@ diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt index 57a5b20dd6..b0c1f1338a 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureActivity.kt @@ -11,6 +11,8 @@ import android.widget.PopupMenu import androidx.databinding.DataBindingUtil import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModelProvider +import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar @@ -22,6 +24,7 @@ import org.dhis2.commons.dialogs.CustomDialog import org.dhis2.commons.dialogs.DialogClickListener import org.dhis2.commons.popupmenu.AppMenuHelper import org.dhis2.commons.resources.ResourceManager +import org.dhis2.commons.sync.OnDismissListener import org.dhis2.commons.sync.SyncContext import org.dhis2.databinding.ActivityEventCaptureBinding import org.dhis2.form.model.EventMode @@ -31,6 +34,7 @@ import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialog import org.dhis2.ui.dialogs.bottomsheet.BottomSheetDialogUiModel import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle.DiscardButton import org.dhis2.ui.dialogs.bottomsheet.DialogButtonStyle.MainButton +import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventCaptureFragment.EventCaptureFormFragment import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.eventCaptureFragment.OnEditionListener import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.model.EventCompletionDialog import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.EventDetailsComponent @@ -38,7 +42,10 @@ import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.Even import org.dhis2.usescases.eventsWithoutRegistration.eventDetails.injection.EventDetailsModule import org.dhis2.usescases.eventsWithoutRegistration.eventInitial.EventInitialActivity import org.dhis2.usescases.general.ActivityGlobalAbstract +import org.dhis2.usescases.teiDashboard.DashboardViewModel import org.dhis2.usescases.teiDashboard.dashboardfragments.relationships.MapButtonObservable +import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataActivityContract +import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataFragment.Companion.newInstance import org.dhis2.utils.analytics.CLICK import org.dhis2.utils.analytics.DELETE_EVENT import org.dhis2.utils.analytics.SHOW_HELP @@ -48,13 +55,16 @@ import org.dhis2.utils.customviews.navigationbar.NavigationPageConfigurator import org.dhis2.utils.granularsync.OPEN_ERROR_LOCATION import org.dhis2.utils.granularsync.SyncStatusDialog import org.dhis2.utils.granularsync.shouldLaunchSyncDialog +import org.dhis2.utils.isLandscape +import org.dhis2.utils.isPortrait import javax.inject.Inject class EventCaptureActivity : ActivityGlobalAbstract(), EventCaptureContract.View, MapButtonObservable, - EventDetailsComponentProvider { + EventDetailsComponentProvider, + TEIDataActivityContract { private lateinit var binding: ActivityEventCaptureBinding @JvmField @@ -78,37 +88,53 @@ class EventCaptureActivity : var eventCaptureComponent: EventCaptureComponent? = null var programUid: String? = null var eventUid: String? = null + private var teiUid: String? = null + private var enrollmentUid: String? = null private val relationshipMapButton: LiveData = MutableLiveData(false) private var onEditionListener: OnEditionListener? = null private var adapter: EventCapturePagerAdapter? = null + private var eventViewPager: ViewPager2? = null + private var dashboardViewModel: DashboardViewModel? = null override fun onCreate(savedInstanceState: Bundle?) { eventUid = intent.getStringExtra(Constants.EVENT_UID) - eventCaptureComponent = this.app().userComponent()!!.plus( - EventCaptureModule( - this, - eventUid, - ), - ) - eventCaptureComponent!!.inject(this) + programUid = intent.getStringExtra(Constants.PROGRAM_UID) + setUpEventCaptureComponent(eventUid) + teiUid = presenter!!.teiUid + enrollmentUid = presenter!!.enrollmentUid themeManager!!.setProgramTheme(intent.getStringExtra(Constants.PROGRAM_UID)!!) super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_event_capture) binding.presenter = presenter + eventViewPager = when { + this.isLandscape() -> binding.eventViewLandPager + else -> binding.eventViewPager + } eventMode = intent.getSerializableExtra(Constants.EVENT_MODE) as EventMode? setUpViewPagerAdapter() setUpNavigationBar() + setUpEventCaptureFormLandscape(eventUid ?: "") + if (this.isLandscape() && areTeiUidAndEnrollmentUidNotNull()) { + val viewModelFactory = this.app().dashboardComponent()?.dashboardViewModelFactory() + + viewModelFactory?.let { + dashboardViewModel = + ViewModelProvider(this, viewModelFactory)[DashboardViewModel::class.java] + supportFragmentManager.beginTransaction().replace(R.id.tei_column, newInstance(programUid, teiUid, enrollmentUid)).commit() + dashboardViewModel?.updateSelectedEventUid(eventUid) + } + } showProgress() presenter!!.initNoteCounter() presenter!!.init() - binding.syncButton.setOnClickListener { showSyncDialog() } + binding.syncButton.setOnClickListener { showSyncDialog(EVENT_SYNC) } if (intent.shouldLaunchSyncDialog()) { - showSyncDialog() + showSyncDialog(EVENT_SYNC) } } private fun setUpViewPagerAdapter() { - binding.eventViewPager.isUserInputEnabled = false + eventViewPager?.isUserInputEnabled = false adapter = EventCapturePagerAdapter( this, intent.getStringExtra(Constants.PROGRAM_UID), @@ -118,8 +144,8 @@ class EventCaptureActivity : intent.getBooleanExtra(OPEN_ERROR_LOCATION, false), eventMode, ) - binding.eventViewPager.adapter = adapter - binding.eventViewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() { + eventViewPager?.adapter = adapter + eventViewPager?.registerOnPageChangeCallback(object : OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) if (position == 0 && eventMode !== EventMode.NEW) { @@ -136,12 +162,56 @@ class EventCaptureActivity : private fun setUpNavigationBar() { binding.navigationBar.pageConfiguration(pageConfigurator!!) + eventViewPager?.registerOnPageChangeCallback( + object : OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + binding.navigationBar.selectItemAt(position) + } + }, + ) binding.navigationBar.setOnItemSelectedListener { item: MenuItem -> - binding.eventViewPager.currentItem = adapter!!.getDynamicTabIndex(item.itemId) + eventViewPager?.currentItem = adapter!!.getDynamicTabIndex(item.itemId) true } } + private fun setUpEventCaptureFormLandscape(eventUid: String) { + if (this.isLandscape()) { + supportFragmentManager.beginTransaction() + .replace(R.id.event_form, EventCaptureFormFragment.newInstance(eventUid, false, eventMode)) + .commit() + } + } + + private fun setUpEventCaptureComponent(eventUid: String?) { + eventCaptureComponent = app().userComponent()!!.plus( + EventCaptureModule( + this, + eventUid, + this.isPortrait(), + ), + ) + eventCaptureComponent!!.inject(this) + } + + private fun updateLandscapeViewsOnEventChange(newEventUid: String) { + if (newEventUid != this.eventUid) { + this.eventUid = newEventUid + setUpEventCaptureComponent(newEventUid) + setUpViewPagerAdapter() + setUpNavigationBar() + setUpEventCaptureFormLandscape(newEventUid) + showProgress() + presenter!!.initNoteCounter() + presenter!!.init() + } + } + + private fun areTeiUidAndEnrollmentUidNotNull(): Boolean { + return teiUid != null && enrollmentUid != null + } + fun openDetails() { binding.navigationBar.selectItemAt(0) } @@ -158,6 +228,9 @@ class EventCaptureActivity : override fun onResume() { super.onResume() presenter!!.refreshTabCounters() + with(dashboardViewModel) { + this?.selectedEventUid()?.observe(this@EventCaptureActivity, ::updateLandscapeViewsOnEventChange) + } } override fun onDestroy() { @@ -210,7 +283,11 @@ class EventCaptureActivity : } private fun isFormScreen(): Boolean { - return adapter?.isFormScreenShown(binding.eventViewPager.currentItem) == true + return if (this.isPortrait()) { + adapter?.isFormScreenShown(binding.eventViewPager?.currentItem) == true + } else { + true + } } override fun updatePercentage(primaryValue: Float) { @@ -225,25 +302,28 @@ class EventCaptureActivity : emptyMandatoryFields: Map, eventCompletionDialog: EventCompletionDialog, ) { - if (binding.navigationBar.selectedItemId == R.id.navigation_data_entry) { - val dialog = BottomSheetDialog( - bottomSheetDialogUiModel = eventCompletionDialog.bottomSheetDialogUiModel, - onMainButtonClicked = { - setAction(eventCompletionDialog.mainButtonAction) - }, - onSecondaryButtonClicked = { - eventCompletionDialog.secondaryButtonAction?.let { setAction(it) } - }, - content = if (eventCompletionDialog.fieldsWithIssues.isNotEmpty()) { - { bottomSheetDialog -> - ErrorFieldList(eventCompletionDialog.fieldsWithIssues) { - bottomSheetDialog.dismiss() - } + val dialog = BottomSheetDialog( + bottomSheetDialogUiModel = eventCompletionDialog.bottomSheetDialogUiModel, + onMainButtonClicked = { + setAction(eventCompletionDialog.mainButtonAction) + }, + onSecondaryButtonClicked = { + eventCompletionDialog.secondaryButtonAction?.let { setAction(it) } + }, + content = if (eventCompletionDialog.fieldsWithIssues.isNotEmpty()) { + { bottomSheetDialog -> + ErrorFieldList(eventCompletionDialog.fieldsWithIssues) { + bottomSheetDialog.dismiss() } - } else { - null - }, - ) + } + } else { + null + }, + ) + if (binding.navigationBar.selectedItemId == R.id.navigation_data_entry) { + dialog.show(supportFragmentManager, SHOW_OPTIONS) + } + if (this.isLandscape()) { dialog.show(supportFragmentManager, SHOW_OPTIONS) } } @@ -452,23 +532,66 @@ class EventCaptureActivity : return eventCaptureComponent!!.plus(module) } - private fun showSyncDialog() { - SyncStatusDialog.Builder() - .withContext(this) - .withSyncContext(SyncContext.Event(eventUid!!)) - .onNoConnectionListener { - val contextView = findViewById(R.id.navigationBar) - Snackbar.make( - contextView, - R.string.sync_offline_check_connection, - Snackbar.LENGTH_SHORT, - ).show() - } - .show("EVENT_SYNC") + private fun showSyncDialog(syncType: String) { + val syncContext = when (syncType) { + TEI_SYNC -> enrollmentUid?.let { SyncContext.Enrollment(it) } + EVENT_SYNC -> SyncContext.Event(eventUid!!) + else -> null + } + + syncContext?.let { + SyncStatusDialog.Builder() + .withContext(this) + .withSyncContext(it) + .onDismissListener(object : OnDismissListener { + override fun onDismiss(hasChanged: Boolean) { + if (hasChanged && syncType == TEI_SYNC) { + dashboardViewModel?.updateDashboard() + } + } + }) + .onNoConnectionListener { + val contextView = findViewById(R.id.navigationBar) + Snackbar.make( + contextView, + R.string.sync_offline_check_connection, + Snackbar.LENGTH_SHORT, + ).show() + } + .show(syncType) + } + } + + override fun openSyncDialog() { + showSyncDialog(TEI_SYNC) + } + + override fun finishActivity() { + finish() + } + + override fun restoreAdapter(programUid: String, teiUid: String, enrollmentUid: String) { + // we do not restore adapter in events + } + + override fun executeOnUIThread() { + activity.runOnUiThread { + showDescription(getString(R.string.error_applying_rule_effects)) + } + } + + override fun getContext(): Context { + return this + } + + override fun activityTeiUid(): String? { + return teiUid } companion object { private const val SHOW_OPTIONS = "SHOW_OPTIONS" + private const val TEI_SYNC = "SYNC_TEI" + private const val EVENT_SYNC = "EVENT_SYNC" @JvmStatic fun getActivityBundle(eventUid: String, programUid: String, eventMode: EventMode): Bundle { diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java index 7581ab9fdf..0cbab10877 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureContract.java @@ -102,6 +102,12 @@ void attemptFinish(boolean canComplete, void emitAction(@NotNull EventCaptureAction onBack); String programStage(); + + @Nullable + String getTeiUid(); + + @Nullable + String getEnrollmentUid(); } public interface EventCaptureRepository { @@ -149,6 +155,12 @@ public interface EventCaptureRepository { boolean hasRelationships(); ValidationStrategy validationStrategy(); + + @Nullable + String getTeiUid(); + + @Nullable + String getEnrollmentUid(); } } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java index c7b9f31d8d..cfc80799f2 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureModule.java @@ -42,9 +42,12 @@ public class EventCaptureModule { private final String eventUid; private final EventCaptureContract.View view; - public EventCaptureModule(EventCaptureContract.View view, String eventUid) { + private final boolean isPortrait; + + public EventCaptureModule(EventCaptureContract.View view, String eventUid, boolean isPortrait) { this.view = view; this.eventUid = eventUid; + this.isPortrait = isPortrait; } @Provides @@ -138,7 +141,7 @@ FlowableProcessor getProcessor() { NavigationPageConfigurator pageConfigurator( EventCaptureContract.EventCaptureRepository repository ) { - return new EventPageConfigurator(repository); + return new EventPageConfigurator(repository, isPortrait); } @Provides diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePagerAdapter.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePagerAdapter.java index e8e044d83e..60841ee589 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePagerAdapter.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePagerAdapter.java @@ -26,14 +26,19 @@ public class EventCapturePagerAdapter extends FragmentStateAdapter { private final String programUid; private final String eventUid; - private final List pages; + private final List landscapePages; + private final List portraitPages; + + private final FragmentActivity fragmentActivity; private final boolean shouldOpenErrorSection; private final EventMode eventMode; + public static final int NO_POSITION = -1; + public boolean isFormScreenShown(@Nullable Integer currentItem) { - return currentItem != null && pages.get(currentItem) == EventPageType.DATA_ENTRY; + return currentItem != null && portraitPages.get(currentItem) == EventPageType.DATA_ENTRY; } private enum EventPageType { @@ -54,50 +59,64 @@ public EventCapturePagerAdapter(FragmentActivity fragmentActivity, this.eventUid = eventUid; this.shouldOpenErrorSection = openErrorSection; this.eventMode = eventMode; - pages = new ArrayList<>(); - pages.add(EventPageType.DATA_ENTRY); + this.fragmentActivity = fragmentActivity; + landscapePages = new ArrayList<>(); + portraitPages = new ArrayList<>(); + + portraitPages.add(EventPageType.DATA_ENTRY); if (displayAnalyticScreen) { - pages.add(EventPageType.ANALYTICS); + portraitPages.add(EventPageType.ANALYTICS); + landscapePages.add(EventPageType.ANALYTICS); } if (displayRelationshipScreen) { - pages.add(EventPageType.RELATIONSHIPS); + portraitPages.add(EventPageType.RELATIONSHIPS); + landscapePages.add(EventPageType.RELATIONSHIPS); } - pages.add(EventPageType.NOTES); + portraitPages.add(EventPageType.NOTES); + landscapePages.add(EventPageType.NOTES); } public int getDynamicTabIndex(@IntegerRes int tabClicked) { - if (tabClicked == R.id.navigation_data_entry) { - return pages.indexOf(EventPageType.DATA_ENTRY); - } else if (tabClicked == R.id.navigation_analytics) { - return pages.indexOf(EventPageType.ANALYTICS); - } else if (tabClicked == R.id.navigation_relationships) { - return pages.indexOf(EventPageType.RELATIONSHIPS); - } else if (tabClicked == R.id.navigation_notes) { - return pages.indexOf(EventPageType.NOTES); + EventPageType pageType = switch (tabClicked) { + case R.id.navigation_analytics -> EventPageType.ANALYTICS; + case R.id.navigation_relationships -> EventPageType.RELATIONSHIPS; + case R.id.navigation_notes -> EventPageType.NOTES; + default -> null; + }; + + if (pageType != null) { + if (isPortrait()) { + return portraitPages.indexOf(pageType); + } else { + return landscapePages.indexOf(pageType); + } + } else { + return NO_POSITION; } - return 0; } @NonNull @Override public Fragment createFragment(int position) { - switch (pages.get(position)) { - default: - case DATA_ENTRY: - return EventCaptureFormFragment.newInstance( - eventUid, - shouldOpenErrorSection, - eventMode - ); - case ANALYTICS: + return createFragmentForPage( + isPortrait() ? + portraitPages.get(position) : + landscapePages.get(position) + ); + } + + private Fragment createFragmentForPage(EventPageType pageType) { + switch (pageType) { + case ANALYTICS -> { Fragment indicatorFragment = new IndicatorsFragment(); Bundle arguments = new Bundle(); arguments.putString(VISUALIZATION_TYPE, VisualizationType.EVENTS.name()); indicatorFragment.setArguments(arguments); return indicatorFragment; - case RELATIONSHIPS: + } + case RELATIONSHIPS -> { Fragment relationshipFragment = new RelationshipFragment(); relationshipFragment.setArguments( RelationshipFragment.withArguments(programUid, @@ -107,13 +126,31 @@ public Fragment createFragment(int position) { ) ); return relationshipFragment; - case NOTES: + } + case NOTES -> { return NotesFragment.newEventInstance(programUid, eventUid); + } + default -> { + return EventCaptureFormFragment.newInstance( + eventUid, + shouldOpenErrorSection, + eventMode + ); + } } + } @Override public int getItemCount() { - return pages.size(); + if (isPortrait()) { + return portraitPages.size(); + } else { + return landscapePages.size(); + } + } + + public boolean isPortrait() { + return fragmentActivity.getResources().getConfiguration().orientation == 1; } } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt index 4ab039b60f..51c2072858 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCapturePresenterImpl.kt @@ -280,4 +280,11 @@ class EventCapturePresenterImpl( get() = eventCaptureRepository.eventStatus().blockingFirst() override fun programStage(): String = eventCaptureRepository.programStage().blockingFirst() + + override fun getEnrollmentUid(): String? { + return eventCaptureRepository.enrollmentUid + } + override fun getTeiUid(): String? { + return eventCaptureRepository.teiUid + } } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java index a357042c2f..7111c4bf5f 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventCaptureRepositoryImpl.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; + import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.Single; @@ -231,5 +233,18 @@ public ValidationStrategy validationStrategy() { return validationStrategy != null ? validationStrategy : ValidationStrategy.ON_COMPLETE; } + + @Override + @Nullable + public String getEnrollmentUid() { + return getCurrentEvent().enrollment(); + } + + @Override + @Nullable + public String getTeiUid() { + Enrollment enrollment = d2.enrollmentModule().enrollments().uid(getEnrollmentUid()).blockingGet(); + return enrollment != null ? enrollment.trackedEntityInstance() : null; + } } diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt index ed3d15822f..2f4a9dcdc8 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/EventPageConfigurator.kt @@ -4,13 +4,14 @@ import org.dhis2.utils.customviews.navigationbar.NavigationPageConfigurator class EventPageConfigurator( private val eventCaptureRepository: EventCaptureContract.EventCaptureRepository, + val isPortrait: Boolean, ) : NavigationPageConfigurator { override fun displayDetails(): Boolean { return true } override fun displayDataEntry(): Boolean { - return true + return isPortrait } override fun displayAnalytics(): Boolean { diff --git a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java index ce2d282d59..f3915a35b3 100644 --- a/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java +++ b/app/src/main/java/org/dhis2/usescases/eventsWithoutRegistration/eventCapture/eventCaptureFragment/EventCaptureFormFragment.java @@ -71,7 +71,6 @@ public void onAttach(@NotNull Context context) { this, getArguments().getString(Constants.EVENT_UID)) ).inject(this); - setRetainInstance(true); } @Override diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt index 7287ae0068..4c05023cda 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/DashboardViewModel.kt @@ -27,6 +27,8 @@ class DashboardViewModel( private val eventUid = MutableLiveData() + private val selectedEventUid = MutableLiveData() + val updateEnrollment = MutableLiveData(false) val showStatusErrorMessages = MutableLiveData(StatusChangeResultCode.CHANGED) @@ -169,4 +171,14 @@ class DashboardViewModel( } } } + + fun selectedEventUid(): LiveData { + return selectedEventUid + } + + fun updateSelectedEventUid(uid: String?) { + if (selectedEventUid.value != uid) { + this.selectedEventUid.value = uid + } + } } diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardComponent.java b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardComponent.java index 50c9278d86..82a0bc0cca 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardComponent.java +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardComponent.java @@ -29,5 +29,7 @@ public interface TeiDashboardComponent { @NonNull TEIDataComponent plus(TEIDataModule teiDataModule); + DashboardViewModelFactory dashboardViewModelFactory(); + void inject(TeiDashboardMobileActivity mobileActivity); } diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt index cf3251fc02..c16f884432 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/TeiDashboardMobileActivity.kt @@ -20,19 +20,27 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModelProvider import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.snackbar.Snackbar import org.dhis2.App import org.dhis2.R import org.dhis2.commons.Constants import org.dhis2.commons.Constants.TEI_UID +import org.dhis2.commons.featureconfig.data.FeatureConfigRepository +import org.dhis2.commons.featureconfig.model.Feature import org.dhis2.commons.filters.FilterManager import org.dhis2.commons.filters.Filters import org.dhis2.commons.network.NetworkUtils import org.dhis2.commons.popupmenu.AppMenuHelper +import org.dhis2.commons.resources.ColorUtils import org.dhis2.commons.resources.ResourceManager import org.dhis2.commons.sync.OnDismissListener import org.dhis2.commons.sync.SyncContext import org.dhis2.databinding.ActivityDashboardMobileBinding +import org.dhis2.form.model.EnrollmentMode +import org.dhis2.form.model.EnrollmentRecords +import org.dhis2.form.ui.FormView +import org.dhis2.form.ui.provider.EnrollmentResultDialogUiProvider import org.dhis2.ui.ThemeManager import org.dhis2.ui.dialogs.bottomsheet.DeleteBottomSheetDialog import org.dhis2.usescases.enrollment.EnrollmentActivity @@ -43,6 +51,7 @@ import org.dhis2.usescases.teiDashboard.adapters.DashboardPagerAdapter import org.dhis2.usescases.teiDashboard.adapters.DashboardPagerAdapter.Companion.NO_POSITION import org.dhis2.usescases.teiDashboard.adapters.DashboardPagerAdapter.DashboardPageType import org.dhis2.usescases.teiDashboard.dashboardfragments.relationships.MapButtonObservable +import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataActivityContract import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataFragment.Companion.newInstance import org.dhis2.usescases.teiDashboard.teiProgramList.TeiProgramListActivity import org.dhis2.usescases.teiDashboard.ui.setButtonContent @@ -63,12 +72,16 @@ import javax.inject.Inject class TeiDashboardMobileActivity : ActivityGlobalAbstract(), TeiDashboardContracts.View, - MapButtonObservable { + MapButtonObservable, + TEIDataActivityContract { private var currentOrientation = -1 @Inject lateinit var presenter: TeiDashboardContracts.Presenter + var featureConfig: FeatureConfigRepository? = null + @Inject set + @Inject lateinit var filterManager: FilterManager @@ -101,6 +114,8 @@ class TeiDashboardMobileActivity : private var elevation = 0f private var restartingActivity = false + private lateinit var formView: FormView + private val detailsLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult(), ) { @@ -189,6 +204,7 @@ class TeiDashboardMobileActivity : if (intent.shouldLaunchSyncDialog()) { openSyncDialog() } + setFormViewForLandScape() setNavigationBar() setEditButton() dashboardViewModel.showStatusErrorMessages.observe(this) { @@ -202,6 +218,47 @@ class TeiDashboardMobileActivity : } } + private fun setFormViewForLandScape() { + if (isLandscape() && enrollmentUid != null) { + val saveButton = findViewById(R.id.saveLand) as FloatingActionButton + formView = FormView.Builder() + .locationProvider(locationProvider) + .onItemChangeListener { + // Do nothing + } + .onLoadingListener { loading -> + if (loading) { + showLoadingProgress(true) + } else { + showLoadingProgress(false) + } + } + .onFinishDataEntry { + dashboardViewModel.updateDashboard() + } + .resultDialogUiProvider( + EnrollmentResultDialogUiProvider( + ResourceManager( + this.context, + ColorUtils(), + ), + ), + ) + .factory(supportFragmentManager) + .setRecords(EnrollmentRecords(enrollmentUid!!, EnrollmentMode.NEW)) + .useComposeForm( + featureConfig?.isFeatureEnable(Feature.COMPOSE_FORMS) ?: false, + ) + .build() + + supportFragmentManager.beginTransaction() + .replace(R.id.tei_form_view, formView) + .commitAllowingStateLoss() + + saveButton.setOnClickListener { formView.onSaveClick() } + } + } + private fun setEditButton() { binding.editButton.setButtonContent(presenter.teType) { enrollmentUid?.let { enrollmentUid -> @@ -272,7 +329,7 @@ class TeiDashboardMobileActivity : super.onDestroy() } - fun openSyncDialog() { + override fun openSyncDialog() { enrollmentUid?.let { enrollmentUid -> SyncStatusDialog.Builder() .withContext(this, null) @@ -339,12 +396,12 @@ class TeiDashboardMobileActivity : binding.relationshipMapIcon.visibility = View.GONE } if (pageType == DashboardPageType.TEI_DETAIL && programUid != null) { - binding.toolbarTitle.visibility = View.GONE - binding.editButton.visibility = View.VISIBLE + binding.toolbarTitle?.visibility = View.GONE + binding.editButton?.visibility = View.VISIBLE binding.syncButton.visibility = View.GONE } else { - binding.toolbarTitle.visibility = View.VISIBLE - binding.editButton.visibility = View.GONE + binding.toolbarTitle?.visibility = View.VISIBLE + binding.editButton?.visibility = View.GONE binding.syncButton.visibility = View.VISIBLE } binding.navigationBar.selectItemAt(position) @@ -719,6 +776,35 @@ class TeiDashboardMobileActivity : startActivity(intent) } + override fun finishActivity() { + finish() + } + + override fun restoreAdapter(programUid: String, teiUid: String, enrollmentUid: String) { + startActivity( + intent( + this, + teiUid, + programUid, + enrollmentUid, + ), + ) + } + + override fun executeOnUIThread() { + activity.runOnUiThread { + showDescription(getString(R.string.error_applying_rule_effects)) + } + } + + override fun getContext(): Context { + return this + } + + override fun activityTeiUid(): String? { + return teiUid + } + companion object { private const val TEI_SYNC = "SYNC_TEI" diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataActivityContract.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataActivityContract.kt new file mode 100644 index 0000000000..e689ee3855 --- /dev/null +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataActivityContract.kt @@ -0,0 +1,12 @@ +package org.dhis2.usescases.teiDashboard.dashboardfragments.teidata + +import android.content.Context + +interface TEIDataActivityContract { + fun restoreAdapter(programUid: String, teiUid: String, enrollmentUid: String) + fun finishActivity() + fun openSyncDialog() + fun executeOnUIThread() + fun getContext(): Context + fun activityTeiUid(): String? +} 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 726826a4ef..7753421b8c 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 @@ -99,7 +99,7 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { private val followUp = ObservableBoolean(false) private var eventCatComboOptionSelector: EventCatComboOptionSelector? = null private val dashboardViewModel: DashboardViewModel by activityViewModels() - private val dashboardActivity: TeiDashboardMobileActivity by lazy { context as TeiDashboardMobileActivity } + private val dashboardActivity: TEIDataActivityContract by lazy { context as TEIDataActivityContract } override fun onAttach(context: Context) { super.onAttach(context) @@ -223,8 +223,8 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { programsCallback = { startActivity( TeiDashboardMobileActivity.intent( - dashboardActivity.context, - dashboardActivity.teiUid, + dashboardActivity.getContext(), + dashboardActivity.activityTeiUid(), null, null, ), @@ -268,6 +268,7 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { !dashboardModel?.teiHeader.isNullOrEmpty() -> { dashboardModel?.teiHeader } + else -> { String.format( "%s %s", @@ -344,6 +345,7 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { currentProgram, colorUtils, cardMapper, + initialSelectedEventUid = dashboardViewModel.selectedEventUid().value, ) binding.teiRecycler.adapter = eventAdapter } @@ -463,15 +465,8 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { } override fun restoreAdapter(programUid: String, teiUid: String, enrollmentUid: String) { - dashboardActivity.startActivity( - TeiDashboardMobileActivity.intent( - activity, - teiUid, - programUid, - enrollmentUid, - ), - ) - dashboardActivity.finish() + dashboardActivity.restoreAdapter(programUid, teiUid, enrollmentUid) + dashboardActivity.finishActivity() } override fun openEventDetails(intent: Intent, options: ActivityOptionsCompat) = @@ -484,10 +479,17 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { updateEnrollment(true) } - override fun openEventCapture(intent: Intent) = - contractHandler.editEvent(intent).observe(this.viewLifecycleOwner) { - updateEnrollment(true) + override fun openEventCapture(intent: Intent) { + if (dashboardActivity is TeiDashboardMobileActivity) { + contractHandler.editEvent(intent).observe(this.viewLifecycleOwner) { + updateEnrollment(true) + } + } + if (dashboardActivity is EventCaptureActivity) { + val selectedEventUid = intent.getStringExtra(Constants.EVENT_UID) + dashboardViewModel.updateSelectedEventUid(selectedEventUid) } + } override fun goToEventInitial( eventCreationType: EventCreationType, @@ -580,9 +582,7 @@ class TEIDataFragment : FragmentGlobalAbstract(), TEIDataContracts.View { } override fun showProgramRuleErrorMessage() { - dashboardActivity.runOnUiThread { - showDescription(getString(R.string.error_applying_rule_effects)) - } + dashboardActivity.executeOnUIThread() } override fun updateEnrollment(update: Boolean) { diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataPresenter.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataPresenter.kt index 6bd28f5b9b..7bd0464cfc 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataPresenter.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/TEIDataPresenter.kt @@ -138,7 +138,10 @@ class TEIDataPresenter( .subscribe( { events -> _events.postValue(events) - _totalTimeLineEvents.postValue(events.firstOrNull()?.eventCount ?: 0) + _totalTimeLineEvents.postValue( + events.firstOrNull()?.eventCount + ?: 0, + ) decrement() }, Timber.Forest::d, diff --git a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/teievents/EventAdapter.kt b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/teievents/EventAdapter.kt index 8ba78e7fdc..7e7313945b 100644 --- a/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/teievents/EventAdapter.kt +++ b/app/src/main/java/org/dhis2/usescases/teiDashboard/dashboardfragments/teidata/teievents/EventAdapter.kt @@ -3,8 +3,10 @@ package org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.teievents import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.LocalTextStyle import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView @@ -29,12 +31,15 @@ import org.dhis2.commons.resources.ColorUtils import org.dhis2.databinding.ItemEventBinding import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.TEIDataPresenter import org.dhis2.usescases.teiDashboard.dashboardfragments.teidata.teievents.ui.mapper.TEIEventCardMapper +import org.dhis2.utils.isLandscape import org.hisp.dhis.android.core.event.EventStatus import org.hisp.dhis.android.core.program.Program import org.hisp.dhis.mobile.ui.designsystem.component.ListCard import org.hisp.dhis.mobile.ui.designsystem.component.ListCardDescriptionModel import org.hisp.dhis.mobile.ui.designsystem.component.ListCardTitleModel +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 import org.hisp.dhis.mobile.ui.designsystem.theme.TextColor class EventAdapter( @@ -42,6 +47,7 @@ class EventAdapter( val program: Program, val colorUtils: ColorUtils, private val cardMapper: TEIEventCardMapper, + private val initialSelectedEventUid: String? = null, ) : ListAdapter( object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: EventViewModel, newItem: EventViewModel): Boolean { @@ -66,6 +72,8 @@ class EventAdapter( private var stageSelector: FlowableProcessor = PublishProcessor.create() + private var previousSelectedPosition: Int = RecyclerView.NO_POSITION + fun stageSelector(): Flowable { return stageSelector } @@ -151,11 +159,26 @@ class EventAdapter( event.uid(), event.status()!!, ) + + if (isLandscape()) { + if (previousSelectedPosition != RecyclerView.NO_POSITION) { + currentList[previousSelectedPosition].isClicked = false + notifyItemChanged(previousSelectedPosition) + } + previousSelectedPosition = position + getItem(position).isClicked = true + notifyItemChanged(position) + } } } } }, ) + + if (it.event?.uid() == initialSelectedEventUid && previousSelectedPosition == RecyclerView.NO_POSITION) { + it.isClicked = true + previousSelectedPosition = position + } Box( modifier = Modifier .padding( @@ -185,6 +208,13 @@ class EventAdapter( shrinkLabelText = card.shrinkLabelText, onCardClick = card.onCardCLick, ) + if (it.isClicked) { + Box( + modifier = Modifier + .matchParentSize() + .background(color = SurfaceColor.Primary.copy(alpha = 0.1f), shape = RoundedCornerShape(Radius.S)), + ) + } } } diff --git a/app/src/main/res/layout-land/activity_dashboard_mobile.xml b/app/src/main/res/layout-land/activity_dashboard_mobile.xml index c86361c3fa..9e8320ffe8 100644 --- a/app/src/main/res/layout-land/activity_dashboard_mobile.xml +++ b/app/src/main/res/layout-land/activity_dashboard_mobile.xml @@ -5,10 +5,6 @@ - - @@ -38,30 +34,6 @@ app:srcCompat="@drawable/ic_arrow_back" tools:ignore="ContentDescription" /> - - - - + tools:ignore="ContentDescription" + tools:visibility="visible" /> - - - - - - - + app:layout_constraintTop_toBottomOf="@id/toolbar"> - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_event_capture.xml b/app/src/main/res/layout-land/activity_event_capture.xml new file mode 100644 index 0000000000..ed0da89ffa --- /dev/null +++ b/app/src/main/res/layout-land/activity_event_capture.xml @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/commons/src/main/java/org/dhis2/commons/data/EventViewModel.kt b/commons/src/main/java/org/dhis2/commons/data/EventViewModel.kt index 10984e8923..d5d5869ac4 100644 --- a/commons/src/main/java/org/dhis2/commons/data/EventViewModel.kt +++ b/commons/src/main/java/org/dhis2/commons/data/EventViewModel.kt @@ -26,6 +26,7 @@ data class EventViewModel( val nameCategoryOptionCombo: String?, val metadataIconData: MetadataIconData, ) { + var isClicked: Boolean = false fun toggleValueList() { this.valueListIsOpen = !valueListIsOpen }