diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt index 03430513efd136f91f631d65df8dfbd2df483029..3099ffa1e939354cbfe2df416caf144abf34fbfb 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt @@ -92,7 +92,7 @@ class HomeFragmentTest : BaseUITest() { every { tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingActive) every { showLoweredRiskLevelDialog } returns MutableLiveData() every { homeItems } returns homeFragmentItemsLiveData() - every { popupEvents } returns SingleLiveEvent() + every { events } returns SingleLiveEvent() every { showPopUps() } just Runs every { restoreAppShortcuts() } just Runs } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt index 86ed99b07f2de4307fd264362a46bbd4c5800ad3..beb511eae8d709ac0fb8ab3f2938559fc1c9d06d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt @@ -5,7 +5,6 @@ import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.fragment.app.Fragment -import androidx.navigation.NavGraph import androidx.navigation.fragment.findNavController import androidx.navigation.ui.onNavDestinationSelected import androidx.recyclerview.widget.DefaultItemAnimator @@ -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.diffutil.update 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.viewBinding import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider @@ -40,30 +40,23 @@ import javax.inject.Inject class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { @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 }, factoryProducer = { viewModelFactory } ) - val binding: HomeFragmentLayoutBinding by viewBinding() - - @Inject lateinit var tracingExplanationDialog: TracingExplanationDialog - @Inject lateinit var deviceTimeIncorrectDialog: DeviceTimeIncorrectDialog - + private val binding by viewBinding<HomeFragmentLayoutBinding>() private val homeAdapter = HomeAdapter() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - with(binding.toolbar) { menu.findItem(R.id.test_nav_graph).isVisible = CWADebug.isDeviceForTestersBuild setOnMenuItemClickListener { it.onNavDestinationSelected(findNavController()) } } - viewModel.tracingHeaderState.observe2(this) { - binding.tracingHeader = it - } - binding.recyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) itemAnimator = DefaultItemAnimator() @@ -71,84 +64,19 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { adapter = homeAdapter } - viewModel.homeItems.observe2(this) { - homeAdapter.update(it) - } - - viewModel.routeToScreen.observe2(this) { - doNavigate(it) - } - binding.mainTracing.setOnClickListener { 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.showLoweredRiskLevelDialog.observe2(this) { - if (it) showRiskLevelLoweredDialog() - } + viewModel.events.observe2(this) { event -> event?.let { navigate(event) } } + viewModel.homeItems.observe2(this) { homeAdapter.update(it) } + 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 -> - if (!showDialog) return@observe2 - deviceTimeIncorrectDialog.show { viewModel.userHasAcknowledgedIncorrectDeviceTime() } + if (showDialog) deviceTimeIncorrectDialog.show { viewModel.userHasAcknowledgedIncorrectDeviceTime() } } - viewModel.coronaTestErrors.observe2(this) { tests -> tests.forEach { test -> test.lastError?.toErrorDialogBuilder(requireContext())?.apply { @@ -160,10 +88,6 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { }?.show() } } - - viewModel.errorEvent.observe2(this) { - it.toErrorDialogBuilder(requireContext()).show() - } } override fun onResume() { @@ -205,4 +129,75 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { 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()) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt index 52e8e337966a600284fc84ae41a082e069ee3d8f..e458063333eb10098fc6997f12cba13d176d54d5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt @@ -1,5 +1,7 @@ package de.rki.coronawarnapp.ui.main.home +import androidx.annotation.StringRes +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.type.CoronaTest sealed class HomeFragmentEvents { @@ -10,7 +12,43 @@ sealed class 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 GoToTestResultAvailableFragment(val type: CoronaTest.Type) : HomeFragmentEvents() + + data class GoToTestResultPositiveFragment(val type: CoronaTest.Type) : HomeFragmentEvents() + data class GoToVaccinationList(val personIdentifierCodeSha256: String) : HomeFragmentEvents() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index 1e6c375b886d772f4bd8edddc792d5ea02dd3f01..5ce955fbad8805077527828b3fea610317ba76c7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.ui.main.home import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData -import androidx.navigation.NavDirections import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.appconfig.AppConfigProvider @@ -13,6 +12,8 @@ import de.rki.coronawarnapp.coronatest.latestPCRT import de.rki.coronawarnapp.coronatest.latestRAT import de.rki.coronawarnapp.coronatest.testErrorsSingleEvent import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR +import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR import de.rki.coronawarnapp.coronatest.type.pcr.toSubmissionState @@ -48,6 +49,7 @@ import de.rki.coronawarnapp.tracing.states.LowRisk import de.rki.coronawarnapp.tracing.states.TracingDisabled import de.rki.coronawarnapp.tracing.states.TracingFailed import de.rki.coronawarnapp.tracing.states.TracingInProgress +import de.rki.coronawarnapp.tracing.states.TracingState import de.rki.coronawarnapp.tracing.states.TracingStateProvider import de.rki.coronawarnapp.tracing.ui.homecards.IncreasedRiskCard import de.rki.coronawarnapp.tracing.ui.homecards.LowRiskCard @@ -86,52 +88,35 @@ import timber.log.Timber @Suppress("LongParameterList") class HomeFragmentViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, - private val errorResetTool: EncryptionErrorResetTool, tracingStatus: GeneralTracingStatus, tracingStateProviderFactory: TracingStateProvider.Factory, - private val coronaTestRepository: CoronaTestRepository, + coronaTestRepository: CoronaTestRepository, + statisticsProvider: StatisticsProvider, + vaccinationRepository: VaccinationRepository, + private val errorResetTool: EncryptionErrorResetTool, private val tracingRepository: TracingRepository, private val submissionRepository: SubmissionRepository, private val cwaSettings: CWASettings, private val appConfigProvider: AppConfigProvider, - statisticsProvider: StatisticsProvider, private val appShortcutsHelper: AppShortcutsHelper, private val tracingSettings: TracingSettings, private val traceLocationOrganizerSettings: TraceLocationOrganizerSettings, private val timeStamper: TimeStamper, private val bluetoothSupport: BluetoothSupport, private val vaccinationSettings: VaccinationSettings, - private val vaccinationRepository: VaccinationRepository, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + private var isLoweredRiskLevelDialogBeingShown = false private val tracingStateProvider by lazy { tracingStateProviderFactory.create(isDetailsMode = false) } + private val tracingCardItems = tracingStateProvider.state.map { tracingStateItem(it) }.distinctUntilChanged() - val routeToScreen = SingleLiveEvent<NavDirections>() - val openFAQUrlEvent = SingleLiveEvent<Unit>() - val openIncompatibleEvent = SingleLiveEvent<Boolean>() - val openTraceLocationOrganizerFlow = SingleLiveEvent<Unit>() - val openVaccinationRegistrationFlow = SingleLiveEvent<Unit>() val errorEvent = SingleLiveEvent<Throwable>() + val events = SingleLiveEvent<HomeFragmentEvents>() - val tracingHeaderState: LiveData<TracingHeaderState> = tracingStatus.generalStatus - .map { it.toHeaderState() } + val tracingHeaderState: LiveData<TracingHeaderState> = tracingStatus.generalStatus.map { it.toHeaderState() } .asLiveData(dispatcherProvider.Default) - - val popupEvents = SingleLiveEvent<HomeFragmentEvents>() - val coronaTestErrors = coronaTestRepository.testErrorsSingleEvent - .asLiveData(context = dispatcherProvider.Default) - - fun showPopUps() { - launch { - if (errorResetTool.isResetNoticeToBeShown) { - popupEvents.postValue(ShowErrorResetDialog) - } - if (!cwaSettings.wasTracingExplanationDialogShown) { - popupEvents.postValue(ShowTracingExplanation) - } - } - } + .asLiveData(dispatcherProvider.Default) val showIncorrectDeviceTimeDialog by lazy { var wasDeviceTimeDialogShown = false @@ -148,151 +133,6 @@ class HomeFragmentViewModel @AssistedInject constructor( } } - private val tracingCardItems = tracingStateProvider.state.map { tracingState -> - when (tracingState) { - is TracingInProgress -> TracingProgressCard.Item( - state = tracingState, - onCardClick = { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment()) - } - ) - is TracingDisabled -> TracingDisabledCard.Item( - state = tracingState, - onCardClick = { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment()) - }, - onEnableTracingClick = { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSettingsTracingFragment()) - } - ) - is LowRisk -> LowRiskCard.Item( - state = tracingState, - onCardClick = { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment()) - }, - onUpdateClick = { refreshRiskResult() } - ) - is IncreasedRisk -> IncreasedRiskCard.Item( - state = tracingState, - onCardClick = { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment()) - }, - onUpdateClick = { refreshRiskResult() } - ) - is TracingFailed -> TracingFailedCard.Item( - state = tracingState, - onCardClick = { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToRiskDetailsFragment()) - }, - onRetryClick = { refreshRiskResult() } - ) - } - }.distinctUntilChanged() - - private fun PCRCoronaTest?.toTestCardItem() = when (val state = this.toSubmissionState()) { - is SubmissionStatePCR.NoTest -> TestUnregisteredCard.Item(state) { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher()) - } - is SubmissionStatePCR.FetchingResult -> TestFetchingCard.Item(state) - is SubmissionStatePCR.TestResultReady -> PcrTestReadyCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment(CoronaTest.Type.PCR) - ) - } - is SubmissionStatePCR.TestPositive -> PcrTestPositiveCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment(CoronaTest.Type.PCR) - ) - } - is SubmissionStatePCR.TestNegative -> PcrTestNegativeCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections.actionMainFragmentToSubmissionTestResultNegativeFragment(CoronaTest.Type.PCR) - ) - } - is SubmissionStatePCR.TestInvalid -> PcrTestInvalidCard.Item(state) { - popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog(CoronaTest.Type.PCR)) - } - is SubmissionStatePCR.TestError -> PcrTestErrorCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultPendingFragment(testType = CoronaTest.Type.PCR) - ) - } - is SubmissionStatePCR.TestPending -> PcrTestPendingCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultPendingFragment( - testType = CoronaTest.Type.PCR, - forceTestResultUpdate = true - ) - ) - } - is SubmissionStatePCR.SubmissionDone -> PcrTestSubmissionDoneCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultKeysSharedFragment(CoronaTest.Type.PCR) - ) - } - } - - private fun RACoronaTest?.toTestCardItem(coronaTestConfig: CoronaTestConfig) = - when (val state = this.toSubmissionState(timeStamper.nowUTC, coronaTestConfig)) { - is SubmissionStateRAT.NoTest -> TestUnregisteredCard.Item(state) { - routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher()) - } - is SubmissionStateRAT.FetchingResult -> TestFetchingCard.Item(state) - is SubmissionStateRAT.TestResultReady -> RapidTestReadyCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultAvailableFragment(CoronaTest.Type.RAPID_ANTIGEN) - ) - } - is SubmissionStateRAT.TestPositive -> RapidTestPositiveCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment( - CoronaTest.Type.RAPID_ANTIGEN - ) - ) - } - is SubmissionStateRAT.TestNegative -> RapidTestNegativeCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionNegativeAntigenTestResultFragment() - ) - } - is SubmissionStateRAT.TestInvalid -> RapidTestInvalidCard.Item(state) { - popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog(CoronaTest.Type.RAPID_ANTIGEN)) - } - is SubmissionStateRAT.TestError -> RapidTestErrorCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultPendingFragment( - testType = CoronaTest.Type.RAPID_ANTIGEN - ) - ) - } - is SubmissionStateRAT.TestPending -> RapidTestPendingCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultPendingFragment( - testType = CoronaTest.Type.RAPID_ANTIGEN, - forceTestResultUpdate = true - ) - ) - } - is SubmissionStateRAT.TestOutdated -> RapidTestOutdatedCard.Item(state) { - submissionRepository.removeTestFromDevice(type = CoronaTest.Type.RAPID_ANTIGEN) - } - is SubmissionStateRAT.SubmissionDone -> RapidTestSubmissionDoneCard.Item(state) { - routeToScreen.postValue( - HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultKeysSharedFragment(CoronaTest.Type.RAPID_ANTIGEN) - ) - } - } - val homeItems: LiveData<List<HomeItem>> = combine( tracingCardItems, coronaTestRepository.latestPCRT, @@ -320,7 +160,7 @@ class HomeFragmentViewModel @AssistedInject constructor( VaccinatedPerson.Status.INCOMPLETE -> VaccinationHomeCard.Item( vaccinatedPerson = vaccinatedPerson, onClickAction = { - popupEvents.postValue( + events.postValue( HomeFragmentEvents.GoToVaccinationList(vaccinatedPerson.identifier.codeSHA256) ) } @@ -328,7 +168,7 @@ class HomeFragmentViewModel @AssistedInject constructor( VaccinatedPerson.Status.IMMUNITY -> ImmuneVaccinationHomeCard.Item( vaccinatedPerson = vaccinatedPerson, onClickAction = { - popupEvents.postValue( + events.postValue( HomeFragmentEvents.GoToVaccinationList(vaccinatedPerson.identifier.codeSHA256) ) } @@ -338,12 +178,10 @@ class HomeFragmentViewModel @AssistedInject constructor( } if (bluetoothSupport.isAdvertisingSupported == false) { - val scanningSupported = bluetoothSupport.isScanningSupported != false - add( IncompatibleCard.Item( - onClickAction = { openIncompatibleEvent.postValue(scanningSupported) }, + onClickAction = { events.postValue(HomeFragmentEvents.OpenIncompatibleUrl(scanningSupported)) }, bluetoothSupported = scanningSupported ) ) @@ -365,9 +203,7 @@ class HomeFragmentViewModel @AssistedInject constructor( add(testRAT.toTestCardItem(coronaTestParameters)) add( TestUnregisteredCard.Item(SubmissionStatePCR.NoTest) { - routeToScreen.postValue( - HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher() - ) + events.postValue(HomeFragmentEvents.GoToSubmissionDispatcher) } ) } else add(testRAT.toTestCardItem(coronaTestParameters)) @@ -377,7 +213,11 @@ class HomeFragmentViewModel @AssistedInject constructor( add( CreateVaccinationHomeCard.Item( onClickAction = { - openVaccinationRegistrationFlow.postValue(Unit) + events.postValue( + HomeFragmentEvents.OpenVaccinationRegistrationGraph( + vaccinationSettings.registrationAcknowledged + ) + ) } ) ) @@ -387,22 +227,30 @@ class HomeFragmentViewModel @AssistedInject constructor( StatisticsHomeCard.Item( data = statsData, onHelpAction = { - popupEvents.postValue(HomeFragmentEvents.GoToStatisticsExplanation) + events.postValue(HomeFragmentEvents.GoToStatisticsExplanation) } ) ) } - add(CreateTraceLocationCard.Item(onClickAction = { openTraceLocationOrganizerFlow.postValue(Unit) })) + add( + CreateTraceLocationCard.Item( + onClickAction = { + events.postValue( + HomeFragmentEvents.OpenTraceLocationOrganizerGraph( + traceLocationOrganizerSettings.qrInfoAcknowledged + ) + ) + } + ) + ) - add(FAQCard.Item(onClickAction = { openFAQUrlEvent.postValue(Unit) })) + add(FAQCard.Item(onClickAction = { events.postValue(HomeFragmentEvents.OpenFAQUrl) })) } } .distinctUntilChanged() .asLiveData(dispatcherProvider.Default) - private var isLoweredRiskLevelDialogBeingShown = false - // TODO only lazy to keep tests going which would break because of LocalData access val showLoweredRiskLevelDialog: LiveData<Boolean> by lazy { tracingSettings @@ -434,16 +282,17 @@ class HomeFragmentViewModel @AssistedInject constructor( } } + fun showPopUps() = launch { + if (errorResetTool.isResetNoticeToBeShown) events.postValue(ShowErrorResetDialog) + if (!cwaSettings.wasTracingExplanationDialogShown) events.postValue(ShowTracingExplanation) + } + fun restoreAppShortcuts() { launch { appShortcutsHelper.restoreAppShortcut() } } - private fun refreshRiskResult() { - tracingRepository.refreshRiskResult() - } - fun deregisterWarningAccepted(type: CoronaTest.Type) { submissionRepository.removeTestFromDevice(type) } @@ -461,9 +310,96 @@ class HomeFragmentViewModel @AssistedInject constructor( cwaSettings.wasTracingExplanationDialogShown = true } - fun wasQRInfoWasAcknowledged() = traceLocationOrganizerSettings.qrInfoAcknowledged + private fun PCRCoronaTest?.toTestCardItem() = when (val state = this.toSubmissionState()) { + is SubmissionStatePCR.NoTest -> TestUnregisteredCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToSubmissionDispatcher) + } + is SubmissionStatePCR.FetchingResult -> TestFetchingCard.Item(state) + is SubmissionStatePCR.TestResultReady -> PcrTestReadyCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultAvailableFragment(PCR)) + } + is SubmissionStatePCR.TestPositive -> PcrTestPositiveCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultPositiveFragment(PCR)) + } + is SubmissionStatePCR.TestNegative -> PcrTestNegativeCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToPcrTestResultNegativeFragment(PCR)) + } + is SubmissionStatePCR.TestInvalid -> PcrTestInvalidCard.Item(state) { + events.postValue(HomeFragmentEvents.ShowDeleteTestDialog(PCR)) + } + is SubmissionStatePCR.TestError -> PcrTestErrorCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultPendingFragment(PCR)) + } + is SubmissionStatePCR.TestPending -> PcrTestPendingCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultPendingFragment(PCR, true)) + } + is SubmissionStatePCR.SubmissionDone -> PcrTestSubmissionDoneCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultKeysSharedFragment(PCR)) + } + } + + private fun RACoronaTest?.toTestCardItem(coronaTestConfig: CoronaTestConfig) = + when (val state = this.toSubmissionState(timeStamper.nowUTC, coronaTestConfig)) { + is SubmissionStateRAT.NoTest -> TestUnregisteredCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToSubmissionDispatcher) + } + is SubmissionStateRAT.FetchingResult -> TestFetchingCard.Item(state) + is SubmissionStateRAT.TestResultReady -> RapidTestReadyCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultAvailableFragment(RAPID_ANTIGEN)) + } + is SubmissionStateRAT.TestPositive -> RapidTestPositiveCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultPositiveFragment(RAPID_ANTIGEN)) + } + is SubmissionStateRAT.TestNegative -> RapidTestNegativeCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToRapidTestResultNegativeFragment) + } + is SubmissionStateRAT.TestInvalid -> RapidTestInvalidCard.Item(state) { + events.postValue(HomeFragmentEvents.ShowDeleteTestDialog(RAPID_ANTIGEN)) + } + is SubmissionStateRAT.TestError -> RapidTestErrorCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultPendingFragment(RAPID_ANTIGEN)) + } + is SubmissionStateRAT.TestPending -> RapidTestPendingCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultPendingFragment(RAPID_ANTIGEN, true)) + } + is SubmissionStateRAT.TestOutdated -> RapidTestOutdatedCard.Item(state) { + submissionRepository.removeTestFromDevice(RAPID_ANTIGEN) + } + is SubmissionStateRAT.SubmissionDone -> RapidTestSubmissionDoneCard.Item(state) { + events.postValue(HomeFragmentEvents.GoToTestResultKeysSharedFragment(RAPID_ANTIGEN)) + } + } - fun wasVaccinationRegistrationAcknowledged() = vaccinationSettings.registrationAcknowledged + private fun refreshRiskResult() { + tracingRepository.refreshRiskResult() + } + + private fun tracingStateItem(tracingState: TracingState) = when (tracingState) { + is TracingInProgress -> TracingProgressCard.Item( + state = tracingState, + onCardClick = { events.postValue(HomeFragmentEvents.GoToRiskDetailsFragment) } + ) + is TracingDisabled -> TracingDisabledCard.Item( + state = tracingState, + onCardClick = { events.postValue(HomeFragmentEvents.GoToRiskDetailsFragment) }, + onEnableTracingClick = { events.postValue(HomeFragmentEvents.GoToSettingsTracingFragment) } + ) + is LowRisk -> LowRiskCard.Item( + state = tracingState, + onCardClick = { events.postValue(HomeFragmentEvents.GoToRiskDetailsFragment) }, + onUpdateClick = { refreshRiskResult() } + ) + is IncreasedRisk -> IncreasedRiskCard.Item( + state = tracingState, + onCardClick = { events.postValue(HomeFragmentEvents.GoToRiskDetailsFragment) }, + onUpdateClick = { refreshRiskResult() } + ) + is TracingFailed -> TracingFailedCard.Item( + state = tracingState, + onCardClick = { events.postValue(HomeFragmentEvents.GoToRiskDetailsFragment) }, + onRetryClick = { refreshRiskResult() } + ) + } @AssistedFactory interface Factory : SimpleCWAViewModelFactory<HomeFragmentViewModel> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt index 4cdc9ec41071c7d3b397661ba3cc01786c07a905..95b2fa47fdf7eca2f5b64ca3a4c471423d22f511 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt @@ -7,10 +7,12 @@ import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.FragmentManager import androidx.navigation.NavController import androidx.navigation.NavDirections +import androidx.navigation.NavGraph import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import de.rki.coronawarnapp.ui.doNavigate import timber.log.Timber +import java.lang.IllegalArgumentException fun Fragment.doNavigate(direction: NavDirections) = findNavController().doNavigate(direction) @@ -35,3 +37,13 @@ fun FragmentManager.findNavController(@IdRes id: Int): NavController { val fragment = findFragmentById(id) ?: throw IllegalStateException("Fragment is not found for id:$id") 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") +} diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml index 95dbddcbfb3017424dbd8d5a3fdd717b1b775b8d..e91b6e0d929306d60e5ecbf988254baa8baeb498 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -312,6 +312,9 @@ android:name="de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragment" android:label="SubmissionTestResultPendingFragment" tools:layout="@layout/fragment_submission_test_result_pending"> + <argument + android:name="testType" + app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" /> <argument android:name="forceTestResultUpdate" android:defaultValue="false" @@ -334,9 +337,6 @@ app:destination="@id/submissionTestResultAvailableFragment" app:popUpTo="@id/mainFragment" app:popUpToInclusive="false" /> - <argument - android:name="testType" - app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" /> <action android:id="@+id/action_submissionTestResultPendingFragment_to_submissionNegativeAntigenTestResultFragment" app:destination="@id/submissionNegativeAntigenTestResultFragment" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt index a1a3c9983362bf68370a23706727cb4f2c1a96fb..7d9d96f3fa5151fff19408411c86ef342dc572ee 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt @@ -169,7 +169,7 @@ class HomeFragmentViewModelTest : BaseTest() { with(createInstance()) { showPopUps() - popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowErrorResetDialog + events.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowErrorResetDialog } } }