diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/QrCodeDetailFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/QrCodeDetailFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..9986448a35777f646a5b4b694ce54a39f8fbde0d --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/QrCodeDetailFragmentTest.kt @@ -0,0 +1,98 @@ +package de.rki.coronawarnapp.ui.eventregistration.organizer + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.presencetracing.checkins.qrcode.QrCodeGenerator +import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository +import de.rki.coronawarnapp.ui.presencetracing.organizer.details.QrCodeDetailFragment +import de.rki.coronawarnapp.ui.presencetracing.organizer.details.QrCodeDetailFragmentArgs +import de.rki.coronawarnapp.ui.presencetracing.organizer.details.QrCodeDetailViewModel +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import org.joda.time.DateTimeZone +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.TestDispatcherProvider +import testhelpers.launchFragmentInContainer2 +import java.util.TimeZone + +@RunWith(AndroidJUnit4::class) +class QrCodeDetailFragmentTest : BaseUITest() { + + @MockK private lateinit var qrCodeGenerator: QrCodeGenerator + @MockK private lateinit var traceLocationRepository: TraceLocationRepository + + private val timeZone = TimeZone.getTimeZone("Europe/Berlin") + + @Before + fun setup() { + TimeZone.setDefault(timeZone) + DateTimeZone.setDefault(DateTimeZone.forTimeZone(timeZone)) + MockKAnnotations.init(this, relaxed = true) + + coEvery { traceLocationRepository.traceLocationForId(1) } returns TraceLocationData.traceLocationSameDate + coEvery { traceLocationRepository.traceLocationForId(2) } returns TraceLocationData.traceLocationDifferentDate + + setupMockViewModel( + object : QrCodeDetailViewModel.Factory { + override fun create(traceLocationId: Long): QrCodeDetailViewModel { + return createViewModel(traceLocationId) + } + } + ) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + fun eventDetailForSameDatesTest() { + launchFragmentInContainer2<QrCodeDetailFragment>( + fragmentArgs = QrCodeDetailFragmentArgs( + traceLocationId = 1 + ).toBundle() + ) + + onView(withId(R.id.title)).check(matches(withText("My Birthday Party"))) + onView(withId(R.id.subtitle)).check(matches(withText("at my place"))) + onView(withId(R.id.eventDate)).check(matches(withText("19.04.2021, 06:12 - 22:52 Uhr"))) + } + + @Test + fun eventDetailForDifferentDatesTest() { + launchFragmentInContainer2<QrCodeDetailFragment>( + fragmentArgs = QrCodeDetailFragmentArgs( + traceLocationId = 2 + ).toBundle() + ) + onView(withId(R.id.title)).check(matches(withText("Your Birthday Party"))) + onView(withId(R.id.subtitle)).check(matches(withText("at your place"))) + onView(withId(R.id.eventDate)).check(matches(withText("18.04.2021, 12:00 - 19.04.2021, 22:52 Uhr"))) + } + + private fun createViewModel(traceLocationId: Long) = + QrCodeDetailViewModel( + traceLocationId = traceLocationId, + qrCodeGenerator = qrCodeGenerator, + traceLocationRepository = traceLocationRepository, + dispatcher = TestDispatcherProvider() + ) +} + +@Module +abstract class QrCodeDetailFragmentTestModule { + @ContributesAndroidInjector + abstract fun qrCodeDetailFragment(): QrCodeDetailFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationCreateFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationCreateFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2685049bd5b53fbcc0887d35c53494f417ef6fda --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationCreateFragmentTest.kt @@ -0,0 +1,122 @@ +package de.rki.coronawarnapp.ui.eventregistration.organizer + +import androidx.navigation.Navigation +import androidx.navigation.testing.TestNavHostController +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.internal.runner.junit4.statement.UiThreadStatement +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.presencetracing.locations.TraceLocationCreator +import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository +import de.rki.coronawarnapp.ui.presencetracing.organizer.category.adapter.category.TraceLocationCategory +import de.rki.coronawarnapp.ui.presencetracing.organizer.create.TraceLocationCreateFragment +import de.rki.coronawarnapp.ui.presencetracing.organizer.create.TraceLocationCreateFragmentArgs +import de.rki.coronawarnapp.ui.presencetracing.organizer.create.TraceLocationCreateViewModel +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import org.joda.time.DateTimeZone +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.TestDispatcherProvider +import testhelpers.launchFragmentInContainer2 +import java.util.TimeZone + +@RunWith(AndroidJUnit4::class) +class TraceLocationCreateFragmentTest : BaseUITest() { + + @MockK private lateinit var traceLocationRepository: TraceLocationRepository + @MockK private lateinit var traceLocationCreator: TraceLocationCreator + + private val timeZone = TimeZone.getTimeZone("Europe/Berlin") + + private val navController = TestNavHostController( + ApplicationProvider.getApplicationContext() + ).apply { + UiThreadStatement.runOnUiThread { setGraph(R.navigation.trace_location_organizer_nav_graph) } + } + + @Before + fun setup() { + TimeZone.setDefault(timeZone) + DateTimeZone.setDefault(DateTimeZone.forTimeZone(timeZone)) + MockKAnnotations.init(this, relaxed = true) + + coEvery { traceLocationRepository.addTraceLocation(any()) } returns TraceLocationData.traceLocationSameDate + + setupMockViewModel( + object : TraceLocationCreateViewModel.Factory { + override fun create(category: TraceLocationCategory): TraceLocationCreateViewModel { + return createViewModel(category) + } + } + ) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + fun duplicateEventWithSameDatesTest() { + launchFragmentInContainer2<TraceLocationCreateFragment>( + fragmentArgs = TraceLocationCreateFragmentArgs( + category = TraceLocationData.categoryEvent, + originalItem = TraceLocationData.traceLocationSameDate + ).toBundle() + ).onFragment { fragment -> Navigation.setViewNavController(fragment.requireView(), navController) } + + onView(withId(R.id.description_input_edit)).check(matches(withText("My Birthday Party"))) + onView(withId(R.id.place_input_edit)).check(matches(withText("at my place"))) + onView(withId(R.id.value_start)).check(matches(withText("Mo., 19.04.21 06:12"))) + onView(withId(R.id.value_end)).check(matches(withText("Mo., 19.04.21 22:52"))) + + onView(withId(R.id.button_submit)).perform(click()) + + navController.currentDestination?.id shouldBe R.id.traceLocationInfoFragment + } + + @Test + fun duplicateEventWithDifferentDatesTest() { + launchFragmentInContainer2<TraceLocationCreateFragment>( + fragmentArgs = TraceLocationCreateFragmentArgs( + category = TraceLocationData.categoryEvent, + originalItem = TraceLocationData.traceLocationDifferentDate + ).toBundle() + ).onFragment { fragment -> Navigation.setViewNavController(fragment.requireView(), navController) } + + onView(withId(R.id.description_input_edit)).check(matches(withText("Your Birthday Party"))) + onView(withId(R.id.place_input_edit)).check(matches(withText("at your place"))) + onView(withId(R.id.value_start)).check(matches(withText("So., 18.04.21 12:00"))) + onView(withId(R.id.value_end)).check(matches(withText("Mo., 19.04.21 22:52"))) + + onView(withId(R.id.button_submit)).perform(click()) + + navController.currentDestination?.id shouldBe R.id.traceLocationInfoFragment + } + + private fun createViewModel(category: TraceLocationCategory) = + TraceLocationCreateViewModel( + category = category, + traceLocationCreator = traceLocationCreator, + dispatcherProvider = TestDispatcherProvider() + ) +} + +@Module +abstract class CreateEventTestModule { + @ContributesAndroidInjector + abstract fun traceLocationCreateFragment(): TraceLocationCreateFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationData.kt new file mode 100644 index 0000000000000000000000000000000000000000..9bf3382fff76dde0a8faf0f26d21008da1ab301f --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationData.kt @@ -0,0 +1,51 @@ +package de.rki.coronawarnapp.ui.eventregistration.organizer + +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import de.rki.coronawarnapp.ui.presencetracing.organizer.category.adapter.category.TraceLocationCategory +import de.rki.coronawarnapp.ui.presencetracing.organizer.category.adapter.category.TraceLocationUIType +import de.rki.coronawarnapp.util.TimeAndDateExtensions.secondsToInstant +import okio.ByteString.Companion.decodeBase64 + +object TraceLocationData { + + private const val CRYPTOGRAPHIC_SEED = "MTIzNA==" + + private const val PUB_KEY = + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0z" + + "K7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==" + + val traceLocationSameDate = TraceLocation( + id = 1, + type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_OTHER, + description = "My Birthday Party", + address = "at my place", + startDate = 1618805545L.secondsToInstant(), + endDate = 1618865545L.secondsToInstant(), + defaultCheckInLengthInMinutes = null, + cryptographicSeed = CRYPTOGRAPHIC_SEED.decodeBase64()!!, + cnPublicKey = PUB_KEY, + version = TraceLocation.VERSION + ) + + val traceLocationDifferentDate = TraceLocation( + id = 2, + type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_OTHER, + description = "Your Birthday Party", + address = "at your place", + startDate = 1618740005L.secondsToInstant(), + endDate = 1618865545L.secondsToInstant(), + defaultCheckInLengthInMinutes = null, + cryptographicSeed = CRYPTOGRAPHIC_SEED.decodeBase64()!!, + cnPublicKey = PUB_KEY, + version = TraceLocation.VERSION + ) + + val categoryEvent = TraceLocationCategory( + TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT, + TraceLocationUIType.EVENT, + R.string.tracelocation_organizer_category_cultural_event_title, + R.string.tracelocation_organizer_category_cultural_event_subtitle + ) +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationsFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e478d51bc5b0709902117966277aae8f58014ace --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/eventregistration/organizer/TraceLocationsFragmentTest.kt @@ -0,0 +1,94 @@ +package de.rki.coronawarnapp.ui.eventregistration.organizer + +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository +import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository +import de.rki.coronawarnapp.ui.presencetracing.organizer.list.TraceLocationsFragment +import de.rki.coronawarnapp.ui.presencetracing.organizer.list.TraceLocationsViewModel +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.flowOf +import org.joda.time.DateTimeZone +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.TestDispatcherProvider +import testhelpers.launchFragmentInContainer2 +import java.util.TimeZone + +@RunWith(AndroidJUnit4::class) +class TraceLocationsFragmentTest : BaseUITest() { + + @MockK private lateinit var checkInsRepository: CheckInRepository + @MockK private lateinit var traceLocationRepository: TraceLocationRepository + + private val timeZone = TimeZone.getTimeZone("Europe/Berlin") + + @Before + fun setup() { + TimeZone.setDefault(timeZone) + DateTimeZone.setDefault(DateTimeZone.forTimeZone(timeZone)) + MockKAnnotations.init(this, relaxed = true) + + every { checkInsRepository.allCheckIns } returns flowOf(listOf()) + + setupMockViewModel( + object : TraceLocationsViewModel.Factory { + override fun create(): TraceLocationsViewModel { + return createViewModel() + } + } + ) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + fun itemWithSameDatesTest() { + every { traceLocationRepository.traceLocationsWithinRetention } returns + flowOf(listOf(TraceLocationData.traceLocationSameDate)) + + launchFragmentInContainer2<TraceLocationsFragment>() + + onView(withId(R.id.description)).check(matches(withText("My Birthday Party"))) + onView(withId(R.id.address)).check(matches(withText("at my place"))) + onView(withId(R.id.duration)).check(matches(withText("19.04.21 06:12 - 22:52 Uhr"))) + } + + @Test + fun itemWithDifferentDatesTest() { + every { traceLocationRepository.traceLocationsWithinRetention } returns + flowOf(listOf(TraceLocationData.traceLocationDifferentDate)) + + launchFragmentInContainer2<TraceLocationsFragment>() + + onView(withId(R.id.description)).check(matches(withText("Your Birthday Party"))) + onView(withId(R.id.address)).check(matches(withText("at your place"))) + onView(withId(R.id.duration)).check(matches(withText("18.04.21 12:00 - 19.04.21 22:52 Uhr"))) + } + + private fun createViewModel() = TraceLocationsViewModel( + checkInsRepository = checkInsRepository, + traceLocationRepository = traceLocationRepository, + dispatcherProvider = TestDispatcherProvider() + ) +} + +@Module +abstract class TraceLocationsFragmentTestModule { + @ContributesAndroidInjector + abstract fun traceLocationsFragment(): TraceLocationsFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt index d610fb4905df02d3f210878cee1c6ad16b09b225..6a90f9e2132e68c155bbe1691ea4d197f793da50 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt @@ -134,12 +134,17 @@ object HomeData { ) val TEST_POSITIVE_ITEM = PcrTestPositiveCard.Item( - state = TestPositive, + state = TestPositive( + testRegisteredAt = Instant.now() + ), onClickAction = {} ) val TEST_NEGATIVE_ITEM = PcrTestNegativeCard.Item( - state = TestNegative + state = TestNegative( + testRegisteredAt = Instant.now() + ), + onClickAction = {} ) val TEST_INVALID_ITEM = PcrTestInvalidCard.Item( 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 db6e15d1b787f3a1ff5f73150390ecd0b5c85096..4dc5e974943a1f3b8b2744823285e49af166a10a 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 @@ -12,9 +12,9 @@ import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard import de.rki.coronawarnapp.storage.TracingRepository @@ -84,7 +84,6 @@ class HomeFragmentTest : BaseUITest() { MockKAnnotations.init(this, relaxed = true) homeFragmentViewModel = homeFragmentViewModelSpy() with(homeFragmentViewModel) { - every { observeTestResultToSchedulePositiveTestResultReminder() } just Runs every { refreshRequiredData() } just Runs every { tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingActive) every { showLoweredRiskLevelDialog } returns MutableLiveData() @@ -266,7 +265,6 @@ class HomeFragmentTest : BaseUITest() { errorResetTool = errorResetTool, tracingRepository = tracingRepository, tracingStateProviderFactory = tracingStateProviderFactory, - shareTestResultNotificationService = shareTestResultNotificationService, appConfigProvider = appConfigProvider, tracingStatus = tracingStatus, submissionRepository = submissionRepository, diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt index 7748ab27cde652375f87387997882dea0eec59a9..27bc93242d927d3caea09fffa5fafb3a18dde9df 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt @@ -48,11 +48,9 @@ class SubmissionConsentFragmentTest : BaseUITest() { every { interoperabilityRepository.countryList } returns flowOf() viewModel = SubmissionConsentViewModel( - submissionRepository, interoperabilityRepository, TestDispatcherProvider(), tekHistoryProvider, - analyticsKeySubmissionCollector ) setupMockViewModel( object : SubmissionConsentViewModel.Factory { diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt index 06ab62aa36eba132f077b81be1b302b906204f0a..e516c0b18e863220d12b08eb4ed205ff83fb7468 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionQrCodeScanFragmentTest.kt @@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment +import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragmentArgs import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanViewModel import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK @@ -19,12 +20,14 @@ class SubmissionQrCodeScanFragmentTest : BaseUITest() { @MockK lateinit var viewModel: SubmissionQRCodeScanViewModel + private var fragmentArgs = SubmissionQRCodeScanFragmentArgs(isConsentGiven = true).toBundle() + @Before fun setup() { MockKAnnotations.init(this, relaxed = true) setupMockViewModel( object : SubmissionQRCodeScanViewModel.Factory { - override fun create(): SubmissionQRCodeScanViewModel = viewModel + override fun create(isConsentGiven: Boolean): SubmissionQRCodeScanViewModel = viewModel } ) } @@ -36,7 +39,9 @@ class SubmissionQrCodeScanFragmentTest : BaseUITest() { @Test fun launch_fragment() { - launchFragment2<SubmissionQRCodeScanFragment>() + launchFragment2<SubmissionQRCodeScanFragment>( + fragmentArgs = fragmentArgs + ) } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt index 5e48790a6dbfd987b0a88beb50c7607a5cf8d63b..97f06b6023be71680d732e9bc1dcb791831f1674 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt @@ -15,7 +15,7 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState @@ -46,7 +46,7 @@ class SubmissionTestResultConsentGivenFragmentTest : BaseUITest() { @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var autoSubmission: AutoSubmission - @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService + @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector @Rule diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt index 4b970b3a999263b3f99687dc97b6d4355939cfa7..849a4e22013f7d00d36f500edb61df251c3d62bb 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt @@ -10,16 +10,14 @@ import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragment +import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragmentArgs import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingViewModel import io.mockk.MockKAnnotations -import io.mockk.Runs import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.just import io.mockk.mockk import io.mockk.spyk import kotlinx.coroutines.flow.flowOf @@ -43,7 +41,8 @@ class SubmissionTestResultFragmentTest : BaseUITest() { lateinit var viewModel: SubmissionTestResultPendingViewModel @MockK lateinit var submissionRepository: SubmissionRepository - @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService + + private val pendingFragmentArgs = SubmissionTestResultPendingFragmentArgs(testType = CoronaTest.Type.PCR).toBundle() @Rule @JvmField @@ -61,13 +60,12 @@ class SubmissionTestResultFragmentTest : BaseUITest() { viewModel = spyk( SubmissionTestResultPendingViewModel( TestDispatcherProvider(), - shareTestResultNotificationService, - submissionRepository + submissionRepository, + testType = CoronaTest.Type.PCR ) ) with(viewModel) { - every { observeTestResultToSchedulePositiveTestResultReminder() } just Runs every { consentGiven } returns MutableLiveData(true) every { testState } returns MutableLiveData( TestResultUIState( @@ -82,7 +80,7 @@ class SubmissionTestResultFragmentTest : BaseUITest() { setupMockViewModel( object : SubmissionTestResultPendingViewModel.Factory { - override fun create(): SubmissionTestResultPendingViewModel = viewModel + override fun create(testType: CoronaTest.Type): SubmissionTestResultPendingViewModel = viewModel } ) } @@ -94,19 +92,19 @@ class SubmissionTestResultFragmentTest : BaseUITest() { @Test fun launch_fragment() { - launchFragment2<SubmissionTestResultPendingFragment>() + launchFragment2<SubmissionTestResultPendingFragment>(pendingFragmentArgs) } @Test fun testEventPendingRefreshClicked() { - launchFragmentInContainer2<SubmissionTestResultPendingFragment>() + launchFragmentInContainer2<SubmissionTestResultPendingFragment>(pendingFragmentArgs) onView(withId(R.id.submission_test_result_button_pending_refresh)) .perform(click()) } @Test fun testEventPendingRemoveClicked() { - launchFragmentInContainer2<SubmissionTestResultPendingFragment>() + launchFragmentInContainer2<SubmissionTestResultPendingFragment>(pendingFragmentArgs) onView(withId(R.id.submission_test_result_button_pending_remove_test)) .perform(click()) } @@ -123,7 +121,7 @@ class SubmissionTestResultFragmentTest : BaseUITest() { } ) ) - captureScreenshot<SubmissionTestResultPendingFragment>() + captureScreenshot<SubmissionTestResultPendingFragment>(fragmentArgs = pendingFragmentArgs) } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt index 5eba253d54bb986fbd8c95f225c8c3e7eeb4876e..53df826ee0150196d647da355c51c091e58f43bf 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt @@ -6,7 +6,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeFragment @@ -35,7 +35,7 @@ class SubmissionTestResultNegativeFragmentTest : BaseUITest() { lateinit var viewModel: SubmissionTestResultNegativeViewModel @MockK lateinit var submissionRepository: SubmissionRepository - @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService + @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @Rule @JvmField diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt index 55d8144158bfc191c60d729b26af79204b5ea397..3ec1a885ea9f0fef9e4443cda94977da3a276781 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt @@ -7,7 +7,7 @@ import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultNoConsentFragment @@ -33,7 +33,7 @@ import tools.fastlane.screengrab.locale.LocaleTestRule class SubmissionTestResultNoConsentGivenFragmentTest : BaseUITest() { @MockK lateinit var submissionRepository: SubmissionRepository - @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService + @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector @Rule diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt index ecb1c406efdc4d80d26d484c1e55fa286e48da5d..5f3cabbfcacfc6f5c44078480bc4dfcff0651fde 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt @@ -10,6 +10,9 @@ import de.rki.coronawarnapp.ui.contactdiary.ContactDiaryLocationListFragmentTest import de.rki.coronawarnapp.ui.contactdiary.ContactDiaryOnboardingFragmentTestModule import de.rki.coronawarnapp.ui.contactdiary.ContactDiaryOverviewFragmentTestModule import de.rki.coronawarnapp.ui.contactdiary.ContactDiaryPersonListFragmentTestModule +import de.rki.coronawarnapp.ui.eventregistration.organizer.CreateEventTestModule +import de.rki.coronawarnapp.ui.eventregistration.organizer.QrCodeDetailFragmentTestModule +import de.rki.coronawarnapp.ui.eventregistration.organizer.TraceLocationsFragmentTestModule import de.rki.coronawarnapp.ui.main.home.HomeFragmentTestModule import de.rki.coronawarnapp.ui.onboarding.OnboardingAnalyticsFragmentTestModule import de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaInteroperabilityFragmentTestModule @@ -74,7 +77,11 @@ import de.rki.coronawarnapp.ui.tracing.TracingDetailsFragmentTestTestModule StatisticsExplanationFragmentTestModule::class, // Bugreporting DebugLogUploadTestModule::class, - DebugLogTestModule::class + DebugLogTestModule::class, + // Event Registration + CreateEventTestModule::class, + TraceLocationsFragmentTestModule::class, + QrCodeDetailFragmentTestModule::class ] ) class FragmentTestModuleRegistrar diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt index 8d43b3595cdcbfd7dda641e9bc9d043f7fb9dc70..2def4a1e835ee1167eea8a8eb61272f82a1c2e91 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/hometestcards/ui/HomeTestCardsFragmentViewModel.kt @@ -44,16 +44,16 @@ class HomeTestCardsFragmentViewModel @AssistedInject constructor( PcrTestReadyCard.Item(SubmissionStatePCR.TestResultReady) {}, PcrTestInvalidCard.Item(SubmissionStatePCR.TestInvalid) {}, PcrTestErrorCard.Item(SubmissionStatePCR.TestError) {}, - PcrTestNegativeCard.Item(SubmissionStatePCR.TestNegative), - PcrTestPositiveCard.Item(SubmissionStatePCR.TestPositive) {}, + PcrTestNegativeCard.Item(SubmissionStatePCR.TestNegative(Instant.now())) {}, + PcrTestPositiveCard.Item(SubmissionStatePCR.TestPositive(Instant.now())) {}, PcrTestSubmissionDoneCard.Item(SubmissionStatePCR.SubmissionDone(Instant.now())), RapidTestPendingCard.Item(SubmissionStateRAT.TestPending) {}, RapidTestReadyCard.Item(SubmissionStateRAT.TestResultReady) {}, RapidTestInvalidCard.Item(SubmissionStateRAT.TestInvalid) {}, RapidTestOutdatedCard.Item(SubmissionStateRAT.TestInvalid) {}, RapidTestErrorCard.Item(SubmissionStateRAT.TestError) {}, - RapidTestNegativeCard.Item(SubmissionStateRAT.TestNegative), - RapidTestPositiveCard.Item(SubmissionStateRAT.TestPositive) {}, + RapidTestNegativeCard.Item(SubmissionStateRAT.TestNegative(Instant.now())) {}, + RapidTestPositiveCard.Item(SubmissionStateRAT.TestPositive(Instant.now())) {}, RapidTestSubmissionDoneCard.Item(SubmissionStateRAT.SubmissionDone(Instant.now())) ) ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt index d10c520260fb53b3cc45d2457c95d168d37a8329..b2d67700ee839cee11b6e2e8dabbe454500ea6ab 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt @@ -16,13 +16,14 @@ import de.rki.coronawarnapp.appconfig.devicetime.DeviceTimeHandler import de.rki.coronawarnapp.bugreporting.loghistory.LogHistoryTree import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryWorkScheduler import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler -import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpScheduler import de.rki.coronawarnapp.exception.reporting.ErrorReportReceiver import de.rki.coronawarnapp.exception.reporting.ReportingConstants.ERROR_REPORT_LOCAL_BROADCAST_CHANNEL import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut +import de.rki.coronawarnapp.presencetracing.storage.retention.TraceLocationDbCleanUpScheduler import de.rki.coronawarnapp.risk.RiskLevelChangeDetector import de.rki.coronawarnapp.storage.OnboardingSettings import de.rki.coronawarnapp.submission.auto.AutoSubmission @@ -68,6 +69,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { @Inject lateinit var autoCheckOut: AutoCheckOut @Inject lateinit var traceLocationDbCleanupScheduler: TraceLocationDbCleanUpScheduler @Inject lateinit var backgroundWorkScheduler: BackgroundWorkScheduler + @Inject lateinit var shareTestResultNotificationService: ShareTestResultNotificationService @LogHistoryTree @Inject lateinit var rollingLogHistory: Timber.Tree @@ -119,6 +121,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector { autoSubmission.setup() autoCheckOut.setupMonitor() traceLocationDbCleanupScheduler.scheduleDaily() + shareTestResultNotificationService.setup() } private val activityLifecycleCallback = object : ActivityLifecycleCallbacks { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt index b3c47171c9024dcd36ff61c6ff86d1d5417bd0ec..c8769d23087ff67a5d92401421161273d206e739 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt @@ -6,7 +6,6 @@ import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.coronatest.CoronaTestRepository import de.rki.coronawarnapp.util.CWADebug import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map import javax.inject.Inject @Reusable @@ -14,9 +13,7 @@ class RegistrationTokenCensor @Inject constructor( private val coronaTestRepository: CoronaTestRepository, ) : BugCensor { override suspend fun checkLog(entry: LogLine): LogLine? { - val tokens = coronaTestRepository.coronaTests.map { tests -> - tests.map { it.registrationToken } - }.first() + val tokens = coronaTestRepository.coronaTests.first().map { it.registrationToken } if (tokens.isEmpty()) return null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotification.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotification.kt new file mode 100644 index 0000000000000000000000000000000000000000..3cebace1c18fa97124d78c0cb557b9120764a2d8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotification.kt @@ -0,0 +1,57 @@ +package de.rki.coronawarnapp.coronatest.notification + +import android.content.Context +import androidx.navigation.NavDeepLinkBuilder +import dagger.Reusable +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.notification.GeneralNotifications +import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_ID +import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET +import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_INTERVAL +import de.rki.coronawarnapp.ui.main.MainActivity +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.notifications.setContentTextExpandable +import timber.log.Timber +import javax.inject.Inject + +@Reusable +class ShareTestResultNotification @Inject constructor( + @AppContext private val context: Context, + private val timeStamper: TimeStamper, + private val notificationHelper: GeneralNotifications, +) { + + fun scheduleSharePositiveTestResultReminder() { + notificationHelper.scheduleRepeatingNotification( + timeStamper.nowUTC.plus(POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET), + POSITIVE_RESULT_NOTIFICATION_INTERVAL, + POSITIVE_RESULT_NOTIFICATION_ID + ) + } + + fun showSharePositiveTestResultNotification(notificationId: Int) { + Timber.d("showSharePositiveTestResultNotification(notificationId=$notificationId)") + val pendingIntent = NavDeepLinkBuilder(context) + .setGraph(R.navigation.nav_graph) + .setComponentName(MainActivity::class.java) + .setDestination(R.id.submissionTestResultAvailableFragment) + .createPendingIntent() + + val notification = notificationHelper.newBaseBuilder().apply { + setContentTitle(context.getString(R.string.notification_headline_share_positive_result)) + setContentTextExpandable(context.getString(R.string.notification_body_share_positive_result)) + setContentIntent(pendingIntent) + }.build() + + notificationHelper.sendNotification( + notificationId = notificationId, + notification = notification, + ) + } + + fun cancelSharePositiveTestResultNotification() { + notificationHelper.cancelFutureNotifications(POSITIVE_RESULT_NOTIFICATION_ID) + Timber.v("Future positive test result notifications have been canceled") + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotificationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..dddb09c4b7b328f9304001911108288c0bfd907f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotificationService.kt @@ -0,0 +1,68 @@ +package de.rki.coronawarnapp.coronatest.notification + +import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT +import de.rki.coronawarnapp.util.coroutine.AppScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ShareTestResultNotificationService @Inject constructor( + @AppScope private val appScope: CoroutineScope, + private val cwaSettings: CWASettings, + private val coronaTestRepository: CoronaTestRepository, + private val notification: ShareTestResultNotification +) { + + fun setup() { + Timber.d("setup()") + coronaTestRepository.coronaTests + .onEach { tests -> + when { + tests.any { it.isSubmissionAllowed } -> { + maybeScheduleSharePositiveTestResultReminder() + } + tests.isNotEmpty() -> { + notification.cancelSharePositiveTestResultNotification() + } + tests.isEmpty() -> { + resetSharePositiveTestResultNotification() + } + } + } + .catch { Timber.e(it, "Failed to schedule positive test result reminder.") } + .launchIn(appScope) + } + + fun maybeShowSharePositiveTestResultNotification(notificationId: Int) { + Timber.d("maybeShowSharePositiveTestResultNotification(notificationId=$notificationId)") + if (cwaSettings.numberOfRemainingSharePositiveTestResultReminders > 0) { + cwaSettings.numberOfRemainingSharePositiveTestResultReminders -= 1 + notification.showSharePositiveTestResultNotification(notificationId) + } else { + notification.cancelSharePositiveTestResultNotification() + } + } + + private fun maybeScheduleSharePositiveTestResultReminder() { + if (cwaSettings.numberOfRemainingSharePositiveTestResultReminders < 0) { + Timber.v("Schedule positive test result notification") + cwaSettings.numberOfRemainingSharePositiveTestResultReminders = POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT + notification.scheduleSharePositiveTestResultReminder() + } else { + Timber.v("Positive test result notification has already been scheduled") + } + } + + private fun resetSharePositiveTestResultNotification() { + notification.cancelSharePositiveTestResultNotification() + cwaSettings.numberOfRemainingSharePositiveTestResultReminders = Int.MIN_VALUE + Timber.v("Positive test result notification counter has been reset") + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt index 1867629ee401e40b7d8ed2f2ec6c62d74b40465b..190189a114a6575b56d6b0cfd0a35020f3bb94af 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CommonSubmissionStates.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.coronatest.type +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone import org.joda.time.Instant interface CommonSubmissionStates { @@ -7,7 +8,16 @@ interface CommonSubmissionStates { interface TestFetching : CommonSubmissionStates - interface SubmissionDone : CommonSubmissionStates { + interface PositiveTest : HasTestRegistrationDate, CommonSubmissionStates + + interface NegativeTest : HasTestRegistrationDate, CommonSubmissionStates + + interface SubmissionDone : HasTestRegistrationDate, CommonSubmissionStates + + interface HasTestRegistrationDate { val testRegisteredAt: Instant + + fun getFormattedRegistrationDate(): String = + testRegisteredAt.toUserTimeZone().toLocalDate().toString("dd.MM.yy") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt index 6a3e9b1142e5c68ea4bc995906d3a9b66565b1d1..4fac9bbf2040b9143baeda36bab432a472185e9f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt @@ -18,6 +18,8 @@ interface CoronaTest { val isSubmitted: Boolean val isViewed: Boolean + val isPositive: Boolean + val testResultReceivedAt: Instant? val testResult: CoronaTestResult diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt index ed6b4d56af89d29120f12ed7c1cf1f007be6d500..77957e449c932e055af49ad2dd75d9782488eab3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt @@ -44,7 +44,9 @@ data class PCRCoronaTest( override val type: CoronaTest.Type = CoronaTest.Type.PCR - override val isSubmissionAllowed: Boolean = testResult == CoronaTestResult.PCR_POSITIVE + override val isPositive: Boolean = testResult == CoronaTestResult.PCR_POSITIVE + + override val isSubmissionAllowed: Boolean = isPositive && !isSubmitted val state: State = when (testResult) { CoronaTestResult.PCR_OR_RAT_PENDING -> State.PENDING diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt index 5ce1c65829e0f85eff22df70bb980e40dd264d1f..7ea0ce6d7a53ffae6f3e3b4790cc7c5c084bc3fd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTestExtensions.kt @@ -1,121 +1,33 @@ package de.rki.coronawarnapp.coronatest.type.pcr +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest.State.INVALID +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest.State.NEGATIVE +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest.State.PENDING +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest.State.POSITIVE +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest.State.REDEEMED +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.FetchingResult +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.NoTest +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestError +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestInvalid +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestNegative +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestPending +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestPositive +import de.rki.coronawarnapp.coronatest.type.pcr.SubmissionStatePCR.TestResultReady import de.rki.coronawarnapp.exception.http.CwaServerError -import de.rki.coronawarnapp.util.CWADebug -import de.rki.coronawarnapp.util.DeviceUIState -import de.rki.coronawarnapp.util.NetworkRequestWrapper -import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess -import timber.log.Timber -fun PCRCoronaTest?.toSubmissionState(): SubmissionStatePCR { - if (this == null) return SubmissionStatePCR.NoTest - - val uiState: DeviceUIState = when (state) { - PCRCoronaTest.State.PENDING -> DeviceUIState.PAIRED_NO_RESULT - PCRCoronaTest.State.INVALID -> DeviceUIState.PAIRED_ERROR - PCRCoronaTest.State.POSITIVE -> DeviceUIState.PAIRED_POSITIVE - PCRCoronaTest.State.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE - PCRCoronaTest.State.REDEEMED -> DeviceUIState.PAIRED_REDEEMED - } - - val networkWrapper = when { - isProcessing -> NetworkRequestWrapper.RequestStarted - lastError != null -> NetworkRequestWrapper.RequestFailed(lastError) - else -> NetworkRequestWrapper.RequestSuccessful(uiState) +fun PCRCoronaTest?.toSubmissionState() = when { + this == null -> NoTest + isSubmitted -> SubmissionStatePCR.SubmissionDone(testRegisteredAt = registeredAt) + isProcessing -> FetchingResult + lastError != null -> if (lastError is CwaServerError) TestPending else TestInvalid + else -> when (state) { + INVALID -> TestError + POSITIVE -> when { + isViewed -> TestPositive(testRegisteredAt = registeredAt) + else -> TestResultReady + } + NEGATIVE -> TestNegative(testRegisteredAt = registeredAt) + REDEEMED -> TestInvalid + PENDING -> TestPending } - - val eval = Evaluation( - deviceUiState = networkWrapper, - isDeviceRegistered = registrationToken != null, - hasTestResultBeenSeen = this.isViewed - ) - Timber.d("eval: %s", eval) - return when { - eval.isUnregistered() -> SubmissionStatePCR.NoTest - eval.isFetching() -> SubmissionStatePCR.FetchingResult - eval.isTestResultReady() -> SubmissionStatePCR.TestResultReady - eval.isResultPositive() -> SubmissionStatePCR.TestPositive - eval.isInvalid() -> SubmissionStatePCR.TestInvalid - eval.isError() -> SubmissionStatePCR.TestError - eval.isResultNegative() -> SubmissionStatePCR.TestNegative - eval.isSubmissionDone() -> SubmissionStatePCR.SubmissionDone(testRegisteredAt = registeredAt) - eval.isPending() -> SubmissionStatePCR.TestPending - else -> { - if (CWADebug.isDeviceForTestersBuild) throw IllegalStateException(eval.toString()) - else SubmissionStatePCR.TestPending - } - } -} - -// TODO Refactor this to be easier to understand, probably remove the "withSuccess" logic. -private data class Evaluation( - val deviceUiState: NetworkRequestWrapper<DeviceUIState, Throwable>, - val isDeviceRegistered: Boolean, - val hasTestResultBeenSeen: Boolean -) { - - fun isUnregistered(): Boolean = !isDeviceRegistered - - fun isTestResultReady(): Boolean = deviceUiState.withSuccess(false) { - when (it) { - DeviceUIState.PAIRED_POSITIVE, - DeviceUIState.PAIRED_POSITIVE_TELETAN -> !hasTestResultBeenSeen - else -> false - } - } - - fun isFetching(): Boolean = - isDeviceRegistered && when (deviceUiState) { - is NetworkRequestWrapper.RequestFailed -> false - is NetworkRequestWrapper.RequestStarted -> true - is NetworkRequestWrapper.RequestIdle -> true - else -> false - } - - fun isResultPositive(): Boolean = - deviceUiState.withSuccess(false) { - when (it) { - DeviceUIState.PAIRED_POSITIVE, DeviceUIState.PAIRED_POSITIVE_TELETAN -> hasTestResultBeenSeen - else -> false - } - } - - fun isResultNegative(): Boolean = - deviceUiState.withSuccess(false) { - when (it) { - DeviceUIState.PAIRED_NEGATIVE -> true - else -> false - } - } - - fun isSubmissionDone(): Boolean = - when (deviceUiState) { - is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == DeviceUIState.SUBMITTED_FINAL - else -> false - } - - fun isInvalid(): Boolean = - isDeviceRegistered && when (deviceUiState) { - is NetworkRequestWrapper.RequestFailed -> deviceUiState.error !is CwaServerError - is NetworkRequestWrapper.RequestSuccessful -> deviceUiState.data == DeviceUIState.PAIRED_REDEEMED - else -> false - } - - fun isError(): Boolean = - deviceUiState.withSuccess(false) { - when (it) { - DeviceUIState.PAIRED_ERROR -> true - else -> false - } - } - - fun isPending(): Boolean = - when (deviceUiState) { - is NetworkRequestWrapper.RequestFailed -> true - is NetworkRequestWrapper.RequestSuccessful -> { - deviceUiState.data == DeviceUIState.PAIRED_ERROR || - deviceUiState.data == DeviceUIState.PAIRED_NO_RESULT - } - else -> false - } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt index 90eee46fc2409ca41f3290abe4ff144c455e91b9..f7e353433494fd402f0efd26667a55172fc41dbb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt @@ -5,6 +5,16 @@ import de.rki.coronawarnapp.coronatest.TestRegistrationRequest import de.rki.coronawarnapp.coronatest.execution.TestResultScheduler import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_REDEEMED import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor @@ -16,6 +26,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.util.TimeStamper +import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject @@ -58,23 +69,19 @@ class PCRProcessor @Inject constructor( response: CoronaTestService.RegistrationData ): PCRCoronaTest { analyticsKeySubmissionCollector.reset() - response.testResult.validOrThrow() - testResultDataCollector.updatePendingTestResultReceivedTime(response.testResult) + val testResult = response.testResult.validOrThrow() - if (response.testResult == CoronaTestResult.PCR_POSITIVE) { + testResultDataCollector.updatePendingTestResultReceivedTime(testResult) + + if (testResult == PCR_POSITIVE) { analyticsKeySubmissionCollector.reportPositiveTestResultReceived() deadmanNotificationScheduler.cancelScheduledWork() } analyticsKeySubmissionCollector.reportTestRegistered() -// val currentTime = timeStamper.nowUTC -// submissionSettings.initialTestResultReceivedAt = currentTime -// testResultReceivedDateFlowInternal.value = currentTime.toDate() - if (response.testResult == CoronaTestResult.PCR_OR_RAT_PENDING) { -// riskWorkScheduler.setPeriodicRiskCalculation(enabled = true) - + if (testResult == PCR_OR_RAT_PENDING) { testResultScheduler.setPeriodicTestPolling(enabled = true) } @@ -82,7 +89,8 @@ class PCRProcessor @Inject constructor( identifier = request.identifier, registeredAt = timeStamper.nowUTC, registrationToken = response.registrationToken, - testResult = response.testResult, + testResult = testResult, + testResultReceivedAt = determineReceivedDate(null, testResult), ) } @@ -91,25 +99,26 @@ class PCRProcessor @Inject constructor( Timber.tag(TAG).v("pollServer(test=%s)", test) test as PCRCoronaTest - if (test.isSubmitted || test.isSubmissionAllowed) { - Timber.tag(TAG).w("Not refreshing already final test.") + if (test.isSubmitted) { + Timber.tag(TAG).w("Not refreshing, we have already submitted.") return test } - val testResult = submissionService.asyncRequestTestResult(test.registrationToken) - Timber.tag(TAG).d("Test result was %s", testResult) + val newTestResult = submissionService.asyncRequestTestResult(test.registrationToken) + Timber.tag(TAG).d("Test result was %s", newTestResult) - testResult.validOrThrow() + newTestResult.validOrThrow() - testResultDataCollector.updatePendingTestResultReceivedTime(testResult) + testResultDataCollector.updatePendingTestResultReceivedTime(newTestResult) - if (testResult == CoronaTestResult.PCR_POSITIVE) { + if (newTestResult == PCR_POSITIVE) { analyticsKeySubmissionCollector.reportPositiveTestResultReceived() deadmanNotificationScheduler.cancelScheduledWork() } test.copy( - testResult = testResult, + testResult = newTestResult, + testResultReceivedAt = determineReceivedDate(test, newTestResult), lastError = null ) } catch (e: Exception) { @@ -121,6 +130,12 @@ class PCRProcessor @Inject constructor( } } + private fun determineReceivedDate(oldTest: PCRCoronaTest?, newTestResult: CoronaTestResult): Instant? = when { + oldTest != null && FINAL_STATES.contains(oldTest.testResult) -> oldTest.testResultReceivedAt + FINAL_STATES.contains(newTestResult) -> timeStamper.nowUTC + else -> null + } + override suspend fun onRemove(toBeRemoved: CoronaTest) { Timber.tag(TAG).v("onRemove(toBeRemoved=%s)", toBeRemoved) testResultDataCollector.clear() @@ -162,24 +177,26 @@ class PCRProcessor @Inject constructor( } companion object { + private val FINAL_STATES = setOf(PCR_POSITIVE, PCR_NEGATIVE, PCR_REDEEMED) private const val TAG = "PCRProcessor" } } -private fun CoronaTestResult.validOrThrow() { +private fun CoronaTestResult.validOrThrow(): CoronaTestResult { val isValid = when (this) { - CoronaTestResult.PCR_OR_RAT_PENDING, - CoronaTestResult.PCR_NEGATIVE, - CoronaTestResult.PCR_POSITIVE, - CoronaTestResult.PCR_INVALID, - CoronaTestResult.PCR_REDEEMED -> true - - CoronaTestResult.RAT_PENDING, - CoronaTestResult.RAT_NEGATIVE, - CoronaTestResult.RAT_POSITIVE, - CoronaTestResult.RAT_INVALID, - CoronaTestResult.RAT_REDEEMED -> false + PCR_OR_RAT_PENDING, + PCR_NEGATIVE, + PCR_POSITIVE, + PCR_INVALID, + PCR_REDEEMED -> true + + RAT_PENDING, + RAT_NEGATIVE, + RAT_POSITIVE, + RAT_INVALID, + RAT_REDEEMED -> false } if (!isValid) throw IllegalArgumentException("Invalid testResult $this") + return this } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt index 2a45ebb8a01fbd08b4ef2e0568163986539c28f0..3e1335c76d5b33f87fb212e7b7b451ee8b3b53b5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/SubmissionStatePCR.kt @@ -6,13 +6,25 @@ import org.joda.time.Instant sealed class SubmissionStatePCR { object NoTest : SubmissionStatePCR(), CommonSubmissionStates.TestUnregistered + object FetchingResult : SubmissionStatePCR(), CommonSubmissionStates.TestFetching + object TestResultReady : SubmissionStatePCR() - object TestPositive : SubmissionStatePCR() - object TestNegative : SubmissionStatePCR() + + data class TestPositive( + override val testRegisteredAt: Instant + ) : SubmissionStatePCR(), CommonSubmissionStates.PositiveTest + + data class TestNegative( + override val testRegisteredAt: Instant + ) : SubmissionStatePCR(), CommonSubmissionStates.NegativeTest + object TestError : SubmissionStatePCR() + object TestInvalid : SubmissionStatePCR() + object TestPending : SubmissionStatePCR() + data class SubmissionDone( override val testRegisteredAt: Instant ) : SubmissionStatePCR(), CommonSubmissionStates.SubmissionDone diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt index 7cdf2cf5bcb094d3c11235626e7b2288a1737d3b..75216e43738f04e2cc2ad2f2943aca55b7cc5b51 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt @@ -57,12 +57,18 @@ data class RACoronaTest( override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN - fun getState(nowUTC: Instant): State { - // TODO - return State.PENDING + fun getState(nowUTC: Instant) = when (testResult) { + CoronaTestResult.PCR_OR_RAT_PENDING -> State.PENDING + CoronaTestResult.RAT_NEGATIVE -> State.NEGATIVE + CoronaTestResult.RAT_POSITIVE -> State.POSITIVE + CoronaTestResult.RAT_INVALID -> State.INVALID + CoronaTestResult.RAT_REDEEMED -> State.REDEEMED + else -> throw IllegalArgumentException("Invalid RAT test state $testResult") } - override val isSubmissionAllowed: Boolean = testResult == CoronaTestResult.RAT_POSITIVE + override val isPositive: Boolean = testResult == CoronaTestResult.RAT_POSITIVE + + override val isSubmissionAllowed: Boolean = isPositive && !isSubmitted enum class State { PENDING, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt index fa8ad42e982d75d0e766afd6ac74c2c7fe7adca5..e11e53071d35d16b9de90e79122d566464b5cd75 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenCoronaTestExtensions.kt @@ -1,7 +1,36 @@ package de.rki.coronawarnapp.coronatest.type.rapidantigen -fun RACoronaTest?.toSubmissionState(): SubmissionStateRAT { - if (this == null) return SubmissionStateRAT.NoTest +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest.State.INVALID +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest.State.NEGATIVE +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest.State.OUTDATED +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest.State.PENDING +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest.State.POSITIVE +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest.State.REDEEMED +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.FetchingResult +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.NoTest +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.TestError +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.TestInvalid +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.TestNegative +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.TestPending +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.TestPositive +import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT.TestResultReady +import de.rki.coronawarnapp.exception.http.CwaServerError +import org.joda.time.Instant - return SubmissionStateRAT.FetchingResult +fun RACoronaTest?.toSubmissionState(nowUTC: Instant = Instant.now()) = when { + this == null -> NoTest + isProcessing -> FetchingResult + lastError != null -> if (lastError is CwaServerError) TestPending else TestInvalid + else -> when (getState(nowUTC)) { + INVALID -> TestError + POSITIVE -> { + if (isViewed) TestPositive(testRegisteredAt = registeredAt) + else TestResultReady + } + NEGATIVE -> TestNegative(testRegisteredAt = registeredAt) + REDEEMED -> TestInvalid + PENDING -> TestPending + // TODO: Should be updated once the logic for OUTDATED tests is in + OUTDATED -> TestNegative(testRegisteredAt = registeredAt) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt index 6052fa0a06cc18df4e07161d807c579f3247792e..482f6af98084cf7c6ec736116954d8955a8c3d9d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt @@ -3,6 +3,16 @@ package de.rki.coronawarnapp.coronatest.type.rapidantigen import dagger.Reusable import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_REDEEMED import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor @@ -11,6 +21,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.util.TimeStamper +import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject @@ -28,13 +39,14 @@ class RapidAntigenProcessor @Inject constructor( val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.registrationIdentifier) - registrationData.testResult.validOrThrow() + val testResult = registrationData.testResult.validOrThrow() return RACoronaTest( identifier = request.identifier, registeredAt = timeStamper.nowUTC, registrationToken = registrationData.registrationToken, - testResult = registrationData.testResult, + testResult = testResult, + testResultReceivedAt = determineReceivedDate(null, testResult), testedAt = request.createdAt, firstName = request.firstName, lastName = request.lastName, @@ -48,13 +60,19 @@ class RapidAntigenProcessor @Inject constructor( throw UnsupportedOperationException("There are no TAN based RATs") } + private fun determineReceivedDate(oldTest: RACoronaTest?, newTestResult: CoronaTestResult): Instant? = when { + oldTest != null && FINAL_STATES.contains(oldTest.testResult) -> oldTest.testResultReceivedAt + FINAL_STATES.contains(newTestResult) -> timeStamper.nowUTC + else -> null + } + override suspend fun pollServer(test: CoronaTest): CoronaTest { return try { Timber.tag(TAG).v("pollServer(test=%s)", test) test as RACoronaTest - if (test.isSubmitted || test.isSubmissionAllowed) { - Timber.tag(TAG).w("Not refreshing already final test.") + if (test.isSubmitted) { + Timber.tag(TAG).w("Not refreshing, we have already submitted.") return test } @@ -115,24 +133,26 @@ class RapidAntigenProcessor @Inject constructor( } companion object { + private val FINAL_STATES = setOf(RAT_POSITIVE, RAT_NEGATIVE, RAT_REDEEMED) private const val TAG = "RapidAntigenProcessor" } } -private fun CoronaTestResult.validOrThrow() { +private fun CoronaTestResult.validOrThrow(): CoronaTestResult { val isValid = when (this) { - CoronaTestResult.PCR_OR_RAT_PENDING, - CoronaTestResult.RAT_PENDING, - CoronaTestResult.RAT_NEGATIVE, - CoronaTestResult.RAT_POSITIVE, - CoronaTestResult.RAT_INVALID, - CoronaTestResult.RAT_REDEEMED -> true - - CoronaTestResult.PCR_NEGATIVE, - CoronaTestResult.PCR_POSITIVE, - CoronaTestResult.PCR_INVALID, - CoronaTestResult.PCR_REDEEMED -> false + PCR_OR_RAT_PENDING, + RAT_PENDING, + RAT_NEGATIVE, + RAT_POSITIVE, + RAT_INVALID, + RAT_REDEEMED -> true + + PCR_NEGATIVE, + PCR_POSITIVE, + PCR_INVALID, + PCR_REDEEMED -> false } if (!isValid) throw IllegalArgumentException("Invalid testResult $this") + return this } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt index bbb6cd6474fb89385c762be591e47a486df038e3..8069a5d4d963f954333d0b22034c1204cacf5f68 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/SubmissionStateRAT.kt @@ -6,13 +6,25 @@ import org.joda.time.Instant sealed class SubmissionStateRAT { object NoTest : SubmissionStateRAT(), CommonSubmissionStates.TestUnregistered + object FetchingResult : SubmissionStateRAT(), CommonSubmissionStates.TestFetching + object TestResultReady : SubmissionStateRAT() - object TestPositive : SubmissionStateRAT() - object TestNegative : SubmissionStateRAT() + + data class TestPositive( + override val testRegisteredAt: Instant + ) : SubmissionStateRAT(), CommonSubmissionStates.PositiveTest + + data class TestNegative( + override val testRegisteredAt: Instant + ) : SubmissionStateRAT(), CommonSubmissionStates.NegativeTest + object TestError : SubmissionStateRAT() + object TestInvalid : SubmissionStateRAT() + object TestPending : SubmissionStateRAT() + data class SubmissionDone( override val testRegisteredAt: Instant ) : SubmissionStateRAT(), CommonSubmissionStates.SubmissionDone diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt index 0375e974226341dae587918b107e4e51374622d0..bea984361be38b2b0ce3e29aa119d48f5f46766e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/detectiontracker/DefaultExposureDetectionTracker.kt @@ -47,7 +47,7 @@ class DefaultExposureDetectionTracker @Inject constructor( val setupTimeoutEnforcer: (HotDataFlow<Map<String, TrackedExposureDetection>>) -> Unit = { hd -> flow<Unit> { while (true) { - hd.updateSafely { + hd.updateBlocking { val timeNow = timeStamper.nowUTC Timber.v("Running timeout check (now=%s): %s", timeNow, values) val timeoutLimit = appConfigProvider.currentConfig.first().overallDetectionTimeout @@ -84,7 +84,7 @@ class DefaultExposureDetectionTracker @Inject constructor( override fun trackNewExposureDetection(identifier: String) { Timber.i("trackNewExposureDetection(token=%s)", identifier) - detectionStates.updateSafely { + detectionStates.updateAsync { mutate { this[identifier] = TrackedExposureDetection( identifier = identifier, @@ -97,7 +97,7 @@ class DefaultExposureDetectionTracker @Inject constructor( override fun finishExposureDetection(identifier: String?, result: Result) { Timber.i("finishExposureDetection(token=%s, result=%s)", identifier, result) - detectionStates.updateSafely { + detectionStates.updateAsync { mutate { if (identifier == null) { val id = this.findUnfinishedOrCreateIdentifier() @@ -164,7 +164,7 @@ class DefaultExposureDetectionTracker @Inject constructor( override fun clear() { Timber.i("clear()") - detectionStates.updateSafely { + detectionStates.updateAsync { emptyMap() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt index 9c2f731aa42d867ba995c9c0576c63c5aff6d449..5e89ce916308b96724c92ce00219e9c5806a1813 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt @@ -18,7 +18,8 @@ object NotificationConstants { const val DEADMAN_NOTIFICATION_ID: NotificationId = 3 const val NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID: NotificationId = 110 - const val TEST_RESULT_AVAILABLE_NOTIFICATION_ID: NotificationId = 130 + const val PCR_TEST_RESULT_AVAILABLE_NOTIFICATION_ID: NotificationId = 130 const val INCORRECT_DEVICE_TIME_NOTIFICATION_ID: NotificationId = 140 + const val RAT_TEST_RESULT_AVAILABLE_NOTIFICATION_ID: NotificationId = 150 const val TRACELOCATION_AUTOCHECKOUT_NOTIFICATION_ID: NotificationId = 1001 } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationReceiver.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationReceiver.kt index ea0021a012cb782da316a10a98716e48d98512ce..b11c004a67e76b40da477cd2382af14a64750e8b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationReceiver.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationReceiver.kt @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import dagger.android.AndroidInjection +import de.rki.coronawarnapp.coronatest.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.notification.NotificationConstants.NOTIFICATION_ID import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_ID import timber.log.Timber @@ -20,7 +21,7 @@ class NotificationReceiver : BroadcastReceiver() { when (val notificationId = intent.getIntExtra(NOTIFICATION_ID, Int.MIN_VALUE)) { POSITIVE_RESULT_NOTIFICATION_ID -> { Timber.tag(TAG).v("NotificationReceiver received intent to show a positive test result notification") - shareTestResultNotificationService.showSharePositiveTestResultNotification(notificationId) + shareTestResultNotificationService.maybeShowSharePositiveTestResultNotification(notificationId) } else -> Timber.tag(TAG).d("NotificationReceiver received an undefined notificationId: %s", notificationId) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/PCRTestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/PCRTestResultAvailableNotificationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..15eb8d656cc17073b7ed87bc969b192878fe3c0a --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/PCRTestResultAvailableNotificationService.kt @@ -0,0 +1,26 @@ +package de.rki.coronawarnapp.notification + +import android.content.Context +import androidx.navigation.NavDeepLinkBuilder +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.util.device.ForegroundState +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject +import javax.inject.Provider + +class PCRTestResultAvailableNotificationService @Inject constructor( + @AppContext context: Context, + foregroundState: ForegroundState, + navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder>, + notificationHelper: GeneralNotifications, + cwaSettings: CWASettings +) : TestResultAvailableNotificationService( + context, + foregroundState, + navDeepLinkBuilderProvider, + notificationHelper, + cwaSettings, + NotificationConstants.PCR_TEST_RESULT_AVAILABLE_NOTIFICATION_ID, + R.id.submissionTestResultPendingFragment +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/RATTestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/RATTestResultAvailableNotificationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..2d85badd449cee0bd2c4968fb89e110fec6f82f5 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/RATTestResultAvailableNotificationService.kt @@ -0,0 +1,26 @@ +package de.rki.coronawarnapp.notification + +import android.content.Context +import androidx.navigation.NavDeepLinkBuilder +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.util.device.ForegroundState +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject +import javax.inject.Provider + +class RATTestResultAvailableNotificationService @Inject constructor( + @AppContext context: Context, + foregroundState: ForegroundState, + navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder>, + notificationHelper: GeneralNotifications, + cwaSettings: CWASettings +) : TestResultAvailableNotificationService( + context, + foregroundState, + navDeepLinkBuilderProvider, + notificationHelper, + cwaSettings, + NotificationConstants.RAT_TEST_RESULT_AVAILABLE_NOTIFICATION_ID, + R.id.submissionTestResultPendingFragment // TODO check nav args, after other PRs are merged +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt deleted file mode 100644 index fb02d7f334100194376287286f30f75d19b52938..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/ShareTestResultNotificationService.kt +++ /dev/null @@ -1,74 +0,0 @@ -package de.rki.coronawarnapp.notification - -import android.content.Context -import androidx.navigation.NavDeepLinkBuilder -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_ID -import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET -import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_INTERVAL -import de.rki.coronawarnapp.notification.NotificationConstants.POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT -import de.rki.coronawarnapp.ui.main.MainActivity -import de.rki.coronawarnapp.util.TimeStamper -import de.rki.coronawarnapp.util.di.AppContext -import de.rki.coronawarnapp.util.notifications.setContentTextExpandable -import timber.log.Timber -import javax.inject.Inject - -class ShareTestResultNotificationService @Inject constructor( - @AppContext private val context: Context, - private val timeStamper: TimeStamper, - private val notificationHelper: GeneralNotifications, - private val cwaSettings: CWASettings -) { - - fun scheduleSharePositiveTestResultReminder() { - if (cwaSettings.numberOfRemainingSharePositiveTestResultReminders < 0) { - Timber.v("Schedule positive test result notification") - cwaSettings.numberOfRemainingSharePositiveTestResultReminders = POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT - notificationHelper.scheduleRepeatingNotification( - timeStamper.nowUTC.plus(POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET), - POSITIVE_RESULT_NOTIFICATION_INTERVAL, - POSITIVE_RESULT_NOTIFICATION_ID - ) - } else { - Timber.v("Positive test result notification has already been scheduled") - } - } - - fun showSharePositiveTestResultNotification(notificationId: Int) { - if (cwaSettings.numberOfRemainingSharePositiveTestResultReminders > 0) { - cwaSettings.numberOfRemainingSharePositiveTestResultReminders -= 1 - - val pendingIntent = NavDeepLinkBuilder(context) - .setGraph(R.navigation.nav_graph) - .setComponentName(MainActivity::class.java) - .setDestination(R.id.submissionTestResultAvailableFragment) - .createPendingIntent() - - val notification = notificationHelper.newBaseBuilder().apply { - setContentTitle(context.getString(R.string.notification_headline_share_positive_result)) - setContentTextExpandable(context.getString(R.string.notification_body_share_positive_result)) - setContentIntent(pendingIntent) - }.build() - - notificationHelper.sendNotification( - notificationId = notificationId, - notification = notification, - ) - } else { - notificationHelper.cancelFutureNotifications(notificationId) - } - } - - fun cancelSharePositiveTestResultNotification() { - notificationHelper.cancelFutureNotifications(POSITIVE_RESULT_NOTIFICATION_ID) - Timber.v("Future positive test result notifications have been canceled") - } - - fun resetSharePositiveTestResultNotification() { - cancelSharePositiveTestResultNotification() - cwaSettings.numberOfRemainingSharePositiveTestResultReminders = Int.MIN_VALUE - Timber.v("Positive test result notification counter has been reset") - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt index c0b7a2943500af8bcc434d11c042a15bfb5f7de5..db751afd8659e75c4e6c9619f7b86ba60692742a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationService.kt @@ -1,25 +1,26 @@ package de.rki.coronawarnapp.notification import android.content.Context +import androidx.annotation.IdRes import androidx.navigation.NavDeepLinkBuilder import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.util.device.ForegroundState -import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.notifications.setContentTextExpandable import kotlinx.coroutines.flow.first import timber.log.Timber -import javax.inject.Inject import javax.inject.Provider -class TestResultAvailableNotificationService @Inject constructor( - @AppContext private val context: Context, +open class TestResultAvailableNotificationService( + private val context: Context, private val foregroundState: ForegroundState, private val navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder>, private val notificationHelper: GeneralNotifications, - private val cwaSettings: CWASettings + private val cwaSettings: CWASettings, + private val notificationId: NotificationId, + @IdRes private val destination: Int ) { suspend fun showTestResultAvailableNotification(testResult: CoronaTestResult) { @@ -38,7 +39,16 @@ class TestResultAvailableNotificationService @Inject constructor( val pendingIntent = navDeepLinkBuilderProvider.get().apply { setGraph(R.navigation.nav_graph) setComponentName(MainActivity::class.java) - setDestination(getNotificationDestination(testResult)) + /* + * The pending result fragment will forward to the correct screen + * Because we can't save the test result at the moment (legal), + * it needs to be reloaded each time. + * If we navigate directly to the positive/negative result screen, + * then we also need to add explicit test loading logic there. + * By letting the forwarding happen via the PendingResultFragment, + * we have a common location to retrieve the test result. + */ + setDestination(destination) }.createPendingIntent() val notification = notificationHelper.newBaseBuilder().apply { @@ -49,23 +59,12 @@ class TestResultAvailableNotificationService @Inject constructor( Timber.i("Showing TestResultAvailable notification!") notificationHelper.sendNotification( - notificationId = NotificationConstants.TEST_RESULT_AVAILABLE_NOTIFICATION_ID, + notificationId = notificationId, notification = notification, ) } fun cancelTestResultAvailableNotification() { - notificationHelper.cancelCurrentNotification(NotificationConstants.TEST_RESULT_AVAILABLE_NOTIFICATION_ID) + notificationHelper.cancelCurrentNotification(notificationId) } - - /** - * The pending result fragment will forward to the correct screen - * Because we can't save the test result at the moment (legal), - * it needs to be reloaded each time. - * If we navigate directly to the positive/negative result screen, - * then we also need to add explicit test loading logic there. - * By letting the forwarding happen via the PendingResultFragment, - * we have a common location to retrieve the test result. - */ - fun getNotificationDestination(testResult: CoronaTestResult): Int = R.id.submissionTestResultPendingFragment } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/source/StatisticsProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/source/StatisticsProvider.kt index 3d254d25e69e67155345cda31c084c9238453d27..87736b0e50e144c7a1daa9f2cb11600f3c0a9620 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/source/StatisticsProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/source/StatisticsProvider.kt @@ -78,14 +78,10 @@ class StatisticsProvider @Inject constructor( fun triggerUpdate() { Timber.tag(TAG).d("triggerUpdate()") - statisticsData.updateSafely { - try { - fromServer() - } catch (e: Exception) { - Timber.tag(TAG).e(e, "Failed to update statistics.") - this@updateSafely // return previous data - } - } + statisticsData.updateAsync( + onUpdate = { fromServer() }, + onError = { Timber.tag(TAG).e(it, "Failed to update statistics.") } + ) } fun clear() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt index 49c162c39401af971dd5e0135121b419c30f55f2..85f17678359c8ef09b11ae6427017d2ff3c819a6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/task/SubmissionTask.kt @@ -4,10 +4,10 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository import de.rki.coronawarnapp.presencetracing.checkins.CheckInsTransformer @@ -41,7 +41,7 @@ class SubmissionTask @Inject constructor( private val autoSubmission: AutoSubmission, private val timeStamper: TimeStamper, private val shareTestResultNotificationService: ShareTestResultNotificationService, - private val testResultAvailableNotificationService: TestResultAvailableNotificationService, + private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, private val checkInsRepository: CheckInRepository, private val checkInsTransformer: CheckInsTransformer, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, @@ -129,7 +129,7 @@ class SubmissionTask @Inject constructor( private suspend fun performSubmission(): Result { val availableTests = coronaTestRepository.coronaTests.first() Timber.tag(TAG).v("Available tests: %s", availableTests) - val coronaTest = availableTests.firstOrNull { it.isSubmissionAllowed && !it.isSubmitted } + val coronaTest = availableTests.firstOrNull { it.isSubmissionAllowed } ?: throw IllegalStateException("No valid test available to authorize submission") Timber.tag(TAG).d("Submission is authorized by coronaTest=%s", coronaTest) @@ -199,7 +199,6 @@ class SubmissionTask @Inject constructor( coronaTestRepository.markAsSubmitted(coronaTest.identifier) backgroundWorkScheduler.startWorkScheduler() - shareTestResultNotificationService.cancelSharePositiveTestResultNotification() testResultAvailableNotificationService.cancelTestResultAvailableNotification() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt index b9ae26372a44fea01df80384b7debbe5777396ab..2441c9d54171e06dc09832f61f17fa54bce6de3f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestNegativeCard.kt @@ -26,10 +26,18 @@ class PcrTestNegativeCard( override val onBindData: HomeSubmissionPcrStatusCardNegativeBinding.( item: Item, payloads: List<Any> - ) -> Unit = { _, _ -> } + ) -> Unit = { item, payloads -> + val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item + + val userDate = curItem.state.getFormattedRegistrationDate() + date.text = resources.getString(R.string.ag_homescreen_card_pcr_body_result_date, userDate) + + itemView.setOnClickListener { curItem.onClickAction(item) } + } data class Item( - val state: SubmissionStatePCR.TestNegative + val state: SubmissionStatePCR.TestNegative, + val onClickAction: (Item) -> Unit ) : TestResultItem.PCR, HasPayloadDiffer { override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt index 327ea83f7ae04c1f08fd1b950e8950beca79e9d7..5d52d9acdaaf1c2ebe84b0f9a60e2380357cbef9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestPositiveCard.kt @@ -28,6 +28,10 @@ class PcrTestPositiveCard( payloads: List<Any> ) -> Unit = { item, payloads -> val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item + + val userDate = curItem.state.getFormattedRegistrationDate() + date.text = resources.getString(R.string.ag_homescreen_card_pcr_body_result_date, userDate) + itemView.setOnClickListener { curItem.onClickAction(item) } submissionStatusCardPositiveButton.setOnClickListener { itemView.performClick() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt index 34ecfef89a27bc2c69769620493dc956f9eb07d6..9cb260b99330b0006922b70d69f3d6ec14cd3960 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/PcrTestSubmissionDoneCard.kt @@ -22,7 +22,12 @@ class PcrTestSubmissionDoneCard( override val onBindData: HomeSubmissionPcrStatusCardPositiveSharedBinding.( item: Item, payloads: List<Any> - ) -> Unit = { _, _ -> } + ) -> Unit = { item, payloads -> + val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item + + val userDate = curItem.state.getFormattedRegistrationDate() + date.text = resources.getString(R.string.ag_homescreen_card_pcr_body_result_date, userDate) + } data class Item( val state: SubmissionStatePCR.SubmissionDone diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt index f8ae43ad4edb42a2d070f5cb21f3e250c36c8490..930aeef2b4789426a8de1809b6fa1f701aaf15b3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestNegativeCard.kt @@ -26,10 +26,18 @@ class RapidTestNegativeCard( override val onBindData: HomeSubmissionRapidStatusCardNegativeBinding.( item: Item, payloads: List<Any> - ) -> Unit = { _, _ -> } + ) -> Unit = { item, payloads -> + val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item + + val userDate = curItem.state.getFormattedRegistrationDate() + date.text = resources.getString(R.string.ag_homescreen_card_rapid_body_result_date, userDate) + + itemView.setOnClickListener { curItem.onClickAction(item) } + } data class Item( - val state: SubmissionStateRAT.TestNegative + val state: SubmissionStateRAT.TestNegative, + val onClickAction: (Item) -> Unit ) : TestResultItem.RA, HasPayloadDiffer { override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt index e9c021499f4ee97962892bbbd762a18e92d7d77e..4b244824e66be08097bea31ec4f26035307f8c82 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestPositiveCard.kt @@ -28,6 +28,10 @@ class RapidTestPositiveCard( payloads: List<Any> ) -> Unit = { item, payloads -> val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item + + val userDate = curItem.state.getFormattedRegistrationDate() + date.text = resources.getString(R.string.ag_homescreen_card_rapid_body_result_date, userDate) + itemView.setOnClickListener { curItem.onClickAction(item) } submissionStatusCardPositiveButton.setOnClickListener { itemView.performClick() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt index a96d2a531c24f998838019bf19bfa1cbfe1f5f09..c6e3866a09b30c10f864d7b732150a7f7c16c0fe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/RapidTestSubmissionDoneCard.kt @@ -22,7 +22,12 @@ class RapidTestSubmissionDoneCard( override val onBindData: HomeSubmissionRapidStatusCardPositiveSharedBinding.( item: Item, payloads: List<Any> - ) -> Unit = { _, _ -> } + ) -> Unit = { item, payloads -> + val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item + + val userDate = curItem.state.getFormattedRegistrationDate() + date.text = resources.getString(R.string.ag_homescreen_card_rapid_body_result_date, userDate) + } data class Item( val state: SubmissionStateRAT.SubmissionDone diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt index 2271042d492467547db54f49f2825fb3e875f328..6d7e4ec9f88c0db0a88e0bc240db90fef47bf0a0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/TestResultItem.kt @@ -1,6 +1,8 @@ package de.rki.coronawarnapp.submission.ui.homecards import de.rki.coronawarnapp.ui.main.home.items.HomeItem +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone +import org.joda.time.Instant interface TestResultItem : HomeItem { override val stableId: Long @@ -23,4 +25,6 @@ interface TestResultItem : HomeItem { val LIST_ID = PCR::class.java.name.hashCode().toLong() } } + + fun Instant.formatAsUserTestRegisteredAt(): String = toUserTimeZone().toLocalDate().toString("dd.MM.yy") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..082b63819b5f409c2ec72fd4a650f0621c337545 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeFragment.kt @@ -0,0 +1,24 @@ +package de.rki.coronawarnapp.submission.ui.testresults.negative + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentSubmissionAntigenTestResultNegativeBinding +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import javax.inject.Inject + +class RATResultNegativeFragment : Fragment(R.layout.fragment_submission_antigen_test_result_negative), AutoInject { + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel: RATResultNegativeViewModel by cwaViewModels { viewModelFactory } + + private val binding: FragmentSubmissionAntigenTestResultNegativeBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + // TODO: Add fragment logic and databinding. + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..8476430efdc7413e96120d25db842fc5db33dad1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeModule.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.submission.ui.testresults.negative + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey + +@Module +abstract class RATResultNegativeModule { + @Binds + @IntoMap + @CWAViewModelKey(RATResultNegativeViewModel::class) + abstract fun ratResultNegativeFragment( + factory: RATResultNegativeViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..ee69cae57e7ddec41d54d482494044998780a738 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/testresults/negative/RATResultNegativeViewModel.kt @@ -0,0 +1,15 @@ +package de.rki.coronawarnapp.submission.ui.testresults.negative + +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory + +class RATResultNegativeViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider +) : CWAViewModel(dispatcherProvider) { + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<RATResultNegativeViewModel> +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt index 7ebebc73a22e6aef5ae2753807fa505df2e42540..e5f992ed1a0b94b0c3bed9104ba16104e3c139d9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/TracingExplanationDialog.kt @@ -26,7 +26,7 @@ class TracingExplanationDialog @Inject constructor( message = "$infoPeriodLogged\n$infoPeriodLoggedAssessment\n\n$infoFAQ", positiveButton = context.getString(R.string.errors_generic_button_positive), negativeButton = null, - cancelable = null, + cancelable = false, positiveButtonFunction = onPositive, negativeButtonFunction = {} ) 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 f2596fefb4c970957ce572717bbec87fc3df2cb2..ad760fe105b7e6761951322bf58af0442cdbf7da 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 @@ -121,8 +121,6 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { if (!showDialog) return@observe2 deviceTimeIncorrectDialog.show { viewModel.userHasAcknowledgedIncorrectDeviceTime() } } - - viewModel.observeTestResultToSchedulePositiveTestResultReminder() } override fun onResume() { 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 6a761f77735f46cb759bc32d52cf1881e27c17da..ff35f3915795045b9d37d746d8a55c58e1f90ef8 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 @@ -19,13 +19,11 @@ import de.rki.coronawarnapp.coronatest.type.rapidantigen.SubmissionStateRAT import de.rki.coronawarnapp.coronatest.type.rapidantigen.toSubmissionState import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.submission.toDeviceUIState import de.rki.coronawarnapp.submission.ui.homecards.PcrTestErrorCard import de.rki.coronawarnapp.submission.ui.homecards.PcrTestInvalidCard import de.rki.coronawarnapp.submission.ui.homecards.PcrTestNegativeCard @@ -63,7 +61,6 @@ import de.rki.coronawarnapp.ui.main.home.items.FAQCard import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.ui.main.home.items.ReenableRiskCard import de.rki.coronawarnapp.ui.presencetracing.organizer.TraceLocationOrganizerSettings -import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.encryptionmigration.EncryptionErrorResetTool import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper @@ -72,7 +69,6 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -84,7 +80,6 @@ class HomeFragmentViewModel @AssistedInject constructor( tracingStateProviderFactory: TracingStateProvider.Factory, private val coronaTestRepository: CoronaTestRepository, private val tracingRepository: TracingRepository, - private val shareTestResultNotificationService: ShareTestResultNotificationService, private val submissionRepository: SubmissionRepository, private val cwaSettings: CWASettings, appConfigProvider: AppConfigProvider, @@ -181,29 +176,33 @@ class HomeFragmentViewModel @AssistedInject constructor( is SubmissionStatePCR.FetchingResult -> TestFetchingCard.Item(state) is SubmissionStatePCR.TestResultReady -> PcrTestReadyCard.Item(state) { routeToScreen.postValue( - HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment() + HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment(CoronaTest.Type.PCR) ) } is SubmissionStatePCR.TestPositive -> PcrTestPositiveCard.Item(state) { routeToScreen.postValue( HomeFragmentDirections - .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment() + .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment(CoronaTest.Type.PCR) + ) + } + is SubmissionStatePCR.TestNegative -> PcrTestNegativeCard.Item(state) { + routeToScreen.postValue( + HomeFragmentDirections.actionMainFragmentToSubmissionTestResultNegativeFragment() ) } - is SubmissionStatePCR.TestNegative -> PcrTestNegativeCard.Item(state) is SubmissionStatePCR.TestInvalid -> PcrTestInvalidCard.Item(state) { popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog) } is SubmissionStatePCR.TestError -> PcrTestErrorCard.Item(state) { routeToScreen.postValue( HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultPendingFragment() + .actionMainFragmentToSubmissionTestResultPendingFragment(testType = CoronaTest.Type.PCR) ) } is SubmissionStatePCR.TestPending -> PcrTestPendingCard.Item(state) { routeToScreen.postValue( HomeFragmentDirections - .actionMainFragmentToSubmissionTestResultPendingFragment() + .actionMainFragmentToSubmissionTestResultPendingFragment(testType = CoronaTest.Type.PCR) ) } is SubmissionStatePCR.SubmissionDone -> PcrTestSubmissionDoneCard.Item(state) @@ -211,41 +210,43 @@ class HomeFragmentViewModel @AssistedInject constructor( private fun RACoronaTest?.toTestCardItem() = when (val state = this.toSubmissionState()) { is SubmissionStateRAT.NoTest -> TestUnregisteredCard.Item(state) { - // TODO -// routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher()) + routeToScreen.postValue(HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher()) } is SubmissionStateRAT.FetchingResult -> TestFetchingCard.Item(state) is SubmissionStateRAT.TestResultReady -> RapidTestReadyCard.Item(state) { - // TODO -// routeToScreen.postValue( -// HomeFragmentDirections.actionMainFragmentToSubmissionTestResultAvailableFragment() -// ) + routeToScreen.postValue( + HomeFragmentDirections + .actionMainFragmentToSubmissionTestResultAvailableFragment(CoronaTest.Type.RAPID_ANTIGEN) + ) } is SubmissionStateRAT.TestPositive -> RapidTestPositiveCard.Item(state) { - // TODO -// routeToScreen.postValue( -// HomeFragmentDirections -// .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment() -// ) + routeToScreen.postValue( + HomeFragmentDirections + .actionMainFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment( + CoronaTest.Type.RAPID_ANTIGEN + ) + ) + } + is SubmissionStateRAT.TestNegative -> RapidTestNegativeCard.Item(state) { + routeToScreen.postValue( + HomeFragmentDirections + .actionMainFragmentToSubmissionNegativeAntigenTestResultFragment() + ) } - is SubmissionStateRAT.TestNegative -> RapidTestNegativeCard.Item(state) is SubmissionStateRAT.TestInvalid -> RapidTestInvalidCard.Item(state) { - // TODO -// popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog) + popupEvents.postValue(HomeFragmentEvents.ShowDeleteTestDialog) } is SubmissionStateRAT.TestError -> RapidTestErrorCard.Item(state) { - // TODO -// routeToScreen.postValue( -// HomeFragmentDirections -// .actionMainFragmentToSubmissionTestResultPendingFragment() -// ) + routeToScreen.postValue( + HomeFragmentDirections + .actionMainFragmentToSubmissionTestResultPendingFragment(testType = CoronaTest.Type.RAPID_ANTIGEN) + ) } is SubmissionStateRAT.TestPending -> RapidTestPendingCard.Item(state) { - // TODO -// routeToScreen.postValue( -// HomeFragmentDirections -// .actionMainFragmentToSubmissionTestResultPendingFragment() -// ) + routeToScreen.postValue( + HomeFragmentDirections + .actionMainFragmentToSubmissionTestResultPendingFragment(testType = CoronaTest.Type.RAPID_ANTIGEN) + ) } is SubmissionStateRAT.SubmissionDone -> RapidTestSubmissionDoneCard.Item(state) } @@ -270,10 +271,29 @@ class HomeFragmentViewModel @AssistedInject constructor( else -> add(tracingItem) } - add(testPCR.toTestCardItem()) - - if (stateRAT != SubmissionStateRAT.NoTest || statePCR != SubmissionStatePCR.NoTest) { - add(testRAT.toTestCardItem()) + // TODO: Would be nice to have a more elegant solution of displaying the result cards in the right order + when (statePCR) { + SubmissionStatePCR.NoTest -> { + if (stateRAT == SubmissionStateRAT.NoTest) { + add(testPCR.toTestCardItem()) + } else { + add(testRAT.toTestCardItem()) + add(testPCR.toTestCardItem()) + } + } + else -> { + add(testPCR.toTestCardItem()) + if (stateRAT != SubmissionStateRAT.NoTest) { + add(testRAT.toTestCardItem()) + add( + TestUnregisteredCard.Item(SubmissionStatePCR.NoTest) { + routeToScreen.postValue( + HomeFragmentDirections.actionMainFragmentToSubmissionDispatcher() + ) + } + ) + } else add(testRAT.toTestCardItem()) + } } bothTestStates.firstOrNull { it is CommonSubmissionStates.SubmissionDone }?.let { @@ -305,27 +325,14 @@ class HomeFragmentViewModel @AssistedInject constructor( .distinctUntilChanged() .asLiveData(dispatcherProvider.Default) - private var isLoweredRiskLevelDialogBeingShown = false - fun observeTestResultToSchedulePositiveTestResultReminder() = launch { - submissionRepository.pcrTest - .first { test -> - when { - test == null -> false - test.lastError != null -> false - test.testResult.toDeviceUIState() == DeviceUIState.PAIRED_POSITIVE -> true - test.testResult.toDeviceUIState() == DeviceUIState.PAIRED_POSITIVE_TELETAN -> true - else -> false - } - } - .also { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } - } - fun reenableRiskCalculation() { deregisterWarningAccepted() deadmanNotificationScheduler.schedulePeriodic() refreshRiskResult() } + 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 @@ -364,7 +371,6 @@ class HomeFragmentViewModel @AssistedInject constructor( fun deregisterWarningAccepted() { submissionRepository.removeTestFromDevice(type = CoronaTest.Type.PCR) - submissionRepository.refreshTest(type = CoronaTest.Type.PCR) } fun userHasAcknowledgedTheLoweredRiskLevel() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/common/TraceLocationCardHighlightView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/common/TraceLocationCardHighlightView.kt index 9db690c9c246b6562782953810ff91f623127f4a..83f5d258b41d7733d0295bbb95fa5da39cd9d1e5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/common/TraceLocationCardHighlightView.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/common/TraceLocationCardHighlightView.kt @@ -25,8 +25,6 @@ class TraceLocationCardHighlightView @JvmOverloads constructor( init { LayoutInflater.from(context).inflate(R.layout.trace_location_view_cardhighlight, this, true) - background = ContextCompat.getDrawable(context, R.drawable.trace_location_view_cardhighlight_background) - context.withStyledAttributes(attrs, R.styleable.TraceLocationHighlightView) { val captionText = getText(R.styleable.TraceLocationHighlightView_android_text) ?: "" setCaption(captionText.toString()) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateFragment.kt index 4843c35ccac9c53e05cce240cd9357bdc2e5e88d..64348cc5adb780b0d342e0caeaf8ab0d0c79c9b8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateFragment.kt @@ -25,9 +25,9 @@ import de.rki.coronawarnapp.util.ui.popBackStack import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted +import org.joda.time.DateTime import org.joda.time.Duration import org.joda.time.LocalDate -import org.joda.time.LocalDateTime import org.joda.time.LocalTime import javax.inject.Inject @@ -121,8 +121,8 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag placeInputEdit.setText(it.address) } viewModel.apply { - begin = it.startDate?.let { time -> LocalDateTime(time) } - end = it.endDate?.let { time -> LocalDateTime(time) } + begin = it.startDate?.toDateTime() + end = it.endDate?.toDateTime() checkInLength = Duration.standardMinutes(it.defaultCheckInLengthInMinutes?.toLong() ?: 0L) } } @@ -152,9 +152,9 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag } private fun showDatePicker( - defaultValue: LocalDateTime?, - minConstraint: LocalDateTime? = null, - callback: (LocalDateTime) -> Unit + defaultValue: DateTime?, + minConstraint: DateTime? = null, + callback: (DateTime) -> Unit ) { MaterialDatePicker .Builder @@ -181,7 +181,7 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag .show(childFragmentManager, DATE_PICKER_TAG) } - private fun showTimePicker(date: LocalDate, hours: Int?, minutes: Int?, callback: (LocalDateTime) -> Unit) { + private fun showTimePicker(date: LocalDate, hours: Int?, minutes: Int?, callback: (DateTime) -> Unit) { MaterialTimePicker .Builder() .setTimeFormat(if (is24HourFormat(requireContext())) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H) @@ -194,7 +194,7 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag .build() .apply { addOnPositiveButtonClickListener { - callback(date.toLocalDateTime(LocalTime(this.hour, this.minute))) + callback(date.toDateTime(LocalTime(this.hour, this.minute))) } } .show(childFragmentManager, TIME_PICKER_TAG) @@ -228,8 +228,8 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag savedInstanceState?.getLong(LENGTH_OF_STAY)?.let { viewModel.checkInLength = Duration.standardMinutes(it) } - viewModel.begin = savedInstanceState?.getSerializable(BEGIN) as LocalDateTime? - viewModel.end = savedInstanceState?.getSerializable(END) as LocalDateTime? + viewModel.begin = savedInstanceState?.getSerializable(BEGIN) as DateTime? + viewModel.end = savedInstanceState?.getSerializable(END) as DateTime? } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModel.kt index 4e862e265e57d3cb0d9cd7c41bb3a8f8405fa179..29976a36cb93c1afb1b495e72df06335aca4aab1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModel.kt @@ -19,8 +19,8 @@ import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import org.joda.time.DateTime import org.joda.time.Duration -import org.joda.time.LocalDateTime import timber.log.Timber import java.util.Locale import kotlin.properties.ReadWriteProperty @@ -42,8 +42,8 @@ class TraceLocationCreateViewModel @AssistedInject constructor( var description: String by UpdateDelegateWithDefaultValue("") var address: String by UpdateDelegateWithDefaultValue("") var checkInLength: Duration by UpdateDelegateWithDefaultValue(Duration.ZERO) - var begin: LocalDateTime? by UpdateDelegate() - var end: LocalDateTime? by UpdateDelegate() + var begin: DateTime? by UpdateDelegate() + var end: DateTime? by UpdateDelegate() init { checkInLength = when (category.uiType) { @@ -111,8 +111,8 @@ class TraceLocationCreateViewModel @AssistedInject constructor( private fun String.isTextFormattedCorrectly() = trim().length in 1..100 && !contains('\n') data class UIState( - private val begin: LocalDateTime? = null, - private val end: LocalDateTime? = null, + private val begin: DateTime? = null, + private val end: DateTime? = null, private val checkInLength: Duration? = null, @StringRes val title: Int, val isRequestInProgress: Boolean, @@ -129,7 +129,7 @@ class TraceLocationCreateViewModel @AssistedInject constructor( ) } - private fun getFormattedTime(value: LocalDateTime?, locale: Locale) = + private fun getFormattedTime(value: DateTime?, locale: Locale) = value?.toString("E, ${locale.shortDatePattern()} HH:mm", locale) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt index 2607499099a7fe0790bb63e57acfb8b70dd3a318..3dc3137e1e4bb77feb965150d3a7d7b7a4bd4883 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailFragment.kt @@ -125,7 +125,7 @@ class QrCodeDetailFragment : Fragment(R.layout.trace_location_organizer_qr_code_ requireContext().getString( R.string.trace_location_organizer_detail_item_duration_multiple_days, startTime.toLocalDate().toString("dd.MM.yyyy"), - endTime.toLocalTime().toString("HH:mm"), + startTime.toLocalTime().toString("HH:mm"), endTime.toLocalDate().toString("dd.MM.yyyy"), endTime.toLocalTime().toString("HH:mm") ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailViewModel.kt index 0a33ec89e6e88a184b0da4018dc37af1e500f67e..e69e39631b58c76d47ac1b3ebe9780bc544c7210 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/presencetracing/organizer/details/QrCodeDetailViewModel.kt @@ -8,7 +8,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.presencetracing.checkins.qrcode.QrCodeGenerator import de.rki.coronawarnapp.presencetracing.checkins.qrcode.TraceLocation -import de.rki.coronawarnapp.presencetracing.storage.repo.DefaultTraceLocationRepository +import de.rki.coronawarnapp.presencetracing.storage.repo.TraceLocationRepository import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.ui.presencetracing.organizer.category.adapter.category.traceLocationCategories @@ -24,7 +24,7 @@ class QrCodeDetailViewModel @AssistedInject constructor( @Assisted private val traceLocationId: Long, private val dispatcher: DispatcherProvider, private val qrCodeGenerator: QrCodeGenerator, - private val traceLocationRepository: DefaultTraceLocationRepository + private val traceLocationRepository: TraceLocationRepository ) : CWAViewModel() { private var traceLocation: TraceLocation? = null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt index 14459385664ce333363ac8b4bf4a86769f0f920b..aac4f54a8698348da4a06949c581e571c607289f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt @@ -6,7 +6,6 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.util.DataReset import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper @@ -18,7 +17,6 @@ import de.rki.coronawarnapp.worker.BackgroundWorkScheduler class SettingsResetViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val dataReset: DataReset, - private val shareTestResultNotificationService: ShareTestResultNotificationService, private val shortcutsHelper: AppShortcutsHelper, private val backgroundWorkScheduler: BackgroundWorkScheduler, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { @@ -49,7 +47,6 @@ class SettingsResetViewModel @AssistedInject constructor( null ) } - shareTestResultNotificationService.resetSharePositiveTestResultNotification() dataReset.clearAllLocalData() shortcutsHelper.removeAppShortcut() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt index 8a918bfad256615ff081bfd5d0748bb8a06e4d43..b0ceeaeef6310f2e1ea69c84be527c4c1d6107ad 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragment.kt @@ -3,19 +3,43 @@ package de.rki.coronawarnapp.ui.submission.deletionwarning import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent +import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.databinding.FragmentSubmissionDeletionWarningBinding +import de.rki.coronawarnapp.exception.http.BadRequestException +import de.rki.coronawarnapp.exception.http.CwaClientError +import de.rki.coronawarnapp.exception.http.CwaServerError +import de.rki.coronawarnapp.exception.http.CwaWebException +import de.rki.coronawarnapp.ui.submission.ApiRequestState +import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents +import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.ui.doNavigate +import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider -import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted import javax.inject.Inject class SubmissionDeletionWarningFragment : Fragment(R.layout.fragment_submission_deletion_warning), AutoInject { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val viewModel: SubmissionDeletionWarningFragmentViewModel by cwaViewModels { viewModelFactory } + + private val args by navArgs<SubmissionDeletionWarningFragmentArgs>() + val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() + + private val viewModel: SubmissionDeletionWarningViewModel by cwaViewModelsAssisted( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, _ -> + factory as SubmissionDeletionWarningViewModel.Factory + factory.create(args.coronaTestQrCode, args.isConsentGiven) + } + ) private val binding: FragmentSubmissionDeletionWarningBinding by viewBindingLazy() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -23,14 +47,121 @@ class SubmissionDeletionWarningFragment : Fragment(R.layout.fragment_submission_ binding.apply { - cancelButton.setOnClickListener { /* TODO */ } + when (args.coronaTestQrCode.type) { + CoronaTest.Type.PCR -> { + headline.text = getString(R.string.submission_deletion_warning_headline_pcr_test) + body.text = getString(R.string.submission_deletion_warning_body_pcr_test) + } - continueButton.setOnClickListener { /* TODO */ } + CoronaTest.Type.RAPID_ANTIGEN -> { + headline.text = getString(R.string.submission_deletion_warning_headline_antigen_test) + body.text = getString(R.string.submission_deletion_warning_body_antigen_test) + } + } + + continueButton.setOnClickListener { + viewModel.deleteExistingAndRegisterNewTest() + } - toolbar.apply { - setNavigationOnClickListener { /* TODO */ } + toolbar.setNavigationOnClickListener { + viewModel.onCancelButtonClick() } } + + viewModel.showRedeemedTokenWarning.observe2(this) { + val dialog = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_tan_redeemed_title, + R.string.submission_error_dialog_web_tan_redeemed_body, + R.string.submission_error_dialog_web_tan_redeemed_button_positive + ) + + DialogHelper.showDialog(dialog) + + navigateToDispatchScreen() + } + + viewModel.registrationState.observe2(this) { state -> + binding.submissionQrCodeScanSpinner.isVisible = state.apiRequestState == ApiRequestState.STARTED + + if (ApiRequestState.SUCCESS == state.apiRequestState) { + if (state.testResult == CoronaTestResult.PCR_POSITIVE) { + viewModel.triggerNavigationToSubmissionTestResultAvailableFragment() + } else { + viewModel.triggerNavigationToSubmissionTestResultPendingFragment() + } + } + } + + viewModel.registrationError.observe2(this) { + DialogHelper.showDialog(buildErrorDialog(it)) + } + + viewModel.routeToScreen.observe2(this) { + when (it) { + SubmissionNavigationEvents.NavigateToConsent -> { + doNavigate( + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionConsentFragment() + ) + } + is SubmissionNavigationEvents.NavigateToResultAvailableScreen -> { + doNavigate( + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment( + args.coronaTestQrCode.type + ) + ) + } + is SubmissionNavigationEvents.NavigateToResultPendingScreen -> { + doNavigate( + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment( + testType = args.coronaTestQrCode.type + ) + ) + } + } + } + } + + private fun navigateToDispatchScreen() = + doNavigate( + SubmissionDeletionWarningFragmentDirections + .actionSubmissionDeletionWarningFragmentToSubmissionDispatcherFragment() + ) + + private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance { + return when (exception) { + is BadRequestException -> DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_qr_code_scan_invalid_dialog_headline, + R.string.submission_qr_code_scan_invalid_dialog_body, + R.string.submission_qr_code_scan_invalid_dialog_button_positive, + R.string.submission_qr_code_scan_invalid_dialog_button_negative, + true, + { /* startDecode() */ }, + ::navigateToDispatchScreen + ) + is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + R.string.submission_error_dialog_web_generic_network_error_body, + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToDispatchScreen + ) + else -> DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + R.string.submission_error_dialog_web_generic_error_body, + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToDispatchScreen + ) + } } override fun onResume() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragmentViewModel.kt deleted file mode 100644 index a2e1e24371ff9b72a431b46e7077baf17205aeb3..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningFragmentViewModel.kt +++ /dev/null @@ -1,12 +0,0 @@ -package de.rki.coronawarnapp.ui.submission.deletionwarning - -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory - -class SubmissionDeletionWarningFragmentViewModel @AssistedInject constructor() : CWAViewModel() { - - @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<SubmissionDeletionWarningFragmentViewModel> -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningModule.kt index ac2010cd75d8413c47987c5c1533d2294831b4a6..2f95413150901adf0f4b61e246110a459c6cc0b7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningModule.kt @@ -12,8 +12,8 @@ abstract class SubmissionDeletionWarningModule { @Binds @IntoMap - @CWAViewModelKey(SubmissionDeletionWarningFragmentViewModel::class) + @CWAViewModelKey(SubmissionDeletionWarningViewModel::class) abstract fun submissionDeletionWarningFragmentVM( - factory: SubmissionDeletionWarningFragmentViewModel.Factory + factory: SubmissionDeletionWarningViewModel.Factory ): CWAViewModelFactory<out CWAViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..1013bc5517fda4dcebaa1ca3ea74a68b8c1e4c6c --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt @@ -0,0 +1,125 @@ +package de.rki.coronawarnapp.ui.submission.deletionwarning + +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode +import de.rki.coronawarnapp.coronatest.qrcode.InvalidQRCodeException +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.TransactionException +import de.rki.coronawarnapp.exception.http.CwaWebException +import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.ApiRequestState +import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import kotlinx.coroutines.flow.first +import timber.log.Timber + +class SubmissionDeletionWarningViewModel @AssistedInject constructor( + private val coronaTestRepository: CoronaTestRepository, + private val submissionRepository: SubmissionRepository, + @Assisted private val coronaTest: CoronaTestQRCode, + @Assisted private val isConsentGiven: Boolean, +) : CWAViewModel() { + + val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>() + val showRedeemedTokenWarning = SingleLiveEvent<Unit>() + private val mutableRegistrationState = MutableLiveData(RegistrationState(ApiRequestState.IDLE)) + val registrationState: LiveData<RegistrationState> = mutableRegistrationState + val registrationError = SingleLiveEvent<CwaWebException>() + + fun deleteExistingAndRegisterNewTest() = launch { + val currentTest = submissionRepository.testForType(coronaTest.type).first() + try { + coronaTestRepository.removeTest(currentTest!!.identifier) + doDeviceRegistration(coronaTest) + } catch (err: Exception) { + Timber.e(err, "Removal of existing test failed with msg: ${err.message}") + err.report(ExceptionCategory.INTERNAL) + } + } + + data class RegistrationState( + val apiRequestState: ApiRequestState, + val testResult: CoronaTestResult? = null + ) + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal suspend fun doDeviceRegistration(coronaTestQRCode: CoronaTestQRCode) { + try { + mutableRegistrationState.postValue(RegistrationState(ApiRequestState.STARTED)) + val coronaTest = submissionRepository.registerTest(coronaTestQRCode) + if (isConsentGiven) { + submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type) + } + checkTestResult(coronaTest.testResult) + mutableRegistrationState.postValue( + RegistrationState( + ApiRequestState.SUCCESS, + coronaTest.testResult + ) + ) + } catch (err: CwaWebException) { + Timber.e(err, "Msg: ${err.message}") + mutableRegistrationState.postValue(RegistrationState(ApiRequestState.FAILED)) + registrationError.postValue(err) + } catch (err: TransactionException) { + Timber.e(err, "Msg: ${err.message}") + if (err.cause is CwaWebException) { + registrationError.postValue(err.cause) + } else { + err.report(ExceptionCategory.INTERNAL) + } + mutableRegistrationState.postValue(RegistrationState(ApiRequestState.FAILED)) + } catch (err: InvalidQRCodeException) { + Timber.e(err, "Msg: ${err.message}") + mutableRegistrationState.postValue(RegistrationState(ApiRequestState.FAILED)) + deregisterTestFromDevice(coronaTestQRCode) + showRedeemedTokenWarning.postValue(Unit) + } catch (err: Exception) { + Timber.e(err, "Msg: ${err.message}") + mutableRegistrationState.postValue(RegistrationState(ApiRequestState.FAILED)) + err.report(ExceptionCategory.INTERNAL) + } + } + + fun onCancelButtonClick() { + routeToScreen.postValue(SubmissionNavigationEvents.NavigateToConsent) + } + + fun triggerNavigationToSubmissionTestResultAvailableFragment() { + routeToScreen.postValue(SubmissionNavigationEvents.NavigateToResultAvailableScreen) + } + + fun triggerNavigationToSubmissionTestResultPendingFragment() { + routeToScreen.postValue(SubmissionNavigationEvents.NavigateToResultPendingScreen) + } + + private fun checkTestResult(testResult: CoronaTestResult) { + if (testResult == CoronaTestResult.PCR_REDEEMED) { + throw InvalidQRCodeException() + } + } + + private fun deregisterTestFromDevice(coronaTest: CoronaTestQRCode) { + launch { + Timber.d("deregisterTestFromDevice()") + + submissionRepository.removeTestFromDevice(type = coronaTest.type) + routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) + } + } + + @AssistedFactory + interface Factory : CWAViewModelFactory<SubmissionDeletionWarningViewModel> { + fun create(coronaTest: CoronaTestQRCode, isConsentGiven: Boolean): SubmissionDeletionWarningViewModel + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt index 6458a16da1f8c82708ed7bfdc92a3a7e6db23193..89a9a4a17f525b7b027377aae1072e22fdef179d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentFragment.kt @@ -32,9 +32,11 @@ class SubmissionConsentFragment : Fragment(R.layout.fragment_submission_consent) } viewModel.routeToScreen.observe2(this) { when (it) { - is SubmissionNavigationEvents.NavigateToQRCodeScan -> doNavigate( - SubmissionConsentFragmentDirections.actionSubmissionConsentFragmentToSubmissionQRCodeScanFragment() - ) + is SubmissionNavigationEvents.NavigateToQRCodeScan -> + doNavigate( + SubmissionConsentFragmentDirections + .actionSubmissionConsentFragmentToSubmissionQRCodeScanFragment(isConsentGiven = true) + ) is SubmissionNavigationEvents.NavigateToDispatcher -> popBackStack() is SubmissionNavigationEvents.NavigateToDataPrivacy -> doNavigate( SubmissionConsentFragmentDirections.actionSubmissionConsentFragmentToInformationPrivacyFragment() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt index 459ea67f482258a9e58aa664e74dec82f37587d0..e3387e7dc8426e7b8e5fac3908eabbe9dbb4a81c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModel.kt @@ -4,10 +4,8 @@ import androidx.lifecycle.asLiveData import com.google.android.gms.common.api.ApiException import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector import de.rki.coronawarnapp.nearby.modules.tekhistory.TEKHistoryProvider import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository -import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent @@ -16,11 +14,10 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import timber.log.Timber class SubmissionConsentViewModel @AssistedInject constructor( - private val submissionRepository: SubmissionRepository, interoperabilityRepository: InteroperabilityRepository, dispatcherProvider: DispatcherProvider, private val tekHistoryProvider: TEKHistoryProvider, - private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector + // private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() @@ -30,8 +27,7 @@ class SubmissionConsentViewModel @AssistedInject constructor( fun onConsentButtonClick() { // TODO Do we have a Test registered at this time? We need to forward the decission with navargs? -// submissionRepository.giveConsentToSubmission(type = CoronaTest.Type.PCR) - analyticsKeySubmissionCollector.reportAdvancedConsentGiven() + // analyticsKeySubmissionCollector.reportAdvancedConsentGiven() launch { try { val preAuthorized = tekHistoryProvider.preAuthorizeExposureKeyHistory() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt index f19296ecba2b0a05766d8ddd40b6ae8d4546ee30..c2a919a2e22ad3fdd5f967ef434383efef26cf07 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt @@ -6,10 +6,12 @@ import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs import com.google.zxing.BarcodeFormat import com.journeyapps.barcodescanner.DefaultDecoderFactory import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.databinding.FragmentSubmissionQrCodeScanBinding import de.rki.coronawarnapp.exception.http.BadRequestException import de.rki.coronawarnapp.exception.http.CwaClientError @@ -26,18 +28,24 @@ import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider -import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted import javax.inject.Inject /** * A simple [Fragment] subclass. */ -class SubmissionQRCodeScanFragment : - Fragment(R.layout.fragment_submission_qr_code_scan), - AutoInject { - +class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_code_scan), AutoInject { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val viewModel: SubmissionQRCodeScanViewModel by cwaViewModels { viewModelFactory } + + private val args by navArgs<SubmissionQRCodeScanFragmentArgs>() + + private val viewModel: SubmissionQRCodeScanViewModel by cwaViewModelsAssisted( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, _ -> + factory as SubmissionQRCodeScanViewModel.Factory + factory.create(args.isConsentGiven) + } + ) private val binding: FragmentSubmissionQrCodeScanBinding by viewBindingLazy() private var showsPermissionDialog = false @@ -62,6 +70,20 @@ class SubmissionQRCodeScanFragment : submissionQrCodeScanViewfinderView.setCameraPreview(binding.submissionQrCodeScanPreview) } + viewModel.routeToScreen.observe2(this) { + when (it) { + is SubmissionNavigationEvents.NavigateToDeletionWarningFragment -> { + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionDeletionWarningFragment( + it.consentGiven, + it.coronaTestQRCode + ) + ) + } + } + } + viewModel.scanStatusValue.observe2(this) { if (ScanStatus.INVALID == it) { showInvalidScanDialog() @@ -86,16 +108,57 @@ class SubmissionQRCodeScanFragment : else -> View.GONE } if (ApiRequestState.SUCCESS == state.apiRequestState) { - if (state.testResult == CoronaTestResult.PCR_POSITIVE) { - doNavigate( - SubmissionQRCodeScanFragmentDirections - .actionSubmissionQRCodeScanFragmentToSubmissionTestResultAvailableFragment() - ) - } else { - doNavigate( - SubmissionQRCodeScanFragmentDirections - .actionSubmissionQRCodeScanFragmentToSubmissionTestResultPendingFragment() - ) + when (state.testResult) { + CoronaTestResult.PCR_POSITIVE -> + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionTestResultAvailableFragment( + testType = CoronaTest.Type.PCR + ) + ) + CoronaTestResult.PCR_OR_RAT_PENDING -> { + if (state.testType == CoronaTest.Type.RAPID_ANTIGEN) { + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionTestResultPendingFragment( + testType = CoronaTest.Type.RAPID_ANTIGEN + ) + ) + } else { + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionTestResultPendingFragment( + testType = CoronaTest.Type.PCR + ) + ) + } + } + CoronaTestResult.PCR_NEGATIVE, + CoronaTestResult.PCR_INVALID, + CoronaTestResult.PCR_REDEEMED -> + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionTestResultPendingFragment( + testType = CoronaTest.Type.PCR + ) + ) + CoronaTestResult.RAT_POSITIVE -> + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionTestResultAvailableFragment( + testType = CoronaTest.Type.RAPID_ANTIGEN + ) + ) + CoronaTestResult.RAT_NEGATIVE, + CoronaTestResult.RAT_INVALID, + CoronaTestResult.RAT_PENDING, + CoronaTestResult.RAT_REDEEMED -> + doNavigate( + SubmissionQRCodeScanFragmentDirections + .actionSubmissionQRCodeScanFragmentToSubmissionTestResultPendingFragment( + testType = CoronaTest.Type.RAPID_ANTIGEN + ) + ) } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt index ff300dd094c8610d70ccd2ec42bc7231d4114c37..6466857f9a768b2f05a9ef27aa1922814ac94508 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan import androidx.annotation.VisibleForTesting import androidx.lifecycle.MutableLiveData +import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor @@ -18,29 +19,46 @@ import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.ui.submission.ScanStatus import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.permission.CameraSettings import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import kotlinx.coroutines.flow.first import timber.log.Timber class SubmissionQRCodeScanViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, private val submissionRepository: SubmissionRepository, private val cameraSettings: CameraSettings, + @Assisted private val isConsentGiven: Boolean, private val qrCodeValidator: CoronaTestQrCodeValidator -) : CWAViewModel() { +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>() val showRedeemedTokenWarning = SingleLiveEvent<Unit>() val scanStatusValue = SingleLiveEvent<ScanStatus>() - fun validateTestGUID(rawResult: String) { + fun validateTestGUID(rawResult: String) = launch { try { val coronaTestQRCode = qrCodeValidator.validate(rawResult) // TODO this needs to be adapted to work for different types QRCodeCensor.lastGUID = coronaTestQRCode.registrationIdentifier scanStatusValue.postValue(ScanStatus.SUCCESS) - doDeviceRegistration(coronaTestQRCode) + + val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first() + + if (coronaTest != null) { + routeToScreen.postValue( + SubmissionNavigationEvents.NavigateToDeletionWarningFragment( + coronaTestQRCode, + isConsentGiven + ) + ) + } else { + doDeviceRegistration(coronaTestQRCode) + } } catch (err: InvalidQRCodeException) { + Timber.e(err, "Failed to validate GUID") scanStatusValue.postValue(ScanStatus.INVALID) } } @@ -50,17 +68,26 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( data class RegistrationState( val apiRequestState: ApiRequestState, - val testResult: CoronaTestResult? = null + val testResult: CoronaTestResult? = null, + val testType: CoronaTest.Type? = null ) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal fun doDeviceRegistration(coronaTestQRCode: CoronaTestQRCode) = launch { + internal suspend fun doDeviceRegistration(coronaTestQRCode: CoronaTestQRCode) { try { registrationState.postValue(RegistrationState(ApiRequestState.STARTED)) val coronaTest = submissionRepository.registerTest(coronaTestQRCode) - submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type) + if (isConsentGiven) { + submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type) + } checkTestResult(coronaTest.testResult) - registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, coronaTest.testResult)) + registrationState.postValue( + RegistrationState( + ApiRequestState.SUCCESS, + coronaTest.testResult, + coronaTestQRCode.type + ) + ) } catch (err: CwaWebException) { registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) registrationError.postValue(err) @@ -73,7 +100,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) } catch (err: InvalidQRCodeException) { registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) - deregisterTestFromDevice() + deregisterTestFromDevice(coronaTestQRCode) showRedeemedTokenWarning.postValue(Unit) } catch (err: Exception) { registrationState.postValue(RegistrationState(ApiRequestState.FAILED)) @@ -87,12 +114,11 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( } } - private fun deregisterTestFromDevice() { + private fun deregisterTestFromDevice(coronaTest: CoronaTestQRCode) { launch { Timber.d("deregisterTestFromDevice()") - submissionRepository.removeTestFromDevice(type = CoronaTest.Type.PCR) - + submissionRepository.removeTestFromDevice(type = coronaTest.type) routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) } } @@ -111,5 +137,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( } @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<SubmissionQRCodeScanViewModel> + interface Factory : CWAViewModelFactory<SubmissionQRCodeScanViewModel> { + fun create(isConsentGiven: Boolean): SubmissionQRCodeScanViewModel + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt index 81a4ebbc057e1effeed28b8b0f1505ef35b8cbfc..d6e40ed1b55061fac0d811365af65631c574f058 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/invalid/SubmissionTestResultInvalidViewModel.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavDirections import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -20,7 +20,7 @@ import timber.log.Timber class SubmissionTestResultInvalidViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val submissionRepository: SubmissionRepository, - private val testResultAvailableNotificationService: TestResultAvailableNotificationService, + private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { // TODO Use navargs to supply this private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt index e7f6ebec332e3ad125f57f9dc7164cebff9ae569..8640c2bb8dd493c5648255ae79253800ac1ce543 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/negative/SubmissionTestResultNegativeViewModel.kt @@ -6,7 +6,7 @@ import androidx.navigation.NavDirections import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -20,7 +20,7 @@ import timber.log.Timber class SubmissionTestResultNegativeViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val submissionRepository: SubmissionRepository, - private val testResultAvailableNotificationService: TestResultAvailableNotificationService + private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { // TODO Use navargs to supply this private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt index 2c2ed4a3ca78d308b9dfefdea4cd66dcc85e4fec..096b1c81a56c7455a3f398ea0f8683c87a1d676a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultPendingBinding import de.rki.coronawarnapp.exception.http.CwaClientError @@ -19,20 +20,28 @@ import de.rki.coronawarnapp.util.ui.popBackStack import de.rki.coronawarnapp.util.ui.setInvisible import de.rki.coronawarnapp.util.ui.viewBindingLazy import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider -import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted import javax.inject.Inject class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submission_test_result_pending), AutoInject { - @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val pendingViewModel: SubmissionTestResultPendingViewModel by cwaViewModels { viewModelFactory } - private val binding: FragmentSubmissionTestResultPendingBinding by viewBindingLazy() private var skipInitialTestResultRefresh = false private var errorDialog: AlertDialog? = null + private val navArgs by navArgs<SubmissionTestResultPendingFragmentArgs>() + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val pendingViewModel: SubmissionTestResultPendingViewModel by cwaViewModelsAssisted( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, _ -> + factory as SubmissionTestResultPendingViewModel.Factory + factory.create(navArgs.testType) + } + ) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -81,8 +90,6 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio pendingViewModel.routeToScreen.observe2(this) { it?.let { doNavigate(it) } ?: navigateToMainScreen() } - - pendingViewModel.observeTestResultToSchedulePositiveTestResultReminder() } override fun onResume() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt index b01b89606ed2a5b22e16efeacf5164f9b077abf5..d04817c085031fc1f430c95619ae7f28b7d9ab57 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt @@ -3,10 +3,11 @@ package de.rki.coronawarnapp.ui.submission.testresult.pending import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections +import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.CoronaTest -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.toDeviceUIState import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState @@ -14,10 +15,9 @@ import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex @@ -26,27 +26,25 @@ import timber.log.Timber class SubmissionTestResultPendingViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, - private val shareTestResultNotificationService: ShareTestResultNotificationService, - private val submissionRepository: SubmissionRepository + private val submissionRepository: SubmissionRepository, + @Assisted private val testType: CoronaTest.Type ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - // TODO Use navargs to supply this - private val coronaTestType: CoronaTest.Type = CoronaTest.Type.PCR init { - Timber.v("init() coronaTestType=%s", coronaTestType) + Timber.v("init() coronaTestType=%s", testType) } val routeToScreen = SingleLiveEvent<NavDirections?>() val showRedeemedTokenWarning = SingleLiveEvent<Unit>() - val consentGiven = submissionRepository.testForType(type = coronaTestType).map { + val consentGiven = submissionRepository.testForType(type = testType).map { it?.isAdvancedConsentGiven ?: false }.asLiveData() private var wasRedeemedTokenErrorShown = false private val tokenErrorMutex = Mutex() - private val testResultFlow = submissionRepository.testForType(type = coronaTestType) + private val testResultFlow = submissionRepository.testForType(type = testType) .filterNotNull() .map { test -> tokenErrorMutex.withLock { @@ -62,17 +60,33 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( val testState: LiveData<TestResultUIState> = testResultFlow .onEach { testResultUIState -> - when (val deviceState = testResultUIState.coronaTest.testResult.toDeviceUIState()) { - DeviceUIState.PAIRED_POSITIVE -> + when (val deviceState = testResultUIState.coronaTest.testResult) { + CoronaTestResult.PCR_POSITIVE -> SubmissionTestResultPendingFragmentDirections - .actionSubmissionTestResultPendingFragmentToSubmissionTestResultAvailableFragment() - DeviceUIState.PAIRED_NEGATIVE -> + .actionSubmissionTestResultPendingFragmentToSubmissionTestResultAvailableFragment( + testType = CoronaTest.Type.PCR + ) + CoronaTestResult.RAT_POSITIVE -> + SubmissionTestResultPendingFragmentDirections + .actionSubmissionTestResultPendingFragmentToSubmissionTestResultAvailableFragment( + testType = CoronaTest.Type.RAPID_ANTIGEN + ) + CoronaTestResult.PCR_NEGATIVE -> SubmissionTestResultPendingFragmentDirections .actionSubmissionTestResultPendingFragmentToSubmissionTestResultNegativeFragment() - DeviceUIState.PAIRED_REDEEMED, - DeviceUIState.PAIRED_ERROR -> + CoronaTestResult.RAT_NEGATIVE -> + SubmissionTestResultPendingFragmentDirections + .actionSubmissionTestResultPendingFragmentToSubmissionNegativeAntigenTestResultFragment() + CoronaTestResult.PCR_REDEEMED, CoronaTestResult.PCR_INVALID -> + SubmissionTestResultPendingFragmentDirections + .actionSubmissionTestResultPendingFragmentToSubmissionTestResultInvalidFragment( + CoronaTest.Type.PCR + ) + CoronaTestResult.RAT_REDEEMED, CoronaTestResult.RAT_INVALID -> SubmissionTestResultPendingFragmentDirections - .actionSubmissionTestResultPendingFragmentToSubmissionTestResultInvalidFragment() + .actionSubmissionTestResultPendingFragmentToSubmissionTestResultInvalidFragment( + CoronaTest.Type.RAPID_ANTIGEN + ) else -> { Timber.w("Unknown success state: %s", deviceState) null @@ -80,7 +94,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( }?.let { routeToScreen.postValue(it) } } .filter { testResultUIState -> - val isPositiveTest = testResultUIState.coronaTest.isSubmissionAllowed + val isPositiveTest = testResultUIState.coronaTest.isPositive if (isPositiveTest) { Timber.w("Filtering out positive test emission as we don't display this here.") } @@ -88,27 +102,21 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( } .asLiveData(context = dispatcherProvider.Default) - val cwaWebExceptionLiveData = submissionRepository.testForType(type = coronaTestType) + val cwaWebExceptionLiveData = submissionRepository.testForType(type = testType) .filterNotNull() .filter { it.lastError != null } .map { it.lastError!! } .asLiveData() - fun observeTestResultToSchedulePositiveTestResultReminder() = launch { - submissionRepository.testForType(type = coronaTestType) - .first { request -> request?.isSubmissionAllowed ?: false } - .also { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } - } - fun deregisterTestFromDevice() = launch { Timber.d("deregisterTestFromDevice()") - submissionRepository.removeTestFromDevice(type = coronaTestType) + submissionRepository.removeTestFromDevice(type = testType) routeToScreen.postValue(null) } fun refreshDeviceUIState() = launch { Timber.v("refreshDeviceUIState()") - submissionRepository.refreshTest(type = coronaTestType) + submissionRepository.refreshTest(type = testType) } fun onConsentClicked() { @@ -119,5 +127,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( } @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultPendingViewModel> + interface Factory : CWAViewModelFactory<SubmissionTestResultPendingViewModel> { + fun create(testType: CoronaTest.Type): SubmissionTestResultPendingViewModel + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt index 053baa300837faac283f575edda2cd5633f30e78..e217fa4c4e8d4f7ebe94cfaac97819063bb3579f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultConsentGivenViewModel.kt @@ -7,7 +7,7 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.Screen -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState @@ -24,7 +24,7 @@ import timber.log.Timber class SubmissionTestResultConsentGivenViewModel @AssistedInject constructor( private val submissionRepository: SubmissionRepository, private val autoSubmission: AutoSubmission, - private val testResultAvailableNotificationService: TestResultAvailableNotificationService, + private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector, dispatcherProvider: DispatcherProvider ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt index 04374d1b4d688bfe8e66003f303e30dd16312d45..091025361fa654a41a92b4b07a04aa87f6a5c07b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentFragment.kt @@ -6,8 +6,10 @@ import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.activity.OnBackPressedCallback import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultPositiveNoConsentBinding +import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragmentArgs import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.doNavigate @@ -30,6 +32,8 @@ class SubmissionTestResultNoConsentFragment : private val viewModel: SubmissionTestResultNoConsentViewModel by cwaViewModels { viewModelFactory } private val binding: FragmentSubmissionTestResultPositiveNoConsentBinding by viewBindingLazy() + private val navArgs by navArgs<SubmissionTestResultPendingFragmentArgs>() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -85,7 +89,9 @@ class SubmissionTestResultNoConsentFragment : private fun navigateToWarnOthers() { doNavigate( SubmissionTestResultNoConsentFragmentDirections - .actionSubmissionTestResultNoConsentFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment() + .actionSubmissionTestResultNoConsentFragmentToSubmissionResultPositiveOtherWarningNoConsentFragment( + navArgs.testType + ) ) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt index 8161c5e817c77b091843d54556f7425ec76fa7c8..80afcd0a31a65f2e1b14817fe8db78a4e399ebb4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/positive/SubmissionTestResultNoConsentViewModel.kt @@ -7,7 +7,7 @@ import dagger.assisted.AssistedInject import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.Screen -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -19,7 +19,7 @@ import timber.log.Timber class SubmissionTestResultNoConsentViewModel @AssistedInject constructor( private val submissionRepository: SubmissionRepository, - private val testResultAvailableNotificationService: TestResultAvailableNotificationService, + private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector ) : CWAViewModel() { // TODO Use navargs to supply this diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt index e89a06d0dabe6cc67283b8f1c0cd657c3e8b384e..93186ab86ac5b3655f3b99668d21d719adbaa0d8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt @@ -2,6 +2,8 @@ package de.rki.coronawarnapp.ui.submission.viewmodel import dagger.Module import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.submission.ui.testresults.negative.RATResultNegativeFragment +import de.rki.coronawarnapp.submission.ui.testresults.negative.RATResultNegativeModule import de.rki.coronawarnapp.ui.submission.fragment.SubmissionContactFragment import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDispatcherFragment import de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningFragment @@ -95,4 +97,7 @@ internal abstract class SubmissionFragmentModule { @ContributesAndroidInjector(modules = [SubmissionDeletionWarningModule::class]) abstract fun submissionDeletionWarningScreen(): SubmissionDeletionWarningFragment + + @ContributesAndroidInjector(modules = [RATResultNegativeModule::class]) + abstract fun submissionNegativeRATResultScreen(): RATResultNegativeFragment } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt index de51d34bff749610e87ed4dd9aa3a9fdc8261440..838118d1308a928e03ed67551321bbabac459587 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.ui.submission.viewmodel import com.google.android.gms.common.api.ApiException +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode sealed class SubmissionNavigationEvents { object NavigateToContact : SubmissionNavigationEvents() @@ -12,5 +13,9 @@ sealed class SubmissionNavigationEvents { object NavigateToTAN : SubmissionNavigationEvents() object NavigateToConsent : SubmissionNavigationEvents() object NavigateToMainActivity : SubmissionNavigationEvents() + object NavigateToResultPendingScreen : SubmissionNavigationEvents() + object NavigateToResultAvailableScreen : SubmissionNavigationEvents() + data class NavigateToDeletionWarningFragment(val coronaTestQRCode: CoronaTestQRCode, val consentGiven: Boolean) : + SubmissionNavigationEvents() data class ResolvePlayServicesException(val exception: ApiException) : SubmissionNavigationEvents() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt index 11c140a9ff906b736dbafec4afda1f2c1aeddbc3..acdb4ca5d7edbbdc25ecf6efe2aaa83496b1960b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/view/TestResultSectionView.kt @@ -60,7 +60,6 @@ constructor( private fun formatTestStatusIcon(coronaTest: CoronaTest?): Drawable? { val drawable = when (coronaTest?.testResult.toDeviceUIState()) { DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending - DeviceUIState.PAIRED_POSITIVE_TELETAN, DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative DeviceUIState.PAIRED_ERROR, @@ -84,7 +83,6 @@ constructor( SpannableString(context.getString(R.string.test_result_card_status_invalid)) DeviceUIState.PAIRED_POSITIVE, - DeviceUIState.PAIRED_POSITIVE_TELETAN, DeviceUIState.PAIRED_NEGATIVE -> SpannableString(formatTestResult(context, uiState)) else -> SpannableString("") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DeviceUIState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DeviceUIState.kt index bc304ab27c4a7eadac06adb6decadd14c4c97fac..8d4ecd55a44631262b3e84738a50cc7076c9fecf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DeviceUIState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DeviceUIState.kt @@ -4,10 +4,8 @@ enum class DeviceUIState { UNPAIRED, PAIRED_NO_RESULT, PAIRED_POSITIVE, - PAIRED_POSITIVE_TELETAN, PAIRED_NEGATIVE, PAIRED_ERROR, PAIRED_REDEEMED, - SUBMITTED_INITIAL, SUBMITTED_FINAL } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt deleted file mode 100644 index ddef4e3140281c5ddf59dd4ef68bffc640cd0537..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt +++ /dev/null @@ -1,30 +0,0 @@ -package de.rki.coronawarnapp.util - -sealed class NetworkRequestWrapper<out T : Any, out U : Any> { - object RequestIdle : NetworkRequestWrapper<Nothing, Nothing>() - object RequestStarted : NetworkRequestWrapper<Nothing, Nothing>() - data class RequestSuccessful<T : Any, U : Any>(val data: T) : NetworkRequestWrapper<T, U>() - data class RequestFailed<T : Any, U : Throwable>(val error: U) : NetworkRequestWrapper<T, U>() - - companion object { - fun <T : Any, U : Any, W> NetworkRequestWrapper<T, U>?.withSuccess(without: W, block: (data: T) -> W): W { - return if (this is RequestSuccessful) { - block(this.data) - } else { - without - } - } - - fun <T : Any, U : Any> NetworkRequestWrapper<T, U>?.withSuccess(block: (data: T) -> Unit) { - if (this is RequestSuccessful) { - block(this.data) - } - } - - fun <T : Any, U : Any> NetworkRequestWrapper<T, U>?.withFailure(block: (error: U) -> Unit) { - if (this is RequestFailed) { - block(this.error) - } - } - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/flow/HotDataFlow.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/flow/HotDataFlow.kt index adb705b8853b16d296567070479d47f4cc762cc0..b28c701d272a760bc57de284b18789ff15c1e5f3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/flow/HotDataFlow.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/flow/HotDataFlow.kt @@ -6,7 +6,6 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged @@ -21,12 +20,20 @@ import kotlinx.coroutines.sync.withLock import timber.log.Timber import kotlin.coroutines.CoroutineContext +/** + * A thread safe flow that can be updated blockingly and async, with way to provide an initial (lazy) value. + * + * @param loggingTag will be prepended to logging tag, i.e. "$loggingTag:HD" + * @param scope on which the update operations and callbacks will be executed on + * @param coroutineContext used in combination with [scope] + * @param sharingBehavior see [Flow.shareIn] + * @param startValueProvider provides the first value, errors will be rethrown on [scope] + */ class HotDataFlow<T : Any>( loggingTag: String, scope: CoroutineScope, coroutineContext: CoroutineContext = scope.coroutineContext, sharingBehavior: SharingStarted = SharingStarted.WhileSubscribed(), - forwardException: Boolean = true, private val startValueProvider: suspend CoroutineScope.() -> T ) { private val tag = "$loggingTag:HD" @@ -35,19 +42,21 @@ class HotDataFlow<T : Any>( Timber.tag(tag).v("init()") } - private val updateActions = MutableSharedFlow<suspend (T) -> T>( + private val updateActions = MutableSharedFlow<Update<T>>( replay = Int.MAX_VALUE, extraBufferCapacity = Int.MAX_VALUE, onBufferOverflow = BufferOverflow.SUSPEND ) private val valueGuard = Mutex() - private val internalProducer: Flow<Holder<T>> = channelFlow { + private val internalProducer: Flow<State<T>> = channelFlow { var currentValue = valueGuard.withLock { startValueProvider().also { Timber.tag(tag).v("startValue=%s", it) - val updatedBy: suspend T.() -> T = { it } - send(Holder.Data(value = it, updatedBy = updatedBy)) + + val initializer = Update<T>(onError = null, onModify = { it }) + + send(State(value = it, updatedBy = initializer)) } } Timber.tag(tag).v("startValue=%s", currentValue) @@ -57,10 +66,20 @@ class HotDataFlow<T : Any>( Timber.tag(tag).v("updateActions onCompletion -> resetReplayCache()") updateActions.resetReplayCache() } - .collect { updateAction -> + .collect { update -> currentValue = valueGuard.withLock { - updateAction(currentValue).also { - send(Holder.Data(value = it, updatedBy = updateAction)) + try { + update.onModify(currentValue).also { + send(State(value = it, updatedBy = update)) + } + } catch (e: Exception) { + Timber.tag(tag).v(e, "Data modifying failed (hasErrorHandler=${update.onError != null})") + if (update.onError != null) { + update.onError.invoke(e) + } else { + send(State(value = currentValue, error = e, updatedBy = update)) + } + currentValue } } } @@ -70,16 +89,6 @@ class HotDataFlow<T : Any>( private val internalFlow = internalProducer .onStart { Timber.tag(tag).v("Internal onStart") } - .catch { - if (forwardException) { - Timber.tag(tag).w(it, "Forwarding internal Error") - // Wrap the error to get it past `sharedIn` - emit(Holder.Error(error = it)) - } else { - Timber.tag(tag).e(it, "Throwing internal Error") - throw it - } - } .onCompletion { err -> when { err is CancellationException -> Timber.tag(tag).d("internal onCompletion due to cancelation") @@ -92,31 +101,55 @@ class HotDataFlow<T : Any>( replay = 1, started = sharingBehavior ) - .map { - when (it) { - is Holder.Data<T> -> it - is Holder.Error<T> -> throw it.error - } - } - val data: Flow<T> = internalFlow.map { it.value }.distinctUntilChanged() + val data: Flow<T> = internalFlow + .map { it.value } + .distinctUntilChanged() - fun updateSafely(update: suspend T.() -> T) = updateActions.tryEmit(update) + /** + * Non blocking update method. + * Gets executed on the scope and context this instance was initialized with. + * + * @param onError if you don't provide this, and exception in [onUpdate] will the scope passed to this class + */ + fun updateAsync( + onError: (suspend (Exception) -> Unit) = { throw it }, + onUpdate: suspend T.() -> T, + ): Boolean { + val update: Update<T> = Update( + onModify = onUpdate, + onError = onError + ) + return updateActions.tryEmit(update) + } - suspend fun updateBlocking(update: suspend T.() -> T): T { + /** + * Blocking update method + * Gets executed on the scope and context this instance was initialized with. + * Waiting will happen on the callers scope. + * + * Any errors that occured during [action] will be rethrown by this method. + */ + suspend fun updateBlocking(action: suspend T.() -> T): T { + val update: Update<T> = Update(onModify = action) updateActions.tryEmit(update) + Timber.tag(tag).v("Waiting for update.") - return internalFlow.first { it.updatedBy == update }.value - } + val ourUpdate = internalFlow.first { it.updatedBy == update } - internal sealed class Holder<T> { - data class Data<T>( - val value: T, - val updatedBy: suspend T.() -> T - ) : Holder<T>() + ourUpdate.error?.let { throw it } - data class Error<T>( - val error: Throwable - ) : Holder<T>() + return ourUpdate.value } + + private data class Update<T>( + val onModify: suspend T.() -> T, + val onError: (suspend (Exception) -> Unit)? = null, + ) + + private data class State<T>( + val value: T, + val error: Exception? = null, + val updatedBy: Update<T>, + ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt index 4983861085b09dbad197b772a0847b2a331a4faf..41caecdaa6de6fef99dc10bafa80934fd6c8b4be 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelper.kt @@ -67,15 +67,13 @@ fun formatSymptomBackgroundButtonStyleByState( fun formatTestResultStatusText(context: Context, uiState: DeviceUIState): String = when (uiState) { DeviceUIState.PAIRED_NEGATIVE -> R.string.test_result_card_status_negative - DeviceUIState.PAIRED_POSITIVE, - DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.string.test_result_card_status_positive + DeviceUIState.PAIRED_POSITIVE -> R.string.test_result_card_status_positive else -> R.string.test_result_card_status_invalid }.let { context.getString(it) } fun formatTestResultStatusColor(context: Context, uiState: DeviceUIState): Int = when (uiState) { DeviceUIState.PAIRED_NEGATIVE -> R.color.colorTextSemanticGreen - DeviceUIState.PAIRED_POSITIVE, - DeviceUIState.PAIRED_POSITIVE_TELETAN -> R.color.colorTextSemanticRed + DeviceUIState.PAIRED_POSITIVE -> R.color.colorTextSemanticRed else -> R.color.colorTextSemanticRed }.let { context.getColorCompat(it) } @@ -105,7 +103,6 @@ fun formatTestResultCardContent( SpannableString(context.getString(R.string.test_result_card_status_invalid)) DeviceUIState.PAIRED_POSITIVE, - DeviceUIState.PAIRED_POSITIVE_TELETAN, DeviceUIState.PAIRED_NEGATIVE -> SpannableString(formatTestResult(context, uiState)) else -> SpannableString("") } @@ -113,7 +110,6 @@ fun formatTestResultCardContent( fun formatTestStatusIcon(context: Context, uiState: DeviceUIState): Drawable? = when (uiState) { DeviceUIState.PAIRED_NO_RESULT -> R.drawable.ic_test_result_illustration_pending - DeviceUIState.PAIRED_POSITIVE_TELETAN, DeviceUIState.PAIRED_POSITIVE -> R.drawable.ic_test_result_illustration_positive DeviceUIState.PAIRED_NEGATIVE -> R.drawable.ic_test_result_illustration_negative DeviceUIState.PAIRED_ERROR, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt index 0d2b9c43a0cfa7841f4cfed0583b117d72075c71..dd9ce5a954aa810bf83c2b47ed8d8691bb087294 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt @@ -14,7 +14,7 @@ import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.notification.NotificationConstants -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.worker.InjectedWorkerFactory import kotlinx.coroutines.flow.first @@ -30,7 +30,7 @@ import timber.log.Timber class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( @Assisted val context: Context, @Assisted workerParams: WorkerParameters, - private val testResultAvailableNotificationService: TestResultAvailableNotificationService, + private val testResultAvailableNotificationService: PCRTestResultAvailableNotificationService, private val notificationHelper: GeneralNotifications, private val coronaTestRepository: CoronaTestRepository, private val timeStamper: TimeStamper, diff --git a/Corona-Warn-App/src/main/res/drawable/card_counter_green.xml b/Corona-Warn-App/src/main/res/drawable/card_counter_green.xml new file mode 100644 index 0000000000000000000000000000000000000000..b1ca09ca6dab9577b41d1d11b7dc5067887a1b1b --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/card_counter_green.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="rectangle"> + <corners android:topLeftRadius="@dimen/radius_card" android:topRightRadius="@dimen/radius_card" /> + <solid android:color="@color/colorSemanticLowRisk" /> + </shape> + </item> +</selector> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/card_white_bottom_rounded_corners.xml b/Corona-Warn-App/src/main/res/drawable/card_white_bottom_rounded_corners.xml new file mode 100644 index 0000000000000000000000000000000000000000..d61e81a7a7997d073551032a356f1f06a40b7f93 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/card_white_bottom_rounded_corners.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item> + <shape android:shape="rectangle"> + <corners android:bottomLeftRadius="@dimen/radius_card" android:bottomRightRadius="@dimen/radius_card" /> + <solid android:color="@color/colorSurface1" /> + </shape> + </item> +</selector> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable/ic_test_result_delete_test.xml b/Corona-Warn-App/src/main/res/drawable/ic_test_result_delete_test.xml new file mode 100644 index 0000000000000000000000000000000000000000..7d97911443a1fc4bea6e7faa67c2286c19ac4709 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_test_result_delete_test.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="40dp" + android:height="40dp" + android:viewportWidth="40" + android:viewportHeight="40"> + <path + android:pathData="M20,40C31.0457,40 40,31.0457 40,20C40,8.9543 31.0457,0 20,0C8.9543,0 0,8.9543 0,20C0,31.0457 8.9543,40 20,40Z" + android:fillColor="#F5F5F5"/> + <path + android:pathData="M24,17V27H16V17H24ZM22.5,11H17.5L16.5,12H13V14H27V12H23.5L22.5,11ZM26,15H14V27C14,28.1 14.9,29 16,29H24C25.1,29 26,28.1 26,27V15Z" + android:fillColor="#979797"/> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml new file mode 100644 index 0000000000000000000000000000000000000000..19676144452838395000ff8d518611be495c4265 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_antigen_test_result_negative.xml @@ -0,0 +1,272 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navigationIcon="@drawable/ic_close" + app:title="@string/submission_test_result_toolbar_text" /> + + <ScrollView + android:id="@+id/scrollview" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_marginBottom="@dimen/button_padding_top_bottom" + app:layout_constraintBottom_toTopOf="@id/coronatest_negative_antigen_result_button" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/rapid_test_card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_small" + android:background="@drawable/card_dark" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <TextView + android:id="@+id/rapid_test_card_title" + style="@style/headline5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_medium" + android:text="@string/submission_test_result_antigen_title" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/rapid_test_card_subtitle" + style="@style/subtitleMedium" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:text="@string/submission_result_subtitle" + app:layout_constraintStart_toStartOf="@id/rapid_test_card_title" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card_title" /> + + <TextView + android:id="@+id/rapid_test_card_virus_name" + style="@style/headline5Bold" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/test_result_card_virus_name_text" + app:layout_constraintStart_toStartOf="@id/rapid_test_card_subtitle" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card_subtitle" /> + + <TextView + android:id="@+id/rapid_test_card_diagnosis" + style="@style/headline5Bold" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/submission_test_result_negative" + android:textColor="@color/colorTextSemanticGreen" + app:layout_constraintStart_toStartOf="@id/rapid_test_card_virus_name" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card_virus_name" + tools:text="@string/submission_test_result_negative" /> + + <TextView + android:id="@+id/rapid_test_card_patient_name" + style="@style/body2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:layout_marginEnd="@dimen/spacing_normal" + android:textStyle="bold" + app:layout_constraintStart_toStartOf="@id/rapid_test_card_diagnosis" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card_diagnosis" + tools:text="@string/submission_test_result_antigen_patient_name_placeholder" /> + + <TextView + android:id="@+id/rapid_test_card_patient_birthdate" + style="@style/body2" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/rapid_test_card_patient_name" + app:layout_constraintTop_toTopOf="@id/rapid_test_card_patient_name" + tools:text="@string/submission_test_result_antigen_patient_birth_date_placeholder" /> + + <TextView + android:id="@+id/rapid_test_card_negative_result_message" + style="@style/body2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:layout_marginEnd="@dimen/spacing_normal" + android:text="@string/submission_test_result_negative_message" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/rapid_test_card_patient_name" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card_patient_birthdate" /> + + <include + android:id="@+id/result_received_counter" + layout="@layout/time_counter" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="@id/rapid_test_card_negative_result_message" + app:layout_constraintStart_toStartOf="@id/rapid_test_card_negative_result_message" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card_negative_result_message" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/result_received_counter_bottom_part" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_normal" + android:background="@drawable/card_white_bottom_rounded_corners" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@id/result_received_counter" + app:layout_constraintStart_toStartOf="@id/result_received_counter" + app:layout_constraintTop_toBottomOf="@id/result_received_counter"> + + <TextView + android:id="@+id/result_received_time_and_date" + style="@style/body2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/guideline_card" + android:layout_marginBottom="@dimen/guideline_card" + android:gravity="center" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/coronatest_negative_antigen_result_time_date_placeholder" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </androidx.constraintlayout.widget.ConstraintLayout> + + <TextView + android:id="@+id/negative_test_proof_title" + style="@style/headline5" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_small" + android:accessibilityHeading="true" + android:text="@string/submission_test_result_antigen_negative_proof_title" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/rapid_test_card" /> + + <TextView + android:id="@+id/negative_test_proof_body" + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="@string/submission_test_result_antigen_negative_proof_body" + app:layout_constraintEnd_toEndOf="@id/negative_test_proof_title" + app:layout_constraintStart_toStartOf="@id/negative_test_proof_title" + app:layout_constraintTop_toBottomOf="@id/negative_test_proof_title" /> + + <TextView + android:id="@+id/submission_test_result_subtitle" + style="@style/headline5" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_small" + android:accessibilityHeading="true" + android:text="@string/submission_test_result_subtitle" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/negative_test_proof_body" /> + + <de.rki.coronawarnapp.ui.view.SimpleStepEntry + android:id="@+id/test_result_negative_steps_added" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginEnd="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/submission_test_result_subtitle" + app:layout_constraintTop_toBottomOf="@id/submission_test_result_subtitle" + app:simple_step_entry_text="@string/coronatest_negative_antigen_result_first_info_body" + app:simple_step_entry_title="@string/coronatest_negative_antigen_result_first_info_title" + app:step_entry_final="false" + app:step_entry_icon="@drawable/ic_test_result_step_done" /> + + <de.rki.coronawarnapp.ui.view.SimpleStepEntry + android:id="@+id/test_result_negative_steps_negative_result" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/test_result_negative_steps_added" + app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_added" + app:simple_step_entry_text="@string/coronatest_negative_antigen_result_second_info_body" + app:simple_step_entry_title="@string/coronatest_negative_antigen_result_second_info_title" + app:step_entry_final="true" + app:step_entry_icon="@drawable/ic_test_result_step_invalid" /> + + <de.rki.coronawarnapp.ui.view.SimpleStepEntry + android:id="@+id/coronatest_negative_antigen_result_third_info" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacing_normal" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/test_result_negative_steps_negative_result" + app:layout_constraintTop_toBottomOf="@+id/test_result_negative_steps_negative_result" + app:simple_step_entry_text="@string/coronatest_negative_antigen_restul_third_info_body" + app:simple_step_entry_title="@string/coronatest_negative_antigen_result_third_info_title" + app:step_entry_final="true" + app:step_entry_icon="@drawable/ic_test_result_delete_test" /> + + <LinearLayout + android:id="@+id/further_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/colorSurface2" + android:orientation="vertical" + android:padding="@dimen/spacing_normal" + app:layout_constraintTop_toBottomOf="@id/test_result_negative_steps_negative_result"> + + <TextView + android:id="@+id/further_info_title" + style="@style/headline5" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:accessibilityHeading="true" + android:text="@string/test_result_card_negative_further_info_title" /> + + <de.rki.coronawarnapp.ui.view.BulletPointList + android:id="@+id/further_info_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + app:entries="@array/test_result_card_negative_further_info_bullet_points" /> + + </LinearLayout> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </ScrollView> + + <Button + android:id="@+id/coronatest_negative_antigen_result_button" + style="@style/buttonPrimary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_normal" + android:text="@string/submission_test_result_negative_remove_test_button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_deletion_warning.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_deletion_warning.xml index 56a83c008188580c8de15fb748a69241c9a55ba3..27fdf7bca4367c65b6311fa1732c57cf836dd3b2 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_submission_deletion_warning.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_deletion_warning.xml @@ -18,6 +18,19 @@ app:navigationIcon="@drawable/ic_close" app:title="@string/submission_deletion_warning_title" /> + <ProgressBar + android:id="@+id/submission_qr_code_scan_spinner" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> + <ScrollView android:layout_width="0dp" android:layout_height="0dp" @@ -94,26 +107,12 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacing_normal" android:layout_marginEnd="@dimen/spacing_normal" - android:layout_marginBottom="8dp" + android:layout_marginBottom="@dimen/spacing_normal" android:text="@string/submission_deletion_warning_continue_button" - app:layout_constraintBottom_toTopOf="@id/cancel_button" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - tools:text="@string/submission_deletion_warning_continue_button" /> - - <Button - android:id="@+id/cancel_button" - style="@style/buttonLight" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_normal" - android:layout_marginEnd="@dimen/spacing_normal" - android:layout_marginBottom="4dp" - android:text="@string/submission_deletion_warning_cancel_button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - tools:text="@string/submission_deletion_warning_cancel_button" /> + tools:text="@string/submission_deletion_warning_continue_button" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml index f87e108477b6c97139a44c18bc1a19fde7cfd683..49d5aab4c16290b5dbfae8b3908674b1f6f69173 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_test_result_negative.xml @@ -48,6 +48,7 @@ app:layout_constraintTop_toTopOf="parent" app:test_result_section_headline="@string/test_result_card_headline" /> + <TextView android:id="@+id/submission_test_result_subtitle" style="@style/headline5" @@ -58,7 +59,7 @@ android:text="@string/submission_test_result_subtitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/submission_test_result_section" /> + app:layout_constraintTop_toBottomOf="@id/submission_test_result_section" /> <de.rki.coronawarnapp.ui.view.SimpleStepEntry diff --git a/Corona-Warn-App/src/main/res/layout/time_counter.xml b/Corona-Warn-App/src/main/res/layout/time_counter.xml new file mode 100644 index 0000000000000000000000000000000000000000..07a4f52748469fb0ab0edddf20139fa3e8f49fa1 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/time_counter.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/upper_green_part" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@drawable/card_counter_green" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <TextView + android:id="@+id/result_timer" + style="@style/body2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/guideline_card" + android:gravity="center" + android:text="@string/submission_test_result_antigen_negative_counter_title" + android:textColor="@color/colorTextPrimary1InvertedStable" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <Chronometer + android:id="@+id/chronometer" + style="@style/headline4Bold" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:format="00:00:00" + android:gravity="center" + android:textColor="@color/colorTextPrimary1InvertedStable" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/result_timer" + tools:text="00:00:00" /> + + <TextView + android:id="@+id/chronometer_hours" + style="@style/body3Medium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_tiny" + android:text="Std" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/chronometer_minutes" + app:layout_constraintStart_toStartOf="@id/chronometer" + app:layout_constraintTop_toBottomOf="@id/chronometer" /> + + <TextView + android:id="@+id/chronometer_minutes" + style="@style/body3Medium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_tiny" + android:gravity="center" + android:text="Min" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@id/chronometer" + app:layout_constraintStart_toStartOf="@id/chronometer" + app:layout_constraintTop_toBottomOf="@id/chronometer" /> + + <TextView + android:id="@+id/chronometer_seconds" + style="@style/body3Medium" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_tiny" + android:text="Sek" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@id/chronometer" + app:layout_constraintStart_toEndOf="@id/chronometer_minutes" + app:layout_constraintTop_toBottomOf="@id/chronometer" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/Corona-Warn-App/src/main/res/layout/view_test_result_section.xml b/Corona-Warn-App/src/main/res/layout/view_test_result_section.xml index b8662fe55f085e975550c95c2336ac8a8ea94e72..62fefc498eafb3ccab565a421fcbcd63a3d826f4 100644 --- a/Corona-Warn-App/src/main/res/layout/view_test_result_section.xml +++ b/Corona-Warn-App/src/main/res/layout/view_test_result_section.xml @@ -30,7 +30,6 @@ android:layout_height="wrap_content" android:layout_marginEnd="@dimen/spacing_small" android:layout_marginBottom="@dimen/spacing_normal" - app:layout_constraintBottom_toTopOf="@id/test_result_section_registered_at_text" app:layout_constraintEnd_toStartOf="@id/test_result_section_status_icon" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/test_result_section_headline" @@ -40,11 +39,11 @@ android:id="@+id/test_result_section_status_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginEnd="24dp" android:importantForAccessibility="no" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="parent" + app:layout_constraintTop_toTopOf="@id/test_result_section_content" tools:src="@drawable/ic_test_result_illustration_negative" /> - <TextView android:id="@+id/test_result_section_registered_at_text" style="@style/body2" 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 5ff1d99cff64e4f55a40b3bcde41772a920a595b..d4ff1223b1dbc4299c6d3a450a93c7913b1c61d4 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -64,6 +64,9 @@ <action android:id="@+id/action_mainFragment_to_trace_location_organizer_nav_graph" app:destination="@id/trace_location_organizer_nav_graph" /> + <action + android:id="@+id/action_mainFragment_to_submissionNegativeAntigenTestResultFragment" + app:destination="@id/submissionNegativeAntigenTestResultFragment" /> </fragment> <fragment @@ -278,6 +281,9 @@ app:destination="@id/submissionResultReadyFragment" 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_submissionResultPositiveOtherWarningNoConsentFragment_to_checkInsConsentFragment" app:destination="@id/checkInsConsentFragment" @@ -311,7 +317,14 @@ 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" + app:popUpTo="@id/mainFragment" + app:popUpToInclusive="false" /> </fragment> <fragment @@ -348,7 +361,12 @@ <fragment android:id="@+id/submissionQRCodeScanFragment" android:name="de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment" - android:label="SubmissionQRCodeScanFragment"> + android:label="SubmissionQRCodeScanFragment" + tools:layout="@layout/fragment_submission_qr_code_scan"> + <argument + android:name="isConsentGiven" + android:defaultValue="false" + app:argType="boolean" /> <action android:id="@+id/action_submissionQRCodeScanFragment_to_submissionDispatcherFragment" app:popUpTo="@id/submissionQRCodeScanFragment" @@ -368,6 +386,9 @@ app:destination="@id/submissionTestResultAvailableFragment" app:popUpTo="@id/mainFragment" app:popUpToInclusive="false" /> + <action + android:id="@+id/action_submissionQRCodeScanFragment_to_submissionDeletionWarningFragment" + app:destination="@id/submissionDeletionWarningFragment" /> </fragment> <fragment android:id="@+id/submissionResultReadyFragment" @@ -503,7 +524,9 @@ app:destination="@id/submissionTestResultNoConsentFragment" app:popUpTo="@id/mainFragment" app:popUpToInclusive="false" /> - + <argument + android:name="testType" + app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" /> </fragment> <fragment android:id="@+id/submissionTestResultNegativeFragment" @@ -513,7 +536,16 @@ android:id="@+id/submissionTestResultInvalidFragment" android:name="de.rki.coronawarnapp.ui.submission.testresult.invalid.SubmissionTestResultInvalidFragment" android:label="SubmissionTestResultInvalidFragment" - tools:layout="@layout/fragment_submission_test_result_invalid" /> + tools:layout="@layout/fragment_submission_test_result_invalid"> + <argument + android:name="testType" + app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" /> + </fragment> + <fragment + android:id="@+id/submissionNegativeAntigenTestResultFragment" + android:name="de.rki.coronawarnapp.submission.ui.testresults.negative.RATResultNegativeFragment" + android:label="SubmissionNegativeAntigenTestResultFragment" + tools:layout="@layout/fragment_submission_antigen_test_result_negative" /> <fragment android:id="@+id/debuglogFragment" android:name="de.rki.coronawarnapp.bugreporting.debuglog.ui.DebugLogFragment" @@ -619,6 +651,43 @@ android:name="de.rki.coronawarnapp.bugreporting.debuglog.ui.legal.DebugLogLegalFragment" android:label="DebugLogLegalFragment" tools:layout="@layout/bugreporting_legal_fragment" /> + <fragment + android:id="@+id/submissionDeletionWarningFragment" + android:name="de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningFragment" + android:label="SubmissionDeletionWarningFragment" + tools:layout="@layout/fragment_submission_deletion_warning"> + <argument + android:name="isConsentGiven" + android:defaultValue="false" + app:argType="boolean" /> + <argument + android:name="coronaTestQrCode" + app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode" /> + <action + android:id="@+id/action_submissionDeletionWarningFragment_to_submissionDispatcherFragment" + app:popUpTo="@id/submissionQRCodeScanFragment" + app:popUpToInclusive="true" /> + <action + android:id="@+id/action_submissionDeletionWarningFragment_to_submissionTestResultPendingFragment" + app:destination="@id/submissionTestResultPendingFragment" + app:popUpTo="@id/mainFragment" + app:popUpToInclusive="false"> + <argument + android:name="skipInitialTestResultRefresh" + android:defaultValue="true" + app:argType="boolean" /> + </action> + <action + android:id="@+id/action_submissionDeletionWarningFragment_to_submissionTestResultAvailableFragment" + app:destination="@id/submissionTestResultAvailableFragment" + app:popUpTo="@id/mainFragment" + app:popUpToInclusive="false" /> + <action + android:id="@+id/action_submissionDeletionWarningFragment_to_submissionConsentFragment" + app:popUpTo="@id/mainFragment" + app:popUpToInclusive="false" + app:destination="@id/submissionConsentFragment" /> + </fragment> <fragment android:id="@+id/checkInsConsentFragment" diff --git a/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml index 3f69d7869cbb2f30d4ed86edf20e0273a6fe2135..a1aa228c7fe3ca55915981d14af0bf92cb8a5c76 100644 --- a/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/antigen_strings.xml @@ -85,4 +85,56 @@ <!-- XTXT: homescreen card: body - negative --> <string name="ag_homescreen_card_pcr_body_result_date">"Test registriert am %1$s"</string> + <!-- XBUT: submission deletion warning button continue --> + <string name="submission_deletion_warning_continue_button">"Weiter"</string> + <!-- XBUT: submission deletion warning button cancel --> + <string name="submission_deletion_warning_cancel_button">"Abbrechen"</string> + <!-- XHED: submission deletion warning title --> + <string name="submission_deletion_warning_title">"Hinweis"</string> + <!-- YTXT: Headline for rapid test submission deletion warning --> + <string name="submission_deletion_warning_headline_antigen_test">"Sie haben bereits einen Schnelltest registriert."</string> + <!-- YTXT: Body for rapid test submission deletion warning --> + <string name="submission_deletion_warning_body_antigen_test">"Sie haben bereits einen Schnelltest registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren Schnelltest registrieren, wird der erste Schnelltest aus der App gelöscht."</string> + <!-- YTXT: Headline for PCR test submission deletion warning --> + <string name="submission_deletion_warning_headline_pcr_test">"Sie haben bereits einen PCR-Test registriert."</string> + <!-- YTXT: Body for PCR test submission deletion warning --> + <string name="submission_deletion_warning_body_pcr_test">"Sie haben bereits einen PCR-Test registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren PCR-Test registrieren, wird der erste PCR-Test aus der App gelöscht."</string> + + <!-- XHED: submission result antigen fragment toolbar text --> + <string name="submission_test_result_toolbar_text">"Ihr Testergebnis"</string> + <!-- XHED: submission result antigen fragment card title --> + <string name="submission_test_result_antigen_title">"Schnelltest"</string> + <!-- XTXT: submission result antigen fragment card "found" text --> + <string name="submission_result_subtitle">"Befund"</string> + <!-- XTXT: submission test result fragment negative diagnosis --> + <string name="submission_test_result_negative">"Negativ"</string> + <!-- XTXT: submission test result fragment positive diagnosis --> + <string name="submission_test_result_positive">"Positiv"</string> + <!-- XTXT: submission result antigen fragment card patient name placeholder --> + <string name="submission_test_result_antigen_patient_name_placeholder">"Max Mustermann,"</string> + <!-- XTXT: submission result antigen fragment card patient birth date placeholder --> + <string name="submission_test_result_antigen_patient_birth_date_placeholder">"geboren 14.03.1987"</string> + <!-- XTXT: coronatest negative antigen result time and date placeholder --> + <string name="coronatest_negative_antigen_result_time_date_placeholder">"Ausgestellt: 10.03.2021, 18:01 Uhr"</string> + <!-- XTXT: submission result antigen fragment card negative result message --> + <string name="submission_test_result_negative_message">"Das Virus SARS-CoV-2 wurde bei Ihnen nicht nachgewiesen."</string> + <!-- XHED: submission result antigen fragment negative result proof title --> + <string name="submission_test_result_antigen_negative_proof_title">"Nachweis-Funktion"</string> + <!-- XHED: submission result antigen fragment negative result proof body --> + <string name="submission_test_result_antigen_negative_proof_body">"Sie können den hier angezeigten Befund auch als Nachweis für das Vorliegen eines negativen Schnelltest-Ergebnisses verwenden. Informieren Sie sich hierzu bitte auch über die Kriterien für die Anerkennung von Test-Nachweisen in Ihrem Bundesland. Bitte beachten Sie, dass Sie grundsätzlich nicht zum Nachweis per App verpflichtet sind. Sie können Ihr Schnelltest-Ergebnis im Rahmen der rechtlichen Bestimmungen auf andere Weise nachweisen."</string> + <!-- XHED: submission result antigen negative result counter title --> + <string name="submission_test_result_antigen_negative_counter_title">"Ergebnis liegt vor seit"</string> + <!-- XHED: coronatest negative antigen result first info title --> + <string name="coronatest_negative_antigen_result_first_info_title">"Ihr Schnelltest wurde hinzugefügt."</string> + <!-- XTXT: coronatest negative antigen result first info body --> + <string name="coronatest_negative_antigen_result_first_info_body">"Das Test-Ergebnis wird 48 Stunden hier angezeigt. Zusätzlich legt die App einen Eintrag in Ihrem Kontakt-Tagebuch an."</string> + <!-- XHED: coronatest negative antigen result second info title --> + <string name="coronatest_negative_antigen_result_second_info_title">"Befund negativ"</string> + <!-- XTXT: coronatest negative antigen result second info body --> + <string name="coronatest_negative_antigen_result_second_info_body">"Der Schnelltest hat keinen Nachweis für das Coronavirus SARS-CoV-2 bei Ihnen ergeben."</string> + <!-- XTXT: coronatest negative antigen result third info title --> + <string name="coronatest_negative_antigen_result_third_info_title">"Test entfernen"</string> + <!-- XTXT: coronatest negative antigen result third info body --> + <string name="coronatest_negative_antigen_restul_third_info_body">"Bitte entfernen Sie den Test wieder aus der Corona-Warn-App, damit Sie bei Bedarf einen neuen Test hinterlegen können."</string> + </resources> diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml index 6d3f3cf7cb1483798811bf17b2611f4a53d014fc..36def2bf1ce5745feab7d6a91394df5c088e22a8 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -1136,19 +1136,19 @@ <!-- XHED: Page subheadline for dispatcher options asking if they have already been tested: QR and TAN --> <string name="submission_dispatcher_needs_testing_subheadline">"Sie haben sich bereits testen lassen?"</string> <!-- XHED: Page subheadline for dispatcher options asking if they already have a positive test: tele-TAN --> - <string name="submission_dispatcher_already_positive_subheadline">"Ihr Test ist positiv?"</string> + <string name="submission_dispatcher_already_positive_subheadline">"Ihr PCR-Test ist positiv?"</string> <!-- YTXT: Dispatcher text for QR code option --> <string name="submission_dispatcher_card_qr">"Test mit QR-Code"</string> <!-- YTXT: Body text for QR code dispatcher option --> <string name="submission_dispatcher_qr_card_text">"Registrieren Sie Ihren Test, indem Sie den QR-Code Ihres Testdokuments scannen."</string> <!-- YTXT: Dispatcher text for TAN code option --> - <string name="submission_dispatcher_card_tan_code">"TAN-Eingabe"</string> + <string name="submission_dispatcher_card_tan_code">"TAN für PCR-Test eingeben"</string> <!-- YTXT: Body text for TAN code dispatcher option --> - <string name="submission_dispatcher_tan_code_card_text">"Ihnen liegt eine TAN vor? Weiter zur TAN-Eingabe, um andere zu warnen. "</string> + <string name="submission_dispatcher_tan_code_card_text">"Ihnen liegt eine TAN für Ihren PCR-Test vor? Weiter zur TAN-Eingabe, um andere zu warnen. "</string> <!-- YTXT: Dispatcher text for TELE-TAN option --> - <string name="submission_dispatcher_card_tan_tele">"Noch keine TAN?"</string> + <string name="submission_dispatcher_card_tan_tele">"TAN für PCR-Test anfragen?"</string> <!-- YTXT: Body text for TELE_TAN dispatcher option --> - <string name="submission_dispatcher_tan_tele_card_text">"Rufen Sie uns an und erhalten Sie eine TAN."</string> + <string name="submission_dispatcher_tan_tele_card_text">"Rufen Sie uns an und erhalten Sie eine TAN für Ihren PCR-Test."</string> <!-- XACT: Dispatcher Tan page title --> <string name="submission_dispatcher_accessibility_title">"Welche Informationen liegen Ihnen vor?"</string> @@ -1270,7 +1270,7 @@ <!-- Submission Contact --> <!-- XHED: Page title for contact page in submission flow --> - <string name="submission_contact_title">"TAN anfragen"</string> + <string name="submission_contact_title">"TAN für PCR-Test anfragen"</string> <!-- XHED: Page headline for contact page in submission flow --> <string name="submission_contact_headline">"Info zum Ablauf:"</string> <!-- YTXT: Body text for contact page in submission flow--> @@ -1286,7 +1286,7 @@ <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"Geben Sie die TAN in der App ein, um Ihren Test zu registrieren."</string> + <string name="submission_contact_step_2_body">"Geben Sie die TAN in der App ein, um Ihren PCR-Test zu registrieren."</string> <!-- YTXT: Body text for operating hours in contact page--> <string name="submission_contact_operating_hours_body">"Unser Kundenservice ist in den folgenden Sprachen für Sie da: Deutsch, Englisch, Türkisch \n\nErreichbarkeit:\nMo-So: Täglich 24 Stunden"</string> <!-- YTXT: Body text for technical contact and hotline information page --> @@ -1371,23 +1371,6 @@ <!-- YTXT: text for share result card--> <string name="submission_status_card_positive_result_share">"Teilen Sie Ihre Zufalls-IDs, damit andere gewarnt werden können."</string> - - <!-- XBUT: submission deletion warning button continue --> - <string name="submission_deletion_warning_continue_button">"Weiter"</string> - <!-- XBUT: submission deletion warning button cancel --> - <string name="submission_deletion_warning_cancel_button">"Abbrechen"</string> - <!-- XHED: submission deletion warning title --> - <string name="submission_deletion_warning_title">"Hinweis"</string> - <!-- YTXT: Headline for rapid test submission deletion warning --> - <string name="submission_deletion_warning_headline_antigen_test">"Sie haben bereits einen Schnelltest registriert."</string> - <!-- YTXT: Body for rapid test submission deletion warning --> - <string name="submission_deletion_warning_body_antigen_test">"Sie haben bereits einen Schnelltest registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren Schnelltest registrieren, wird der erste Schnelltest aus der App gelöscht."</string> - <!-- YTXT: Headline for PCR test submission deletion warning --> - <string name="submission_deletion_warning_headline_pcr_test">"Sie haben bereits einen PCR-Test registriert."</string> - <!-- YTXT: Body for PCR test submission deletion warning --> - <string name="submission_deletion_warning_body_pcr_test">"Sie haben bereits einen PCR-Test registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren PCR-Test registrieren, wird der erste PCR-Test aus der App gelöscht."</string> - - <!-- Reenable risk card --> <!-- XHED: Card title for re-enable risk card --> <string name="reenable_risk_card_title">"Risiko-Überprüfung beendet"</string> @@ -1407,11 +1390,11 @@ <string name="dialog_reactivate_risk_calculation_button_negative">"Abbrechen"</string> <!-- Test Result Card --> - <string name="test_result_card_headline">"Ihr Befund:"</string> + <string name="test_result_card_headline">"Ihr %s-Befund:"</string> <!-- YTXT: virus name text --> <string name="test_result_card_virus_name_text">"SARS-CoV-2"</string> <!-- YTXT: registered at text --> - <string name="test_result_card_registered_at_text">"Registriert am %s"</string> + <string name="test_result_card_registered_at_text">"Test hinzugefügt am %s"</string> <!-- YTXT: negative status text --> <string name="test_result_card_status_negative">"Negativ"</string> <!-- YTXT: positive status text --> diff --git a/Corona-Warn-App/src/main/res/values/antigen_strings.xml b/Corona-Warn-App/src/main/res/values/antigen_strings.xml index f31cf3f47e5e171b391924ccf15a33e4445f30c2..b4c738edb4fae45ccf819aea2225f0f9db0692e2 100644 --- a/Corona-Warn-App/src/main/res/values/antigen_strings.xml +++ b/Corona-Warn-App/src/main/res/values/antigen_strings.xml @@ -85,4 +85,56 @@ <!-- XTXT: homescreen card: body - negative --> <string name="ag_homescreen_card_pcr_body_result_date">"Test registriert am %1$s"</string> + <!-- XBUT: submission deletion warning button continue --> + <string name="submission_deletion_warning_continue_button">"Weiter"</string> + <!-- XBUT: submission deletion warning button cancel --> + <string name="submission_deletion_warning_cancel_button">"Abbrechen"</string> + <!-- XHED: submission deletion warning title --> + <string name="submission_deletion_warning_title">"Hinweis"</string> + <!-- YTXT: Headline for rapid test submission deletion warning --> + <string name="submission_deletion_warning_headline_antigen_test">"Sie haben bereits einen Schnelltest registriert."</string> + <!-- YTXT: Body for rapid test submission deletion warning --> + <string name="submission_deletion_warning_body_antigen_test">"Sie haben bereits einen Schnelltest registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren Schnelltest registrieren, wird der erste Schnelltest aus der App gelöscht."</string> + <!-- YTXT: Headline for PCR test submission deletion warning --> + <string name="submission_deletion_warning_headline_pcr_test">"Sie haben bereits einen PCR-Test registriert."</string> + <!-- YTXT: Body for PCR test submission deletion warning --> + <string name="submission_deletion_warning_body_pcr_test">"Sie haben bereits einen PCR-Test registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren PCR-Test registrieren, wird der erste PCR-Test aus der App gelöscht."</string> + + <!-- XHED: submission result antigen fragment toolbar text --> + <string name="submission_test_result_toolbar_text">"Ihr Testergebnis"</string> + <!-- XHED: submission result antigen fragment card title --> + <string name="submission_test_result_antigen_title">"Schnelltest"</string> + <!-- XTXT: submission result antigen fragment card "found" text --> + <string name="submission_result_subtitle">"Befund"</string> + <!-- XTXT: submission test result fragment negative diagnosis --> + <string name="submission_test_result_negative">"Negativ"</string> + <!-- XTXT: submission test result fragment positive diagnosis --> + <string name="submission_test_result_positive">"Positiv"</string> + <!-- XTXT: submission result antigen fragment card patient name placeholder --> + <string name="submission_test_result_antigen_patient_name_placeholder">"Max Mustermann,"</string> + <!-- XTXT: submission result antigen fragment card patient birth date placeholder --> + <string name="submission_test_result_antigen_patient_birth_date_placeholder">"geboren 14.03.1987"</string> + <!-- XTXT: coronatest negative antigen result time and date placeholder --> + <string name="coronatest_negative_antigen_result_time_date_placeholder">"Ausgestellt: 10.03.2021, 18:01 Uhr"</string> + <!-- XTXT: submission result antigen fragment card negative result message --> + <string name="submission_test_result_negative_message">"Das Virus SARS-CoV-2 wurde bei Ihnen nicht nachgewiesen."</string> + <!-- XHED: submission result antigen fragment negative result proof title --> + <string name="submission_test_result_antigen_negative_proof_title">"Nachweis-Funktion"</string> + <!-- XHED: submission result antigen fragment negative result proof body --> + <string name="submission_test_result_antigen_negative_proof_body">"Sie können den hier angezeigten Befund auch als Nachweis für das Vorliegen eines negativen Schnelltest-Ergebnisses verwenden. Informieren Sie sich hierzu bitte auch über die Kriterien für die Anerkennung von Test-Nachweisen in Ihrem Bundesland. Bitte beachten Sie, dass Sie grundsätzlich nicht zum Nachweis per App verpflichtet sind. Sie können Ihr Schnelltest-Ergebnis im Rahmen der rechtlichen Bestimmungen auf andere Weise nachweisen."</string> + <!-- XHED: submission result antigen negative result counter title --> + <string name="submission_test_result_antigen_negative_counter_title">"Ergebnis liegt vor seit"</string> + <!-- XHED: coronatest negative antigen result first info title --> + <string name="coronatest_negative_antigen_result_first_info_title">"Ihr Schnelltest wurde hinzugefügt."</string> + <!-- XTXT: coronatest negative antigen result first info body --> + <string name="coronatest_negative_antigen_result_first_info_body">"Das Test-Ergebnis wird 48 Stunden hier angezeigt. Zusätzlich legt die App einen Eintrag in Ihrem Kontakt-Tagebuch an."</string> + <!-- XHED: coronatest negative antigen result second info title --> + <string name="coronatest_negative_antigen_result_second_info_title">"Befund negativ"</string> + <!-- XTXT: coronatest negative antigen result second info body --> + <string name="coronatest_negative_antigen_result_second_info_body">"Der Schnelltest hat keinen Nachweis für das Coronavirus SARS-CoV-2 bei Ihnen ergeben."</string> + <!-- XTXT: coronatest negative antigen result third info title --> + <string name="coronatest_negative_antigen_result_third_info_title">"Test entfernen"</string> + <!-- XTXT: coronatest negative antigen result third info body --> + <string name="coronatest_negative_antigen_restul_third_info_body">"Bitte entfernen Sie den Test wieder aus der Corona-Warn-App, damit Sie bei Bedarf einen neuen Test hinterlegen können."</string> + </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/colors.xml b/Corona-Warn-App/src/main/res/values/colors.xml index 19fa4b0abc97785476b9d4a4fbd9b0cc71c56e40..566b2ecaf1aa5b0f3135e86cf32a5f616039ed7b 100644 --- a/Corona-Warn-App/src/main/res/values/colors.xml +++ b/Corona-Warn-App/src/main/res/values/colors.xml @@ -24,6 +24,7 @@ <color name="colorTextPrimary1Inverted">#FFFFFF</color> <color name="colorTextPrimary1Stable">#17191A</color> <color name="colorTextPrimary1InvertedStable">#FFFFFF</color> + <color name="colorTextPrimary1InvertedStableTransparent">#80FFFFFF</color> <color name="colorTextPrimary2">#9917191A</color> <color name="colorTextPrimary3">#4D17191A</color> <color name="colorTextEmphasizedButton">#FFFFFF</color> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 25a9ac057c69fc7ac41a07be1f4ee2852c4b2e09..aba4d20383a17fd7024a8cdf7252b135eaf01745 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1387,23 +1387,6 @@ <!-- YTXT: text for share result card--> <string name="submission_status_card_positive_result_share">"Share your random IDs so that others can be warned."</string> - - <!-- XBUT: submission deletion warning button continue --> - <string name="submission_deletion_warning_continue_button">"Weiter"</string> - <!-- XBUT: submission deletion warning button cancel --> - <string name="submission_deletion_warning_cancel_button">"Abbrechen"</string> - <!-- XHED: submission deletion warning title --> - <string name="submission_deletion_warning_title">"Hinweis"</string> - <!-- YTXT: Headline for rapid test submission deletion warning --> - <string name="submission_deletion_warning_headline_antigen_test">"Sie haben bereits einen Schnelltest registriert."</string> - <!-- YTXT: Body for rapid test submission deletion warning --> - <string name="submission_deletion_warning_body_antigen_test">"Sie haben bereits einen Schnelltest registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren Schnelltest registrieren, wird der erste Schnelltest aus der App gelöscht."</string> - <!-- YTXT: Headline for PCR test submission deletion warning --> - <string name="submission_deletion_warning_headline_pcr_test">"Sie haben bereits einen PCR-Test registriert."</string> - <!-- YTXT: Body for PCR test submission deletion warning --> - <string name="submission_deletion_warning_body_pcr_test">"Sie haben bereits einen PCR-Test registriert. Die App kann maximal einen Schnelltest und einen PCR-Test gleichzeitig verwalten. Wenn Sie einen weiteren PCR-Test registrieren, wird der erste PCR-Test aus der App gelöscht."</string> - - <!-- Reenable risk card --> <!-- XHED: Card title for re-enable risk card --> <string name="reenable_risk_card_title">"Exposure Check Ended"</string> @@ -1427,7 +1410,7 @@ <!-- YTXT: virus name text --> <string name="test_result_card_virus_name_text">"SARS-CoV-2"</string> <!-- YTXT: registered at text --> - <string name="test_result_card_registered_at_text">"Registered on %s"</string> + <string name="test_result_card_registered_at_text">"Test hinzugefügt am %s"</string> <!-- YTXT: negative status text --> <string name="test_result_card_status_negative">"Negative"</string> <!-- YTXT: positive status text --> diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml index 927a1ef0dba0d35470c54b1d435a62e8ae7f8b88..83fb7087b9c6515e8c37a04b8099c88c9ba08cc3 100644 --- a/Corona-Warn-App/src/main/res/values/styles.xml +++ b/Corona-Warn-App/src/main/res/values/styles.xml @@ -277,6 +277,9 @@ <style name="headline4" parent="@style/TextAppearance.MaterialComponents.Headline4"> <item name="android:textColor">@color/colorTextPrimary1</item> </style> + <style name="headline4Bold" parent="@style/headline4"> + <item name="android:textStyle">bold</item> + </style> <style name="headline5" parent="@style/TextAppearance.MaterialComponents.Headline5"> <item name="android:textColor">@color/colorTextPrimary1</item> @@ -328,6 +331,11 @@ <item name="android:textColor">@color/colorTextPrimary2</item> </style> + <style name="body3Medium" parent="@style/body2"> + <item name="android:textSize">10sp</item> + <item name="android:textColor">@color/colorTextPrimary1InvertedStableTransparent</item> + </style> + <style name="bodyButton" parent="@style/TextAppearance.MaterialComponents.Button"> <item name="android:textColor">@color/colorTextPrimary1</item> </style> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotificationServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotificationServiceTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7e4f8ecd181e644e15a99cf419fbe757ae7711b0 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/notification/ShareTestResultNotificationServiceTest.kt @@ -0,0 +1,126 @@ +package de.rki.coronawarnapp.coronatest.notification + +import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.type.CoronaTest +import de.rki.coronawarnapp.main.CWASettings +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.coroutines.runBlockingTest2 + +class ShareTestResultNotificationServiceTest : BaseTest() { + @MockK lateinit var cwaSettings: CWASettings + @MockK lateinit var coronaTestRepository: CoronaTestRepository + @MockK lateinit var shareTestResultNotification: ShareTestResultNotification + + private val coronaTestFlow = MutableStateFlow( + emptySet<CoronaTest>() + ) + private var numberOfRemainingSharePositiveTestResultReminders: Int = Int.MIN_VALUE + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + every { coronaTestRepository.coronaTests } returns coronaTestFlow + every { cwaSettings.numberOfRemainingSharePositiveTestResultReminders = any() } answers { + numberOfRemainingSharePositiveTestResultReminders = arg(0) + } + every { cwaSettings.numberOfRemainingSharePositiveTestResultReminders } answers { + numberOfRemainingSharePositiveTestResultReminders + } + + every { shareTestResultNotification.showSharePositiveTestResultNotification(any()) } just Runs + every { shareTestResultNotification.cancelSharePositiveTestResultNotification() } just Runs + every { shareTestResultNotification.scheduleSharePositiveTestResultReminder() } just Runs + } + + private fun createInstance(scope: CoroutineScope) = ShareTestResultNotificationService( + appScope = scope, + cwaSettings = cwaSettings, + coronaTestRepository = coronaTestRepository, + notification = shareTestResultNotification, + ) + + @Test + fun `any test which allows submission triggers scheduling`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance(this) + + coronaTestFlow.value = setOf( + mockk<CoronaTest>().apply { + every { isSubmissionAllowed } returns true + every { isSubmitted } returns false + } + ) + + instance.setup() + + verify { shareTestResultNotification.scheduleSharePositiveTestResultReminder() } + verify { cwaSettings.numberOfRemainingSharePositiveTestResultReminders = 2 } + } + + @Test + fun `showing a notification consumes a token`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance(this) + numberOfRemainingSharePositiveTestResultReminders = 2 + + instance.maybeShowSharePositiveTestResultNotification(1) + + numberOfRemainingSharePositiveTestResultReminders shouldBe 1 + + verify { shareTestResultNotification.showSharePositiveTestResultNotification(1) } + } + + @Test + fun `if there are no tokens left to show a notification, cancel the current one`() = + runBlockingTest2(ignoreActive = true) { + val instance = createInstance(this) + numberOfRemainingSharePositiveTestResultReminders = 0 + + instance.maybeShowSharePositiveTestResultNotification(1) + + numberOfRemainingSharePositiveTestResultReminders shouldBe 0 + + verify { shareTestResultNotification.cancelSharePositiveTestResultNotification() } + } + + @Test + fun `any test which allowes submission triggers scheduling`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance(this) + + coronaTestFlow.value = setOf( + mockk<CoronaTest>().apply { + every { isSubmissionAllowed } returns true + every { isSubmitted } returns false + } + ) + + instance.setup() + + verify { shareTestResultNotification.scheduleSharePositiveTestResultReminder() } + verify { cwaSettings.numberOfRemainingSharePositiveTestResultReminders = 2 } + } + + @Test + fun `if there are no tests, we reset scheduling`() = runBlockingTest2(ignoreActive = true) { + val instance = createInstance(this) + + coronaTestFlow.value = emptySet() + + instance.setup() + + advanceUntilIdle() + + verify { shareTestResultNotification.cancelSharePositiveTestResultNotification() } + verify { cwaSettings.numberOfRemainingSharePositiveTestResultReminders = Int.MIN_VALUE } + } +} 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 a5a8653dd65751954d95be2c1057f26dd60f0a42..43dc0a8ba7f789d898f1d03bc73bfb4d0f632097 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 @@ -3,12 +3,9 @@ package de.rki.coronawarnapp.main.home import android.content.Context import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.server.CoronaTestResult -import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.storage.TracingSettings @@ -31,11 +28,9 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk import io.mockk.mockkObject -import io.mockk.verify import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -56,7 +51,6 @@ class HomeFragmentViewModelTest : BaseTest() { @MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory @MockK lateinit var coronaTestRepository: CoronaTestRepository @MockK lateinit var tracingRepository: TracingRepository - @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var cwaSettings: CWASettings @MockK lateinit var appConfigProvider: AppConfigProvider @@ -86,7 +80,6 @@ class HomeFragmentViewModelTest : BaseTest() { errorResetTool = errorResetTool, tracingStatus = generalTracingStatus, tracingRepository = tracingRepository, - shareTestResultNotificationService = shareTestResultNotificationService, submissionRepository = submissionRepository, coronaTestRepository = coronaTestRepository, tracingStateProviderFactory = tracingStateProviderFactory, @@ -145,48 +138,6 @@ class HomeFragmentViewModelTest : BaseTest() { } } - @Test - fun `positive test result notification is triggered on positive QR code result`() { -// every { submissionRepository.deviceUIStateFlow } returns flowOf( -// NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE) -// ) - every { submissionRepository.pcrTest } returns flowOf( - mockk<PCRCoronaTest>().apply { - every { testResult } returns CoronaTestResult.PCR_POSITIVE - every { lastError } returns null - } - ) - every { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } returns Unit - - runBlocking { - createInstance().apply { - observeTestResultToSchedulePositiveTestResultReminder() - verify { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } - } - } - } - - @Test - fun `positive test result notification is triggered on positive TeleTan code result`() { -// every { submissionRepository.deviceUIStateFlow } returns flowOf( -// NetworkRequestWrapper.RequestSuccessful(PAIRED_POSITIVE_TELETAN) -// ) - every { submissionRepository.pcrTest } returns flowOf( - mockk<PCRCoronaTest>().apply { - every { testResult } returns CoronaTestResult.PCR_POSITIVE - every { lastError } returns null - } - ) - every { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } returns Unit - - runBlocking { - createInstance().apply { - observeTestResultToSchedulePositiveTestResultReminder() - verify { shareTestResultNotificationService.scheduleSharePositiveTestResultReminder() } - } - } - } - @Test fun `test correct order of displaying delta onboarding, release notes and popups`() { every { cwaSettings.wasInteroperabilityShownAtLeastOnce } returns false andThen true diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt index 96a785177817e7ca7dfc02ada3aec020d859fb44..e81162622143b53c91be6f9663985f1e6f020f13 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationServiceTest.kt @@ -9,7 +9,6 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.util.device.ForegroundState -import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coEvery @@ -53,7 +52,7 @@ class TestResultAvailableNotificationServiceTest : BaseTest() { every { notificationHelper.newBaseBuilder() } returns mockk(relaxed = true) } - fun createInstance() = TestResultAvailableNotificationService( + fun createInstance() = PCRTestResultAvailableNotificationService( context = context, foregroundState = foregroundState, navDeepLinkBuilderProvider = navDeepLinkBuilderProvider, @@ -61,18 +60,6 @@ class TestResultAvailableNotificationServiceTest : BaseTest() { cwaSettings = cwaSettings ) - @Test - fun `check destination`() { - val negative = createInstance().getNotificationDestination(CoronaTestResult.PCR_NEGATIVE) - negative shouldBe (R.id.submissionTestResultPendingFragment) - - val invalid = createInstance().getNotificationDestination(CoronaTestResult.PCR_INVALID) - invalid shouldBe (R.id.submissionTestResultPendingFragment) - - val positive = createInstance().getNotificationDestination(CoronaTestResult.PCR_POSITIVE) - positive shouldBe (R.id.submissionTestResultPendingFragment) - } - @Test fun `test notification in foreground`() = runBlockingTest { coEvery { foregroundState.isInForeground } returns flow { emit(true) } @@ -98,7 +85,6 @@ class TestResultAvailableNotificationServiceTest : BaseTest() { verifyOrder { navDeepLinkBuilderProvider.get() - instance.getNotificationDestination(CoronaTestResult.PCR_POSITIVE) context.getString(R.string.notification_headline_test_result_ready) context.getString(R.string.notification_body_test_result_ready) notificationHelper.sendNotification( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt index 9a10b45206fd8216eb1325d4da42275e83f3341f..db488d6baf44c6e360b26e399cced531bfa15238 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/task/SubmissionTaskTest.kt @@ -4,10 +4,10 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.coronatest.CoronaTestRepository +import de.rki.coronawarnapp.coronatest.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.presencetracing.checkins.CheckIn import de.rki.coronawarnapp.presencetracing.checkins.CheckInRepository @@ -53,7 +53,7 @@ class SubmissionTaskTest : BaseTest() { @MockK lateinit var tekHistoryStorage: TEKHistoryStorage @MockK lateinit var submissionSettings: SubmissionSettings @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService - @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService + @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @MockK lateinit var autoSubmission: AutoSubmission @MockK lateinit var tekBatch: TEKHistoryStorage.TEKBatch @@ -145,7 +145,6 @@ class SubmissionTaskTest : BaseTest() { every { analyticsKeySubmissionCollector.reportSubmitted() } just Runs every { analyticsKeySubmissionCollector.reportSubmittedInBackground() } just Runs - every { shareTestResultNotificationService.cancelSharePositiveTestResultNotification() } just Runs every { testResultAvailableNotificationService.cancelTestResultAvailableNotification() } just Runs every { autoSubmission.updateMode(any()) } just Runs @@ -236,7 +235,6 @@ class SubmissionTaskTest : BaseTest() { coronaTestRepository.markAsSubmitted(any()) backgroundWorkScheduler.startWorkScheduler() - shareTestResultNotificationService.cancelSharePositiveTestResultNotification() testResultAvailableNotificationService.cancelTestResultAvailableNotification() } @@ -293,7 +291,6 @@ class SubmissionTaskTest : BaseTest() { tekHistoryStorage.clear() checkInRepository.clear() settingSymptomsPreference.update(any()) - shareTestResultNotificationService.cancelSharePositiveTestResultNotification() autoSubmission.updateMode(any()) } submissionSettings.symptoms.value shouldBe userSymptoms diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt index c8430ea2a07a0a8ee28b306d979c7157397ab775..4d4c07cbafcb1e682049f28e99e49d875d106588 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.submission.testresult.pending import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.exception.http.CwaWebException -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingViewModel import io.kotest.matchers.shouldBe @@ -25,8 +24,8 @@ import testhelpers.extensions.InstantExecutorExtension @ExtendWith(InstantExecutorExtension::class) class SubmissionTestResultPendingViewModelTest : BaseTest() { - @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var testType: CoronaTest.Type private val testFlow = MutableStateFlow<CoronaTest?>(null) @@ -42,8 +41,8 @@ class SubmissionTestResultPendingViewModelTest : BaseTest() { fun createInstance(scope: CoroutineScope = TestCoroutineScope()) = SubmissionTestResultPendingViewModel( dispatcherProvider = scope.asDispatcherProvider(), - shareTestResultNotificationService = shareTestResultNotificationService, - submissionRepository = submissionRepository + submissionRepository = submissionRepository, + testType = testType ) @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModelTest.kt index 2736f0d6a4fa231b0042554025af3b66bad965f6..4041652f0d9087a6b198b28fa8ce9d35b8f8021a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/presencetracing/organizer/create/TraceLocationCreateViewModelTest.kt @@ -12,9 +12,9 @@ import io.mockk.coEvery import io.mockk.impl.annotations.MockK import kotlinx.coroutines.test.runBlockingTest import okio.ByteString.Companion.encode +import org.joda.time.DateTime import org.joda.time.Duration import org.joda.time.Instant -import org.joda.time.LocalDateTime import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -60,8 +60,8 @@ internal class TraceLocationCreateViewModelTest : BaseTest() { viewModel.address = "Address" viewModel.description = "Description" - viewModel.begin = LocalDateTime() - viewModel.end = LocalDateTime().plusHours(1) + viewModel.begin = DateTime() + viewModel.end = DateTime().plusHours(1) viewModel.checkInLength = Duration.standardMinutes(1) viewModel.uiState.observeForTesting { @@ -76,8 +76,8 @@ internal class TraceLocationCreateViewModelTest : BaseTest() { viewModel.address = "Address" viewModel.description = "A".repeat(101) - viewModel.begin = LocalDateTime() - viewModel.end = LocalDateTime().plusHours(1) + viewModel.begin = DateTime() + viewModel.end = DateTime().plusHours(1) viewModel.checkInLength = Duration.standardMinutes(1) viewModel.uiState.observeForTesting { @@ -92,8 +92,8 @@ internal class TraceLocationCreateViewModelTest : BaseTest() { viewModel.address = "A".repeat(101) viewModel.description = "Description" - viewModel.begin = LocalDateTime() - viewModel.end = LocalDateTime().plusHours(1) + viewModel.begin = DateTime() + viewModel.end = DateTime().plusHours(1) viewModel.checkInLength = Duration.standardMinutes(1) viewModel.uiState.observeForTesting { @@ -136,8 +136,8 @@ internal class TraceLocationCreateViewModelTest : BaseTest() { viewModel.address = "Address" viewModel.description = "Description" - viewModel.begin = LocalDateTime() - viewModel.end = LocalDateTime().plusHours(1) + viewModel.begin = DateTime() + viewModel.end = DateTime().plusHours(1) viewModel.checkInLength = Duration.ZERO viewModel.uiState.observeForTesting { @@ -151,8 +151,8 @@ internal class TraceLocationCreateViewModelTest : BaseTest() { viewModel.address = "Address" viewModel.description = "Description" - viewModel.begin = LocalDateTime().plusHours(1) - viewModel.end = LocalDateTime() + viewModel.begin = DateTime().plusHours(1) + viewModel.end = DateTime() viewModel.checkInLength = Duration.ZERO viewModel.uiState.observeForTesting { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt index 8a3ef38c00f8fc402969356ee5b861921c3d73bc..73c696f80716aaa3db533ca76147bc8071b8bea8 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/consent/SubmissionConsentViewModelTest.kt @@ -43,11 +43,9 @@ class SubmissionConsentViewModelTest { every { submissionRepository.giveConsentToSubmission(any()) } just Runs every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven() } just Runs viewModel = SubmissionConsentViewModel( - submissionRepository, interoperabilityRepository, dispatcherProvider = TestDispatcherProvider(), tekHistoryProvider, - analyticsKeySubmissionCollector = analyticsKeySubmissionCollector ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt index 02ed331db2b6e0bea748693db91616d7b058694b..78b2f8517bf46566e954672c23ff137bf253d5a0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt @@ -15,11 +15,13 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk import io.mockk.verify -import org.junit.Assert +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTest +import testhelpers.TestDispatcherProvider import testhelpers.extensions.InstantExecutorExtension import testhelpers.preferences.mockFlowPreference @@ -30,15 +32,23 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { @MockK lateinit var cameraSettings: CameraSettings @MockK lateinit var qrCodeValidator: CoronaTestQrCodeValidator + private val coronaTestFlow = MutableStateFlow<CoronaTest?>( + null + ) + @BeforeEach fun setUp() { MockKAnnotations.init(this) + + every { submissionRepository.testForType(any()) } returns coronaTestFlow } private fun createViewModel() = SubmissionQRCodeScanViewModel( + TestDispatcherProvider(), submissionRepository, cameraSettings, - qrCodeValidator + isConsentGiven = true, + qrCodeValidator, ) @Test @@ -64,16 +74,17 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { QRCodeCensor.lastGUID = null viewModel.validateTestGUID(validQrCode) - viewModel.scanStatusValue.let { Assert.assertEquals(ScanStatus.SUCCESS, it.value) } + viewModel.scanStatusValue.observeForever {} + viewModel.scanStatusValue.value shouldBe ScanStatus.SUCCESS QRCodeCensor.lastGUID = guid // invalid guid viewModel.validateTestGUID(invalidQrCode) - viewModel.scanStatusValue.let { Assert.assertEquals(ScanStatus.INVALID, it.value) } + viewModel.scanStatusValue.value shouldBe ScanStatus.INVALID } @Test - fun `doDeviceRegistration calls TestResultDataCollector`() { + fun `doDeviceRegistration calls TestResultDataCollector`() = runBlockingTest { val viewModel = createViewModel() val mockResult = mockk<CoronaTestQRCode>().apply { every { registrationIdentifier } returns "guid" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt index 757497cea3cf68b9202ee6aa258789c4940c54ad..8d160fe6b9fe5ec7ffd334e7e9940ca2b03be061 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultConsentGivenViewModelTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.ui.submission.testresult import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenViewModel @@ -20,7 +20,7 @@ import testhelpers.extensions.InstantExecutorExtension class SubmissionTestResultConsentGivenViewModelTest : BaseTest() { @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var autoSubmission: AutoSubmission - @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService + @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector lateinit var viewModel: SubmissionTestResultConsentGivenViewModel diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt index 9b23f5eca4035d5da1266bdc020f05dc3bb131fe..9cfae529d79f8e5d2e56c1913b52789926d6335c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/flow/HotDataFlowTest.kt @@ -4,6 +4,7 @@ import de.rki.coronawarnapp.util.mutate import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.kotest.matchers.types.instanceOf +import io.kotest.matchers.types.shouldBeInstanceOf import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk @@ -26,8 +27,9 @@ import kotlin.concurrent.thread class HotDataFlowTest : BaseTest() { + // Without an init value, there isn't a way to keep using the flow @Test - fun `init happens on first collection and exception is forwarded`() { + fun `exceptions on initializen are rethrown`() { val testScope = TestCoroutineScope() val hotData = HotDataFlow<String>( loggingTag = "tag", @@ -35,29 +37,6 @@ class HotDataFlowTest : BaseTest() { coroutineContext = Dispatchers.Unconfined, startValueProvider = { throw IOException() } ) - - runBlocking { - // This blocking scope get's the init exception as the first caller - shouldThrow<IOException> { - hotData.data.first() - } - } - - testScope.advanceUntilIdle() - - testScope.uncaughtExceptions.singleOrNull() shouldBe null - } - - @Test - fun `exception is not forwarded if flag is set`() { - val testScope = TestCoroutineScope() - val hotData = HotDataFlow<String>( - loggingTag = "tag", - scope = testScope, - coroutineContext = Dispatchers.Unconfined, - forwardException = false, - startValueProvider = { throw IOException() } - ) runBlocking { withTimeoutOrNull(500) { // This blocking scope get's the init exception as the first caller @@ -136,9 +115,10 @@ class HotDataFlowTest : BaseTest() { thread { (1..200).forEach { _ -> sleep(10) - hotData.updateSafely { - this + 1L - } + hotData.updateAsync( + onUpdate = { this + 1L }, + onError = { throw it } + ) } } } @@ -174,7 +154,7 @@ class HotDataFlowTest : BaseTest() { (1..10).forEach { _ -> thread { (1..400).forEach { _ -> - hotData.updateSafely { + hotData.updateAsync { mutate { this["data"] = getValue("data").copy( number = getValue("data").number + 1 @@ -207,10 +187,10 @@ class HotDataFlowTest : BaseTest() { val testCollector = hotData.data.test(startOnScope = testScope) testCollector.silent = true - hotData.updateSafely { "1" } - hotData.updateSafely { "2" } - hotData.updateSafely { "2" } - hotData.updateSafely { "1" } + hotData.updateAsync { "1" } + hotData.updateAsync { "2" } + hotData.updateAsync { "2" } + hotData.updateAsync { "1" } runBlocking { testCollector.await { list, l -> list.size == 3 } @@ -236,9 +216,9 @@ class HotDataFlowTest : BaseTest() { val sub2 = hotData.data.test(tag = "sub2", startOnScope = this) val sub3 = hotData.data.test(tag = "sub3", startOnScope = this) - hotData.updateSafely { "A" } - hotData.updateSafely { "B" } - hotData.updateSafely { "C" } + hotData.updateAsync { "A" } + hotData.updateAsync { "B" } + hotData.updateAsync { "C" } listOf(sub1, sub2, sub3).forEach { it.await { list, s -> list.size == 4 } @@ -268,7 +248,7 @@ class HotDataFlowTest : BaseTest() { testCollector1.silent = false (1..10).forEach { _ -> - hotData.updateSafely { + hotData.updateAsync { this + 1L } } @@ -308,7 +288,7 @@ class HotDataFlowTest : BaseTest() { sharingBehavior = SharingStarted.Lazily ) - hotData.updateSafely { + hotData.updateAsync { delay(2000) this + 1 } @@ -324,4 +304,86 @@ class HotDataFlowTest : BaseTest() { testCollector.cancel() } + + @Test + fun `blocking update rethrows error`() = runBlocking { + val testScope = TestCoroutineScope() + val hotData = HotDataFlow( + loggingTag = "tag", + scope = testScope, + coroutineContext = testScope.coroutineContext, + startValueProvider = { + delay(2000) + 2 + }, + sharingBehavior = SharingStarted.Lazily + ) + + val testCollector = hotData.data.test(startOnScope = testScope) + + testScope.advanceUntilIdle() + + shouldThrow<IOException> { + hotData.updateBlocking { throw IOException("Suprise") } shouldBe 0 + } + hotData.data.first() shouldBe 2 + + hotData.updateBlocking { 3 } shouldBe 3 + hotData.data.first() shouldBe 3 + + testScope.uncaughtExceptions.singleOrNull() shouldBe null + + testCollector.cancel() + } + + @Test + fun `async updates error handler`() { + val testScope = TestCoroutineScope() + + val hotData = HotDataFlow( + loggingTag = "tag", + scope = testScope, + startValueProvider = { 1 }, + sharingBehavior = SharingStarted.Lazily + ) + + val testCollector = hotData.data.test(startOnScope = testScope) + testScope.advanceUntilIdle() + + hotData.updateAsync { throw IOException("Suprise") } + + testScope.advanceUntilIdle() + + testScope.uncaughtExceptions.single() shouldBe instanceOf(IOException::class) + + testCollector.cancel() + } + + @Test + fun `async updates rethrow errors on hotdata scope if no error handler is set`() = runBlocking { + val testScope = TestCoroutineScope() + + val hotData = HotDataFlow( + loggingTag = "tag", + scope = testScope, + startValueProvider = { 1 }, + sharingBehavior = SharingStarted.Lazily + ) + + val testCollector = hotData.data.test(startOnScope = testScope) + testScope.advanceUntilIdle() + + var thrownError: Exception? = null + + hotData.updateAsync( + onUpdate = { throw IOException("Suprise") }, + onError = { thrownError = it } + ) + + testScope.advanceUntilIdle() + thrownError!!.shouldBeInstanceOf<IOException>() + testScope.uncaughtExceptions.singleOrNull() shouldBe null + + testCollector.cancel() + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt index 54d8d584e583125d432175bc799c7845a1a6e17f..14bb4ab063279a7ec43ffa27d230088de0f65fab 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/formatter/FormatterSubmissionHelperTest.kt @@ -138,18 +138,10 @@ class FormatterSubmissionHelperTest : BaseTest() { oUiState = DeviceUIState.PAIRED_POSITIVE, iResult = context.getString(R.string.test_result_card_status_positive) ) - formatTestResultStatusTextBase( - oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN, - iResult = context.getString(R.string.test_result_card_status_positive) - ) formatTestResultStatusTextBase( oUiState = DeviceUIState.SUBMITTED_FINAL, iResult = context.getString(R.string.test_result_card_status_invalid) ) - formatTestResultStatusTextBase( - oUiState = DeviceUIState.SUBMITTED_INITIAL, - iResult = context.getString(R.string.test_result_card_status_invalid) - ) formatTestResultStatusTextBase( oUiState = DeviceUIState.UNPAIRED, iResult = context.getString(R.string.test_result_card_status_invalid) @@ -174,18 +166,10 @@ class FormatterSubmissionHelperTest : BaseTest() { oUiState = DeviceUIState.PAIRED_POSITIVE, iResult = context.getColorCompat(R.color.colorTextSemanticRed) ) - formatTestResultStatusColorBase( - oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN, - iResult = context.getColorCompat(R.color.colorTextSemanticRed) - ) formatTestResultStatusColorBase( oUiState = DeviceUIState.SUBMITTED_FINAL, iResult = context.getColorCompat(R.color.colorTextSemanticRed) ) - formatTestResultStatusColorBase( - oUiState = DeviceUIState.SUBMITTED_INITIAL, - iResult = context.getColorCompat(R.color.colorTextSemanticRed) - ) formatTestResultStatusColorBase( oUiState = DeviceUIState.UNPAIRED, iResult = context.getColorCompat(R.color.colorTextSemanticRed) @@ -198,9 +182,7 @@ class FormatterSubmissionHelperTest : BaseTest() { formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_ERROR) formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_NO_RESULT) formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_POSITIVE) - formatTestStatusIconBase(oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN) formatTestStatusIconBase(oUiState = DeviceUIState.SUBMITTED_FINAL) - formatTestStatusIconBase(oUiState = DeviceUIState.SUBMITTED_INITIAL) formatTestStatusIconBase(oUiState = DeviceUIState.UNPAIRED) } @@ -210,9 +192,7 @@ class FormatterSubmissionHelperTest : BaseTest() { formatTestResultBase(oUiState = DeviceUIState.PAIRED_ERROR) formatTestResultBase(oUiState = DeviceUIState.PAIRED_NO_RESULT) formatTestResultBase(oUiState = DeviceUIState.PAIRED_POSITIVE) - formatTestResultBase(oUiState = DeviceUIState.PAIRED_POSITIVE_TELETAN) formatTestResultBase(oUiState = DeviceUIState.SUBMITTED_FINAL) - formatTestResultBase(oUiState = DeviceUIState.SUBMITTED_INITIAL) formatTestResultBase(oUiState = DeviceUIState.UNPAIRED) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt index da4f8b70e31ad2b531ee73da732b52d8b1d83dc8..ab535e0277ead7364c92ef994970d204aea11ca8 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt @@ -15,7 +15,7 @@ import de.rki.coronawarnapp.deadman.DeadmanNotificationSender import de.rki.coronawarnapp.deniability.NoiseScheduler import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.notification.GeneralNotifications -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.presencetracing.checkins.checkout.CheckOutNotification import de.rki.coronawarnapp.presencetracing.checkins.checkout.auto.AutoCheckOut @@ -130,7 +130,7 @@ class MockProvider { fun exposureSummaryRepository(): RiskLevelStorage = mockk() @Provides - fun testResultAvailableNotification(): TestResultAvailableNotificationService = mockk() + fun testResultAvailableNotification(): PCRTestResultAvailableNotificationService = mockk() @Provides fun notificationHelper(): GeneralNotifications = mockk() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt index aa99fa078ddeaf064e3f154ba7e717bbdc8b2398..ee61927fa1c66cb697c65d3b0a95d0ef10ceb55d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt @@ -11,7 +11,7 @@ import de.rki.coronawarnapp.coronatest.type.CoronaTest import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest import de.rki.coronawarnapp.notification.GeneralNotifications import de.rki.coronawarnapp.notification.NotificationConstants -import de.rki.coronawarnapp.notification.TestResultAvailableNotificationService +import de.rki.coronawarnapp.notification.PCRTestResultAvailableNotificationService import de.rki.coronawarnapp.util.TimeAndDateExtensions.daysToMilliseconds import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppInjector @@ -41,7 +41,7 @@ import testhelpers.BaseTest class DiagnosisTestResultRetrievalPeriodicWorkerTest : BaseTest() { @MockK lateinit var context: Context @MockK lateinit var request: WorkRequest - @MockK lateinit var testResultAvailableNotificationService: TestResultAvailableNotificationService + @MockK lateinit var testResultAvailableNotificationService: PCRTestResultAvailableNotificationService @MockK lateinit var notificationHelper: GeneralNotifications @MockK lateinit var appComponent: ApplicationComponent @MockK lateinit var encryptedPreferencesFactory: EncryptedPreferencesFactory