Skip to content
Snippets Groups Projects
Unverified Commit 7484a1b0 authored by Mohamed Metwalli's avatar Mohamed Metwalli Committed by GitHub
Browse files

Home screen navigation (DEV) (#3266)


* Refactoring

* Move rest to events

* Handle Nullable

Co-authored-by: default avatarchris-cwa <69595386+chris-cwa@users.noreply.github.com>
parent c2d73e2a
No related branches found
No related tags found
No related merge requests found
...@@ -92,7 +92,7 @@ class HomeFragmentTest : BaseUITest() { ...@@ -92,7 +92,7 @@ class HomeFragmentTest : BaseUITest() {
every { tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingActive) every { tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingActive)
every { showLoweredRiskLevelDialog } returns MutableLiveData() every { showLoweredRiskLevelDialog } returns MutableLiveData()
every { homeItems } returns homeFragmentItemsLiveData() every { homeItems } returns homeFragmentItemsLiveData()
every { popupEvents } returns SingleLiveEvent() every { events } returns SingleLiveEvent()
every { showPopUps() } just Runs every { showPopUps() } just Runs
every { restoreAppShortcuts() } just Runs every { restoreAppShortcuts() } just Runs
} }
......
...@@ -5,7 +5,6 @@ import android.os.Bundle ...@@ -5,7 +5,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityEvent
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.NavGraph
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.onNavDestinationSelected
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
...@@ -25,6 +24,7 @@ import de.rki.coronawarnapp.util.errors.RecoveryByResetDialogFactory ...@@ -25,6 +24,7 @@ import de.rki.coronawarnapp.util.errors.RecoveryByResetDialogFactory
import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator import de.rki.coronawarnapp.util.lists.decorations.TopBottomPaddingDecorator
import de.rki.coronawarnapp.util.lists.diffutil.update import de.rki.coronawarnapp.util.lists.diffutil.update
import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.doNavigate
import de.rki.coronawarnapp.util.ui.findNestedGraph
import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.viewBinding import de.rki.coronawarnapp.util.ui.viewBinding
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
...@@ -40,30 +40,23 @@ import javax.inject.Inject ...@@ -40,30 +40,23 @@ import javax.inject.Inject
class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject {
@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
private val viewModel: HomeFragmentViewModel by cwaViewModels( @Inject lateinit var tracingExplanationDialog: TracingExplanationDialog
@Inject lateinit var deviceTimeIncorrectDialog: DeviceTimeIncorrectDialog
private val viewModel by cwaViewModels<HomeFragmentViewModel>(
ownerProducer = { requireActivity().viewModelStore }, ownerProducer = { requireActivity().viewModelStore },
factoryProducer = { viewModelFactory } factoryProducer = { viewModelFactory }
) )
val binding: HomeFragmentLayoutBinding by viewBinding() private val binding by viewBinding<HomeFragmentLayoutBinding>()
@Inject lateinit var tracingExplanationDialog: TracingExplanationDialog
@Inject lateinit var deviceTimeIncorrectDialog: DeviceTimeIncorrectDialog
private val homeAdapter = HomeAdapter() private val homeAdapter = HomeAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
with(binding.toolbar) { with(binding.toolbar) {
menu.findItem(R.id.test_nav_graph).isVisible = CWADebug.isDeviceForTestersBuild menu.findItem(R.id.test_nav_graph).isVisible = CWADebug.isDeviceForTestersBuild
setOnMenuItemClickListener { it.onNavDestinationSelected(findNavController()) } setOnMenuItemClickListener { it.onNavDestinationSelected(findNavController()) }
} }
viewModel.tracingHeaderState.observe2(this) {
binding.tracingHeader = it
}
binding.recyclerView.apply { binding.recyclerView.apply {
layoutManager = LinearLayoutManager(requireContext()) layoutManager = LinearLayoutManager(requireContext())
itemAnimator = DefaultItemAnimator() itemAnimator = DefaultItemAnimator()
...@@ -71,84 +64,19 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { ...@@ -71,84 +64,19 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject {
adapter = homeAdapter adapter = homeAdapter
} }
viewModel.homeItems.observe2(this) {
homeAdapter.update(it)
}
viewModel.routeToScreen.observe2(this) {
doNavigate(it)
}
binding.mainTracing.setOnClickListener { binding.mainTracing.setOnClickListener {
doNavigate(HomeFragmentDirections.actionMainFragmentToSettingsTracingFragment()) doNavigate(HomeFragmentDirections.actionMainFragmentToSettingsTracingFragment())
} }
viewModel.openFAQUrlEvent.observe2(this) {
openUrl(getString(R.string.main_about_link))
}
viewModel.openIncompatibleEvent.observe2(this) {
openUrl(
getString(
when (it) { // true if scanning is supported
true -> R.string.incompatible_link_advertising_not_supported
else -> R.string.incompatible_link_scanning_not_supported
}
)
)
}
viewModel.openTraceLocationOrganizerFlow.observe2(this) {
if (viewModel.wasQRInfoWasAcknowledged()) {
val nestedGraph =
findNavController().graph.findNode(R.id.trace_location_organizer_nav_graph) as NavGraph
nestedGraph.startDestination = R.id.traceLocationsFragment
}
doNavigate(HomeFragmentDirections.actionMainFragmentToTraceLocationOrganizerNavGraph())
}
viewModel.openVaccinationRegistrationFlow.observe2(this) {
if (viewModel.wasVaccinationRegistrationAcknowledged()) {
val nestedGraph =
findNavController().graph.findNode(R.id.vaccination_nav_graph) as NavGraph
nestedGraph.startDestination = R.id.vaccinationQrCodeScanFragment
}
doNavigate(HomeFragmentDirections.actionMainFragmentToVaccinationNavGraph())
}
viewModel.popupEvents.observe2(this) { event ->
when (event) {
HomeFragmentEvents.ShowErrorResetDialog -> {
RecoveryByResetDialogFactory(this).showDialog(
detailsLink = R.string.errors_generic_text_catastrophic_error_encryption_failure,
onPositive = { viewModel.errorResetDialogDismissed() }
)
}
is HomeFragmentEvents.ShowDeleteTestDialog -> showRemoveTestDialog(event.type)
HomeFragmentEvents.GoToStatisticsExplanation -> doNavigate(
HomeFragmentDirections.actionMainFragmentToStatisticsExplanationFragment()
)
HomeFragmentEvents.ShowTracingExplanation -> {
tracingExplanationDialog.show {
viewModel.tracingExplanationWasShown()
}
}
is HomeFragmentEvents.GoToVaccinationList -> findNavController().navigate(
VaccinationListFragment.navigationUri(event.personIdentifierCodeSha256)
)
}
}
viewModel.showPopUps() viewModel.showPopUps()
viewModel.events.observe2(this) { event -> event?.let { navigate(event) } }
viewModel.showLoweredRiskLevelDialog.observe2(this) { viewModel.homeItems.observe2(this) { homeAdapter.update(it) }
if (it) showRiskLevelLoweredDialog() viewModel.errorEvent.observe2(this) { it.toErrorDialogBuilder(requireContext()).show() }
} viewModel.tracingHeaderState.observe2(this) { binding.tracingHeader = it }
viewModel.showLoweredRiskLevelDialog.observe2(this) { if (it) showRiskLevelLoweredDialog() }
viewModel.showIncorrectDeviceTimeDialog.observe2(this) { showDialog -> viewModel.showIncorrectDeviceTimeDialog.observe2(this) { showDialog ->
if (!showDialog) return@observe2 if (showDialog) deviceTimeIncorrectDialog.show { viewModel.userHasAcknowledgedIncorrectDeviceTime() }
deviceTimeIncorrectDialog.show { viewModel.userHasAcknowledgedIncorrectDeviceTime() }
} }
viewModel.coronaTestErrors.observe2(this) { tests -> viewModel.coronaTestErrors.observe2(this) { tests ->
tests.forEach { test -> tests.forEach { test ->
test.lastError?.toErrorDialogBuilder(requireContext())?.apply { test.lastError?.toErrorDialogBuilder(requireContext())?.apply {
...@@ -160,10 +88,6 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { ...@@ -160,10 +88,6 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject {
}?.show() }?.show()
} }
} }
viewModel.errorEvent.observe2(this) {
it.toErrorDialogBuilder(requireContext()).show()
}
} }
override fun onResume() { override fun onResume() {
...@@ -205,4 +129,75 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { ...@@ -205,4 +129,75 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject {
getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(context.getColorCompat(R.color.colorTextTint)) getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(context.getColorCompat(R.color.colorTextTint))
} }
} }
private fun navigate(event: HomeFragmentEvents) {
when (event) {
HomeFragmentEvents.ShowErrorResetDialog -> {
RecoveryByResetDialogFactory(this).showDialog(
detailsLink = R.string.errors_generic_text_catastrophic_error_encryption_failure,
onPositive = { viewModel.errorResetDialogDismissed() }
)
}
HomeFragmentEvents.GoToStatisticsExplanation -> doNavigate(
HomeFragmentDirections.actionMainFragmentToStatisticsExplanationFragment()
)
HomeFragmentEvents.ShowTracingExplanation -> tracingExplanationDialog.show {
viewModel.tracingExplanationWasShown()
}
HomeFragmentEvents.GoToRiskDetailsFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment()
)
HomeFragmentEvents.GoToSettingsTracingFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSettingsTracingFragment()
)
HomeFragmentEvents.GoToSubmissionDispatcher -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher()
)
HomeFragmentEvents.OpenFAQUrl -> openUrl(getString(R.string.main_about_link))
HomeFragmentEvents.GoToRapidTestResultNegativeFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionNegativeAntigenTestResultFragment()
)
is HomeFragmentEvents.ShowDeleteTestDialog -> showRemoveTestDialog(event.type)
is HomeFragmentEvents.OpenIncompatibleUrl -> openUrl(getString(event.url))
is HomeFragmentEvents.OpenVaccinationRegistrationGraph -> openVaccinationGraph(event)
is HomeFragmentEvents.OpenTraceLocationOrganizerGraph -> openPresenceTracingOrganizerGraph(event)
is HomeFragmentEvents.GoToTestResultAvailableFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment(event.type)
)
is HomeFragmentEvents.GoToPcrTestResultNegativeFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionTestResultNegativeFragment(event.type)
)
is HomeFragmentEvents.GoToTestResultKeysSharedFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionTestResultKeysSharedFragment(event.type)
)
is HomeFragmentEvents.GoToVaccinationList -> findNavController().navigate(
VaccinationListFragment.navigationUri(event.personIdentifierCodeSha256)
)
is HomeFragmentEvents.GoToTestResultPositiveFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment(
event.type
)
)
is HomeFragmentEvents.GoToTestResultPendingFragment -> doNavigate(
HomeFragmentDirections.actionMainFragmentToSubmissionTestResultPendingFragment(
event.testType,
event.forceUpdate
)
)
}
}
private fun openPresenceTracingOrganizerGraph(event: HomeFragmentEvents.OpenTraceLocationOrganizerGraph) {
if (event.qrInfoAcknowledged) {
findNestedGraph(R.id.trace_location_organizer_nav_graph).startDestination = R.id.traceLocationsFragment
}
doNavigate(HomeFragmentDirections.actionMainFragmentToTraceLocationOrganizerNavGraph())
}
private fun openVaccinationGraph(event: HomeFragmentEvents.OpenVaccinationRegistrationGraph) {
if (event.registrationAcknowledged) {
findNestedGraph(R.id.vaccination_nav_graph).startDestination = R.id.vaccinationQrCodeScanFragment
}
doNavigate(HomeFragmentDirections.actionMainFragmentToVaccinationNavGraph())
}
} }
package de.rki.coronawarnapp.ui.main.home package de.rki.coronawarnapp.ui.main.home
import androidx.annotation.StringRes
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTest
sealed class HomeFragmentEvents { sealed class HomeFragmentEvents {
...@@ -10,7 +12,43 @@ sealed class HomeFragmentEvents { ...@@ -10,7 +12,43 @@ sealed class HomeFragmentEvents {
object GoToStatisticsExplanation : HomeFragmentEvents() object GoToStatisticsExplanation : HomeFragmentEvents()
object GoToRiskDetailsFragment : HomeFragmentEvents()
object GoToSettingsTracingFragment : HomeFragmentEvents()
object GoToSubmissionDispatcher : HomeFragmentEvents()
object OpenFAQUrl : HomeFragmentEvents()
object GoToRapidTestResultNegativeFragment : HomeFragmentEvents()
data class GoToPcrTestResultNegativeFragment(val type: CoronaTest.Type) : HomeFragmentEvents()
data class GoToTestResultKeysSharedFragment(val type: CoronaTest.Type) : HomeFragmentEvents()
data class OpenIncompatibleUrl(val scanningSupported: Boolean) : HomeFragmentEvents() {
@get:StringRes
val url: Int
get() = when {
scanningSupported -> R.string.incompatible_link_advertising_not_supported
else -> R.string.incompatible_link_scanning_not_supported
}
}
data class OpenVaccinationRegistrationGraph(val registrationAcknowledged: Boolean) : HomeFragmentEvents()
data class OpenTraceLocationOrganizerGraph(val qrInfoAcknowledged: Boolean) : HomeFragmentEvents()
data class GoToTestResultPendingFragment(
val testType: CoronaTest.Type,
val forceUpdate: Boolean = false
) : HomeFragmentEvents()
data class ShowDeleteTestDialog(val type: CoronaTest.Type) : HomeFragmentEvents() data class ShowDeleteTestDialog(val type: CoronaTest.Type) : HomeFragmentEvents()
data class GoToTestResultAvailableFragment(val type: CoronaTest.Type) : HomeFragmentEvents()
data class GoToTestResultPositiveFragment(val type: CoronaTest.Type) : HomeFragmentEvents()
data class GoToVaccinationList(val personIdentifierCodeSha256: String) : HomeFragmentEvents() data class GoToVaccinationList(val personIdentifierCodeSha256: String) : HomeFragmentEvents()
} }
...@@ -7,10 +7,12 @@ import androidx.fragment.app.FragmentContainerView ...@@ -7,10 +7,12 @@ import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.NavGraph
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import de.rki.coronawarnapp.ui.doNavigate import de.rki.coronawarnapp.ui.doNavigate
import timber.log.Timber import timber.log.Timber
import java.lang.IllegalArgumentException
fun Fragment.doNavigate(direction: NavDirections) = findNavController().doNavigate(direction) fun Fragment.doNavigate(direction: NavDirections) = findNavController().doNavigate(direction)
...@@ -35,3 +37,13 @@ fun FragmentManager.findNavController(@IdRes id: Int): NavController { ...@@ -35,3 +37,13 @@ fun FragmentManager.findNavController(@IdRes id: Int): NavController {
val fragment = findFragmentById(id) ?: throw IllegalStateException("Fragment is not found for id:$id") val fragment = findFragmentById(id) ?: throw IllegalStateException("Fragment is not found for id:$id")
return NavHostFragment.findNavController(fragment) return NavHostFragment.findNavController(fragment)
} }
/**
* Finds nested graph [NavGraph] by Id.
* @param nestedGraphId
* @throws IllegalArgumentException if graph not found
*/
fun Fragment.findNestedGraph(@IdRes nestedGraphId: Int): NavGraph {
return findNavController().graph.findNode(nestedGraphId) as? NavGraph
?: throw IllegalArgumentException("Nested graph with id=$nestedGraphId not found")
}
...@@ -312,6 +312,9 @@ ...@@ -312,6 +312,9 @@
android:name="de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragment" android:name="de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragment"
android:label="SubmissionTestResultPendingFragment" android:label="SubmissionTestResultPendingFragment"
tools:layout="@layout/fragment_submission_test_result_pending"> tools:layout="@layout/fragment_submission_test_result_pending">
<argument
android:name="testType"
app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" />
<argument <argument
android:name="forceTestResultUpdate" android:name="forceTestResultUpdate"
android:defaultValue="false" android:defaultValue="false"
...@@ -334,9 +337,6 @@ ...@@ -334,9 +337,6 @@
app:destination="@id/submissionTestResultAvailableFragment" app:destination="@id/submissionTestResultAvailableFragment"
app:popUpTo="@id/mainFragment" app:popUpTo="@id/mainFragment"
app:popUpToInclusive="false" /> app:popUpToInclusive="false" />
<argument
android:name="testType"
app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" />
<action <action
android:id="@+id/action_submissionTestResultPendingFragment_to_submissionNegativeAntigenTestResultFragment" android:id="@+id/action_submissionTestResultPendingFragment_to_submissionNegativeAntigenTestResultFragment"
app:destination="@id/submissionNegativeAntigenTestResultFragment" app:destination="@id/submissionNegativeAntigenTestResultFragment"
......
...@@ -169,7 +169,7 @@ class HomeFragmentViewModelTest : BaseTest() { ...@@ -169,7 +169,7 @@ class HomeFragmentViewModelTest : BaseTest() {
with(createInstance()) { with(createInstance()) {
showPopUps() showPopUps()
popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowErrorResetDialog events.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowErrorResetDialog
} }
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment