diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 89dce2971ae418754d9a64535590705274ecc2ff..25be009f467df291bb66c886d5c653ec3b92bff2 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -173,7 +173,8 @@ android { "-Xno-kotlin-nothing-value-exception", "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi", "-Xuse-experimental=kotlinx.coroutines.FlowPreview", - "-Xuse-experimental=kotlin.time.ExperimentalTime" + "-Xuse-experimental=kotlin.time.ExperimentalTime", + "-Xopt-in=kotlin.RequiresOptIn" ] } } diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json index befd229435a5e47f296ec11550b0b1534c07e0cb..856916f58dfa37e09331ebb2dbe0f6297e126d3e 100644 --- a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json +++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "5117ed4caaa7ecd70051902d844cc665", + "identityHash": "e23913768d43dc0cb1374df83b2fa78a", "entities": [ { "tableName": "checkin", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `traceLocationIdBase64` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `traceLocationStart` TEXT, `traceLocationEnd` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `cryptographicSeedBase64` TEXT NOT NULL, `cnPublicKey` TEXT NOT NULL, `checkInStart` TEXT NOT NULL, `checkInEnd` TEXT NOT NULL, `completed` INTEGER NOT NULL, `createJournalEntry` INTEGER NOT NULL)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `traceLocationIdBase64` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `traceLocationStart` TEXT, `traceLocationEnd` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `cryptographicSeedBase64` TEXT NOT NULL, `cnPublicKey` TEXT NOT NULL, `checkInStart` TEXT NOT NULL, `checkInEnd` TEXT NOT NULL, `completed` INTEGER NOT NULL, `createJournalEntry` INTEGER NOT NULL, `submitted` INTEGER NOT NULL)", "fields": [ { "fieldPath": "id", @@ -97,6 +97,12 @@ "columnName": "createJournalEntry", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "isSubmitted", + "columnName": "submitted", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -186,7 +192,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5117ed4caaa7ecd70051902d844cc665')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e23913768d43dc0cb1374df83b2fa78a')" ] } } \ No newline at end of file diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt index dd683c4bb199b41cb7b86bafd5c90a391a5b63c7..ca8693d3e9f5dbffcb530372d64118dfb452d477 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseMigrationTest.kt @@ -20,6 +20,7 @@ import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Duration import org.joda.time.LocalDate import org.junit.Rule @@ -176,7 +177,7 @@ class ContactDiaryDatabaseMigrationTest : BaseTestInstrumentation() { checkInID = null ) - val locationAfter = location.copy(traceLocationID = "jshrgu-aifhioaio-aofsjof-samofp-kjsadngsgf") + val locationAfter = location.copy(traceLocationID = "jshrgu-aifhioaio-aofsjof-samofp-kjsadngsgf".decodeBase64()) val locationVisitAfter = locationVisit.copy(checkInID = 101) val locationValues = ContentValues().apply { diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt index e3573457d057cb87c2ee4b83e266b9ea276d85b6..6cdc211e2e3dcede9208f035865bd672d741c736 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/contactdiary/storage/ContactDiaryDatabaseTest.kt @@ -14,6 +14,7 @@ import io.kotest.matchers.shouldBe import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Duration import org.joda.time.LocalDate import org.junit.After @@ -37,7 +38,7 @@ class ContactDiaryDatabaseTest : BaseTestInstrumentation() { locationName = "Rewe Wiesloch", emailAddress = "location-emailAddress", phoneNumber = "location-phoneNumber", - traceLocationID = "a-b-c-d" + traceLocationID = "a-b-c-d".decodeBase64() ) private val personEncounter = ContactDiaryPersonEncounterEntity( id = 3, diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt index 370276daf4fe3f5f3dae2b3a188cd403f52488b2..1ab9a7725c591841be423ccaad882b5d9481a744 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt @@ -21,7 +21,8 @@ object CheckInDatabaseData { checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"), checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"), completed = false, - createJournalEntry = true + createJournalEntry = true, + isSubmitted = false, ) val testCheckInWithoutCheckOutTime = TraceLocationCheckInEntity( @@ -38,6 +39,7 @@ object CheckInDatabaseData { checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"), checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"), completed = false, - createJournalEntry = true + createJournalEntry = true, + isSubmitted = false, ) } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt index c1b30a16e5ee62271d1774e40f2bbd7690d67890..44c7ad447b3bf012d76b619785501206af247e24 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingData.kt @@ -48,7 +48,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_INACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) @@ -79,7 +79,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) @@ -110,7 +110,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) @@ -141,7 +141,7 @@ object TracingData { daysSinceInstallation = 4, tracingStatus = GeneralTracingStatus.Status.TRACING_ACTIVE ), - DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedKeyCount = 0) + DetailsLowRiskBox.Item(riskState = RiskState.LOW_RISK, matchedRiskCount = 0) ) ) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/appconfig/ui/AppConfigTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/appconfig/ui/AppConfigTestFragment.kt index 9aa9ce21df5fb36f43c569af882b517ca8751ff5..c151d90a77c3853a014e640c38a5dce0d7f5305e 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/appconfig/ui/AppConfigTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/appconfig/ui/AppConfigTestFragment.kt @@ -28,6 +28,7 @@ class AppConfigTestFragment : Fragment(R.layout.fragment_test_appconfig), AutoIn private val timeFormatter = ISODateTimeFormat.dateTime() .withZone(DateTimeZone.forID("Europe/Berlin")) + @Suppress("DEPRECATION") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt index b02e7a18b31e5d7475a9e102204205ac43a1840c..0b43e0de5005ba6180632620c5f6ee808022fe16 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/crash.ui/SettingsCrashReportViewModel.kt @@ -31,7 +31,7 @@ class SettingsCrashReportViewModel @AssistedInject constructor( fun simulateException() { try { - val a = 2 / 0 + throw RuntimeException("Test crash reporting") } catch (e: Exception) { Timber.e(e, "Msg: ${e.message}") e.reportProblem(SettingsCrashReportViewModel::class.java.simpleName, e.message) diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt index 49daaf6fa2da7d161058154d3607cca0196ca57c..9152f953ae3a51042e5a7e816e5a0f5998b3dc6b 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt @@ -6,6 +6,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.eventregistration.TraceLocationSettings import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModel @@ -13,6 +14,7 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory class DeltaOnboardingFragmentViewModel @AssistedInject constructor( private val settings: CWASettings, + private val traceLocationSettings: TraceLocationSettings, private val contactDiarySettings: ContactDiarySettings, dispatcherProvider: DispatcherProvider ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { @@ -43,10 +45,21 @@ class DeltaOnboardingFragmentViewModel @AssistedInject constructor( fun isDeltaOnboardingDone() = settings.wasInteroperabilityShownAtLeastOnce - fun setDeltaOboardinDone(value: Boolean) { + fun setDeltaOnboardingDone(value: Boolean) { settings.wasInteroperabilityShownAtLeastOnce = value } + fun isAttendeeOnboardingDone() = + traceLocationSettings.onboardingStatus == TraceLocationSettings.OnboardingStatus.ONBOARDED_2_0 + + fun setAttendeeOnboardingDone(value: Boolean) { + traceLocationSettings.onboardingStatus = + if (value) + TraceLocationSettings.OnboardingStatus.ONBOARDED_2_0 + else + TraceLocationSettings.OnboardingStatus.NOT_ONBOARDED + } + @AssistedFactory interface Factory : SimpleCWAViewModelFactory<DeltaOnboardingFragmentViewModel> } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt index 1179e8e0701823179fafe78cb56af5e5dc210d98..1308cff1b13d6ab80567562b9d346df0fb14a5d2 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt @@ -26,6 +26,7 @@ class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding) binding.switchContactJournalOnboarding.isChecked = viewModel.isContactJournalOnboardingDone() binding.switchDeltaOnboarding.isChecked = viewModel.isDeltaOnboardingDone() + binding.switchAttendeeOnboarding.isChecked = viewModel.isAttendeeOnboardingDone() viewModel.changelogVersion.observe(viewLifecycleOwner) { binding.lastChangelogEdittext.setText(it.toString()) } @@ -48,7 +49,11 @@ class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding) } binding.switchDeltaOnboarding.setOnCheckedChangeListener { _, value -> - viewModel.setDeltaOboardinDone(value) + viewModel.setDeltaOnboardingDone(value) + } + + binding.switchAttendeeOnboarding.setOnCheckedChangeListener { _, value -> + viewModel.setAttendeeOnboardingDone(value) } } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt index e631969b4290fe76b22c48870f35bb55b96d1b9d..b4200bc33c96ba2d8132bc803a3657f3831a69f5 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/menu/ui/TestMenuFragmentViewModel.kt @@ -10,7 +10,7 @@ import de.rki.coronawarnapp.test.crash.ui.SettingsCrashReportFragment import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment -import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment +import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment @@ -36,7 +36,7 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { PlaygroundFragment.MENU_ITEM, DataDonationTestFragment.MENU_ITEM, DeltaonboardingFragment.MENU_ITEM, - EventRegistrationTestFragment.MENU_ITEM, + PresenceTracingTestFragment.MENU_ITEM, ).let { MutableLiveData(it) } } val showTestScreenEvent = SingleLiveEvent<TestMenuItem>() diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt similarity index 84% rename from Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt rename to Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt index 95d522e04234c83bbafcb5d659bf3b60cca610e0..8e54311d1f424a8bb319fe076fc00ba731524871 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragment.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.test.eventregistration.ui +package de.rki.coronawarnapp.test.presencetracing.ui import android.annotation.SuppressLint import android.os.Bundle @@ -11,7 +11,7 @@ import androidx.core.text.scale import androidx.core.view.isVisible import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.FragmentTestEventregistrationBinding +import de.rki.coronawarnapp.databinding.FragmentTestPresenceTracingBinding import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.test.menu.ui.TestMenuItem import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat @@ -24,12 +24,12 @@ import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import javax.inject.Inject @SuppressLint("SetTextI18n") -class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregistration), AutoInject { +class PresenceTracingTestFragment : Fragment(R.layout.fragment_test_presence_tracing), AutoInject { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - private val viewModel: EventRegistrationTestFragmentViewModel by cwaViewModels { viewModelFactory } + private val viewModel: PresenceTracingTestViewModel by cwaViewModels { viewModelFactory } - private val binding: FragmentTestEventregistrationBinding by viewBindingLazy() + private val binding: FragmentTestPresenceTracingBinding by viewBindingLazy() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -71,8 +71,8 @@ class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregis lastOrganiserLocationUrl.text = styleText("URL", traceLocation.locationUrl) qrcodeButton.setOnClickListener { doNavigate( - EventRegistrationTestFragmentDirections - .actionEventRegistrationTestFragmentToQrCodePosterFragmentTest(traceLocation.id) + PresenceTracingTestFragmentDirections + .actionPresenceTracingTestFragmentToQrCodePosterTestFragment(traceLocation.id) ) } } @@ -112,7 +112,7 @@ class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregis buildSpannedString { bold { color(requireContext().getColorCompat(R.color.colorAccent)) { - append("$key=") + append("$key = ") } } @@ -121,14 +121,14 @@ class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregis append(value.toString()) } } - append("\n") + appendLine() } companion object { val MENU_ITEM = TestMenuItem( - title = "Event Registration", - description = "View & Control the event registration.", - targetId = R.id.eventRegistrationTestFragment + title = "Presence Tracing", + description = "View & Control presence tracing", + targetId = R.id.presenceTracingTestFragment ) } } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragmentModule.kt similarity index 53% rename from Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentModule.kt rename to Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragmentModule.kt index 3c95a0a4a47d729182eca300cc9e5b7a70ad655f..db41dfdf6a73e26b68393cb7cac9f3b978fba903 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentModule.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestFragmentModule.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.test.eventregistration.ui +package de.rki.coronawarnapp.test.presencetracing.ui import dagger.Binds import dagger.Module @@ -8,11 +8,11 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey @Module -abstract class EventRegistrationTestFragmentModule { +abstract class PresenceTracingTestFragmentModule { @Binds @IntoMap - @CWAViewModelKey(EventRegistrationTestFragmentViewModel::class) - abstract fun testEventRegistrationFragment( - factory: EventRegistrationTestFragmentViewModel.Factory + @CWAViewModelKey(PresenceTracingTestViewModel::class) + abstract fun testPresenceTracingFragment( + factory: PresenceTracingTestViewModel.Factory ): CWAViewModelFactory<out CWAViewModel> } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestViewModel.kt similarity index 96% rename from Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentViewModel.kt rename to Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestViewModel.kt index 3913503fe7082eb9597ce0f5b8d136bd9d0513e1..7e0e674bcee1e3f48cb962d9a1f286e9ef20f8ba 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/PresenceTracingTestViewModel.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.test.eventregistration.ui +package de.rki.coronawarnapp.test.presencetracing.ui import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.map import timber.log.Timber import kotlin.system.measureTimeMillis -class EventRegistrationTestFragmentViewModel @AssistedInject constructor( +class PresenceTracingTestViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, traceLocationRepository: TraceLocationRepository, checkInRepository: CheckInRepository, @@ -144,5 +144,5 @@ class EventRegistrationTestFragmentViewModel @AssistedInject constructor( } @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<EventRegistrationTestFragmentViewModel> + interface Factory : SimpleCWAViewModelFactory<PresenceTracingTestViewModel> } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..c4aa4a257925c75f84e399058b78d5abfc9eddb6 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestFragment.kt @@ -0,0 +1,298 @@ +package de.rki.coronawarnapp.test.presencetracing.ui.poster + +import android.annotation.SuppressLint +import android.os.Bundle +import android.print.PrintAttributes +import android.print.PrintManager +import android.text.SpannedString +import android.util.TypedValue +import android.view.View +import android.widget.Toast +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.getSystemService +import androidx.core.text.bold +import androidx.core.text.buildSpannedString +import androidx.core.text.color +import androidx.core.view.isVisible +import androidx.core.widget.TextViewCompat +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentTestQrCodePosterBinding +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.server.protocols.internal.pt.QrCodePosterTemplate +import de.rki.coronawarnapp.ui.color.parseColor +import de.rki.coronawarnapp.ui.print.PrintingAdapter +import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat +import de.rki.coronawarnapp.util.di.AutoInject +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 timber.log.Timber +import java.io.File +import javax.inject.Inject + +@SuppressLint("SetTextI18n") +class QrCodePosterTestFragment : Fragment(R.layout.fragment_test_qr_code_poster), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + + private val args by navArgs<QrCodePosterTestFragmentArgs>() + private val viewModel: QrCodePosterTestViewModel by cwaViewModelsAssisted( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, _ -> + factory as QrCodePosterTestViewModel.Factory + factory.create(args.traceLocationId) + } + ) + + private var itemId = -1 + + private val binding: FragmentTestQrCodePosterBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + with(binding) { + toolbar.setNavigationOnClickListener { popBackStack() } + viewModel.poster.observe(viewLifecycleOwner) { poster -> + bindPoster(poster) + bindToolbar() + } + } + + viewModel.sharingIntent.observe(viewLifecycleOwner) { fileIntent -> + when (itemId) { + R.id.action_print -> printFile(fileIntent.file) + R.id.action_share -> startActivity(fileIntent.intent(requireActivity())) + } + } + + viewModel.qrCodeBitmap.observe(viewLifecycleOwner) { + binding.qrCodeImage.setImageBitmap(it) + } + } + + private fun FragmentTestQrCodePosterBinding.bindPoster(poster: QrCodePosterTestViewModel.Poster) { + Timber.d("poster=$poster") + progressBar.hide() + + val template = poster.template ?: return // Exit early + Timber.d("template=$template") + + // Adjust poster image dimensions ratio to have a proper printing preview + val posterLayoutParam = posterImage.layoutParams as ConstraintLayout.LayoutParams + val dimensionRatio = template.run { "$width:$height" } // W:H + Timber.d("dimensionRatio=$dimensionRatio") + posterLayoutParam.dimensionRatio = dimensionRatio + + // Display images + qrCodeImage.setImageBitmap(poster.qrCode) + posterImage.setImageBitmap(template.bitmap) + + // Position QR Code image based on data provided by server + topGuideline.setGuidelinePercent(template.offsetY) + startGuideline.setGuidelinePercent(template.offsetX) + endGuideline.setGuidelinePercent(1 - template.offsetX) + + // Qr Code positioning + qrOffsetXSlider.apply { + value = template.offsetX.sliderValue + addOnChangeListener { _, value, fromUser -> + if (fromUser) { + val offset = value.percentage + startGuideline.setGuidelinePercent(offset) + endGuideline.setGuidelinePercent(1 - offset) + updateQrCodeOffsetText() + } + } + } + qrOffsetYSlider.apply { + value = template.offsetY.sliderValue + addOnChangeListener { _, value, fromUser -> + if (fromUser) { + topGuideline.setGuidelinePercent(value.percentage) + updateQrCodeOffsetText() + } + } + } + updateQrCodeOffsetText() + + qrLengthSlider.apply { + value = poster.template.qrCodeLength.toFloat() + addOnChangeListener { _, value, _ -> + updateQrCodeLengthText() + viewModel.generateQrCode(value.toInt()) + } + } + updateQrCodeLengthText() + + // Bind text info + bindTextBox(poster.infoText, poster.template.textBox) + offsetsPanel.isVisible = true + tooltip.setOnClickListener { + Toast.makeText(requireContext(), toastText(), Toast.LENGTH_LONG).show() + } + } + + private fun toastText(): SpannedString = + buildSpannedString { + bold { + append("Tips:") + } + color(requireContext().getColorCompat(R.color.colorAccent)) { + appendLine() + appendLine() + append( + "- Qr-Code Length defines Qr-Code bitmap length and not" + + "\nthe displayed Qr-Code ImageView where its length is flexible per screen size" + + "\nIn a way it defines the bitmap quality." + + "\nThe more the length the more the bitmaps's sharpness." + ) + + appendLine() + appendLine() + append( + "- Text below Qr-Code has max 2 lines and has a uniform auto scaling down to `fontSize - 6`." + + "\nIf the size is way larger than it fits in two lines, it will be cut." + ) + + appendLine() + appendLine() + append( + "- OffsetX defines the position of two guidelines left and right for the edges." + + "\n If the offset is increasing, the space in between the guidelines is decreasing." + + "\n Which means less width of the respective view. -> | -> view_width <- | <- " + ) + } + } + + private fun updateQrCodeLengthText() { + val value = binding.qrLengthSlider.value + binding.qrCodeLength.text = "Qr Code length:%d".format(value.toInt()) + } + + private fun FragmentTestQrCodePosterBinding.bindTextBox( + infoText: String, + textBox: QrCodePosterTemplate.QRCodePosterTemplateAndroid.QRCodeTextBoxAndroid + ) = with(infoTextView) { + text = infoText + setFontSize(textBox.fontSize) + setTextColor(textBox.fontColor.parseColor()) + textEndGuideline.setGuidelinePercent(1 - textBox.offsetX) + textStartGuideline.setGuidelinePercent(textBox.offsetX) + textTopGuideline.setGuidelinePercent(textBox.offsetY) + + // Text Position + txtOffsetXSlider.apply { + value = textBox.offsetX.sliderValue + addOnChangeListener { _, value, fromUser -> + if (fromUser) { + val offset = value.percentage + textEndGuideline.setGuidelinePercent(1 - offset) + textStartGuideline.setGuidelinePercent(offset) + updateInfoOffsetText() + } + } + } + txtOffsetYSlider.apply { + value = textBox.offsetY.sliderValue + addOnChangeListener { _, value, fromUser -> + if (fromUser) { + textTopGuideline.setGuidelinePercent(value.percentage) + updateInfoOffsetText() + } + } + } + updateInfoOffsetText() + + // Text Size + infoTextSizeSlider.apply { + value = textBox.fontSize.toFloat() + addOnChangeListener { _, value, _ -> + updateFontSizeText() + setFontSize(value.toInt()) + } + } + updateFontSizeText() + + // Text Color + infoTextColorValue.doOnTextChanged { color, _, _, _ -> + infoTextView.setTextColor(color.toString().parseColor()) + } + } + + private fun updateFontSizeText() { + binding.infoTextSize.text = + "Font size: %s sp".format(binding.infoTextSizeSlider.value) + } + + private fun FragmentTestQrCodePosterBinding.setFontSize(maxFontSize: Int) { + val minFontSize = maxFontSize - 6 + TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration( + infoTextView, + minFontSize, + maxFontSize, + 1, + TypedValue.COMPLEX_UNIT_SP + ) + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, maxFontSize.toFloat()) + } + + private fun FragmentTestQrCodePosterBinding.bindToolbar() { + toolbar.setOnMenuItemClickListener { + itemId = it.itemId + viewModel.createPDF(binding.qrCodePoster) + true + } + } + + private fun printFile(file: File) { + val printingManger = context?.getSystemService<PrintManager>() + Timber.i("PrintingManager=$printingManger") + if (printingManger == null) { + Toast.makeText(requireContext(), R.string.errors_generic_headline, Toast.LENGTH_LONG).show() + return + } + + try { + val job = printingManger.print( + getString(R.string.app_name), + PrintingAdapter(file), + PrintAttributes.Builder() + .setMediaSize(PrintAttributes.MediaSize.ISO_A3) + .build() + ) + + Timber.d("JobState=%s", job.info.state) + } catch (e: Exception) { + Timber.d(e, "Printing job failed") + e.report(ExceptionCategory.INTERNAL) + } + } + + private fun updateQrCodeOffsetText() { + with(binding) { + qrCodeOffsets.text = "Qr Code offsets: X=%.3f, Y=%.3f".format( + qrOffsetXSlider.value.percentage, + qrOffsetYSlider.value.percentage + ) + } + } + + private fun updateInfoOffsetText() { + with(binding) { + infoTextOffsets.text = "Text offsets: X=%.3f, Y=%.3f".format( + txtOffsetXSlider.value.percentage, + txtOffsetYSlider.value.percentage + ) + } + } + + private val Float.percentage get() = this / 1000 + + private val Float.sliderValue get() = this * 1000 +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestFragmentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..871e9145d64a740fe20b77525813dc9174aca507 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestFragmentModule.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.test.presencetracing.ui.poster + +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 QrCodePosterTestFragmentModule { + @Binds + @IntoMap + @CWAViewModelKey(QrCodePosterTestViewModel::class) + abstract fun qrCodePosterTestFragmentModule( + factory: QrCodePosterTestViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..f4cf799fa56161f0b5171622d89d8cca564f7e66 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/presencetracing/ui/poster/QrCodePosterTestViewModel.kt @@ -0,0 +1,138 @@ +package de.rki.coronawarnapp.test.presencetracing.ui.poster + +import android.graphics.Bitmap +import android.graphics.pdf.PdfDocument +import android.view.View +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.PosterTemplateProvider +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QrCodeGenerator +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.Template +import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository +import de.rki.coronawarnapp.exception.ExceptionCategory +import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.files.FileSharing +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import timber.log.Timber +import java.io.File +import java.io.FileOutputStream +import java.lang.ref.WeakReference + +class QrCodePosterTestViewModel @AssistedInject constructor( + @Assisted private val traceLocationId: Long, + private val dispatcher: DispatcherProvider, + private val qrCodeGenerator: QrCodeGenerator, + private val posterTemplateProvider: PosterTemplateProvider, + private val traceLocationRepository: TraceLocationRepository, + private val fileSharing: FileSharing +) : CWAViewModel(dispatcher) { + + private val posterLiveData = MutableLiveData<Poster>() + val poster: LiveData<Poster> = posterLiveData + val sharingIntent = SingleLiveEvent<FileSharing.FileIntentProvider>() + val qrCodeBitmap = SingleLiveEvent<Bitmap>() + private var isRunning = false + + init { + generatePoster() + } + + /** + * Create a new PDF file and result is delivered by [sharingIntent] + * as a sharing [FileSharing.ShareIntentProvider] + */ + @Suppress("BlockingMethodInNonBlockingContext") + fun createPDF(view: View) = launch(context = dispatcher.IO) { + try { + val weakViewRef = WeakReference(view) // Accessing view in background thread + val directory = File(view.context.cacheDir, "poster").apply { if (!exists()) mkdirs() } + val file = File(directory, "cwa-qr-code.pdf") + + val weakView = weakViewRef.get() ?: return@launch // View is not existing anymore + val pageInfo = PdfDocument.PageInfo.Builder(weakView.width, weakView.height, 1).create() + + PdfDocument().apply { + startPage(pageInfo).apply { + weakView.draw(canvas) + finishPage(this) + } + + FileOutputStream(file).use { + writeTo(it) + close() + } + } + + sharingIntent.postValue(fileSharing.getFileIntentProvider(file, traceLocation().description)) + } catch (e: Exception) { + Timber.d(e, "Creating pdf failed") + e.report(ExceptionCategory.INTERNAL) + } + } + + fun generateQrCode(length: Int) = launch(context = dispatcher.IO) { + try { + if (isRunning) return@launch + isRunning = true + val traceLocation = traceLocation() + val qrCode = qrCodeGenerator.createQrCode( + input = traceLocation.locationUrl, + length = length, + margin = 0 + ) + qrCodeBitmap.postValue(qrCode) + } catch (e: Exception) { + Timber.e(e) + e.report(ExceptionCategory.INTERNAL) + } finally { + isRunning = false + } + } + + private fun generatePoster() = launch(context = dispatcher.IO) { + try { + val traceLocation = traceLocation() + val template = posterTemplateProvider.template() + Timber.d("template=$template") + val qrCode = qrCodeGenerator.createQrCode( + input = traceLocation.locationUrl, + length = template.qrCodeLength, + margin = 0 + ) + + val textInfo = buildString { + append(traceLocation.description) + appendLine() + append(traceLocation.address) + } + posterLiveData.postValue( + Poster(qrCode, template, textInfo) + ) + } catch (e: Exception) { + Timber.d(e, "Generating poster failed") + posterLiveData.postValue(Poster()) + e.report(ExceptionCategory.INTERNAL) + } + } + + private suspend fun traceLocation() = traceLocationRepository.traceLocationForId(traceLocationId) + + @AssistedFactory + interface Factory : CWAViewModelFactory<QrCodePosterTestViewModel> { + fun create( + traceLocationId: Long + ): QrCodePosterTestViewModel + } + + data class Poster( + val qrCode: Bitmap? = null, + val template: Template? = null, + val infoText: String = "" + ) +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt index ed246e65b60fc63a60c27263933ca1ee8315a5c0..d8968b77d5260df6292fb3eb131ad0e9685fa404 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/risklevel/ui/TestRiskLevelCalculationFragmentCWAViewModel.kt @@ -104,6 +104,7 @@ class TestRiskLevelCalculationFragmentCWAViewModel @AssistedInject constructor( .appendLine("Number of Days With Low Risk: $numberOfDaysWithLowRisk") .toString() + @Suppress("DEPRECATION") val backendParameters = appConfigProvider .currentConfig .map { it.rawConfig.riskCalculationParameters.toString() } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt index 9728bc8fd07c67ac0dec8dfe6185f066692b91e9..e4a3ea0afa6b77501ace2c87fe364335253996e6 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/ui/main/MainActivityTestModule.kt @@ -14,22 +14,22 @@ import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragmentModule import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaOnboardingFragmentModule import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment -import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment -import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragmentModule +import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment +import de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragmentModule import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment import de.rki.coronawarnapp.test.menu.ui.TestMenuFragmentModule import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment import de.rki.coronawarnapp.test.playground.ui.PlaygroundModule +import de.rki.coronawarnapp.test.presencetracing.ui.poster.QrCodePosterTestFragment +import de.rki.coronawarnapp.test.presencetracing.ui.poster.QrCodePosterTestFragmentModule import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragmentModule import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragment import de.rki.coronawarnapp.test.submission.ui.SubmissionTestFragmentModule import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragment import de.rki.coronawarnapp.test.tasks.ui.TestTaskControllerFragmentModule -import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragment -import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragmentModule @Module abstract class MainActivityTestModule { @@ -70,9 +70,9 @@ abstract class MainActivityTestModule { @ContributesAndroidInjector(modules = [DeltaOnboardingFragmentModule::class]) abstract fun deltaOnboarding(): DeltaonboardingFragment - @ContributesAndroidInjector(modules = [EventRegistrationTestFragmentModule::class]) - abstract fun eventRegistration(): EventRegistrationTestFragment + @ContributesAndroidInjector(modules = [PresenceTracingTestFragmentModule::class]) + abstract fun eventRegistration(): PresenceTracingTestFragment - @ContributesAndroidInjector(modules = [QrCodeDetailFragmentModule::class]) - abstract fun showEventDetail(): QrCodeDetailFragment + @ContributesAndroidInjector(modules = [QrCodePosterTestFragmentModule::class]) + abstract fun showEventDetail(): QrCodePosterTestFragment } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml index 374995566303f641149887fb280e156f3eec5f80..b1305ae31eaabe74c88dd174a649dcb4787db6be 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml @@ -146,5 +146,32 @@ </androidx.constraintlayout.widget.ConstraintLayout> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/debug_container4" + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/event_creation_title" + style="@style/headline6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Event Creation Onboarding" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.switchmaterial.SwitchMaterial + android:id="@+id/switch_attendee_onboarding" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="Attendee onboarding finished" + app:layout_constraintTop_toBottomOf="@id/event_creation_title" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml similarity index 99% rename from Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml rename to Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml index 1e4441e000d99e3810c34fcc01a9b9c9c4dd31ff..dba2acc1b028e69527c54e05b6eb93271f194782 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_presence_tracing.xml @@ -16,6 +16,7 @@ style="@style/Card" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginTop="10dp" android:layout_marginHorizontal="@dimen/spacing_tiny" android:orientation="vertical"> diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qr_code_poster.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qr_code_poster.xml new file mode 100644 index 0000000000000000000000000000000000000000..590b0a48c28bc6f1771a2a1063778e0c93b67f41 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_qr_code_poster.xml @@ -0,0 +1,277 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.coordinatorlayout.widget.CoordinatorLayout 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" + tools:ignore="HardcodedText"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/white"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + style="@style/CWAToolbar.BackArrow" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:elevation="2dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:menu="@menu/menu_trace_location_qr_code_poster" + app:title="@string/trace_location_organiser_poster_title" /> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/qr_code_poster" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar"> + + <ImageView + android:id="@+id/poster_image" + android:layout_width="0dp" + android:layout_height="0dp" + android:adjustViewBounds="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:layout_constraintDimensionRatio="595:841" /> + + <ImageView + android:id="@+id/qr_code_image" + android:layout_width="0dp" + android:layout_height="0dp" + android:scaleType="fitXY" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toEndOf="@id/end_guideline" + app:layout_constraintStart_toStartOf="@id/start_guideline" + app:layout_constraintTop_toTopOf="@id/top_guideline" + tools:src="@drawable/ic_qrcode" + tools:tint="@android:color/black" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/start_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.16" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/end_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.84" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/top_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.095" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/text_start_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.132" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/text_end_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.87" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/text_top_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.61" /> + + <TextView + android:id="@+id/info_text_view" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:maxLines="2" + app:layout_constraintEnd_toEndOf="@id/text_end_guideline" + app:layout_constraintStart_toStartOf="@id/text_start_guideline" + app:layout_constraintTop_toTopOf="@id/text_top_guideline" + tools:ignore="SmallSp" + tools:text="Vereinsaktivität: Jahrestreffen der deutschen SAP Anwendergruppe\nHauptstr 3, 69115 Heidelberg" + tools:textColor="#000000" + tools:textSize="10sp" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + <com.google.android.material.progressindicator.LinearProgressIndicator + android:id="@+id/progress_bar" + android:layout_width="150dp" + android:layout_height="wrap_content" + android:indeterminate="true" + app:hideAnimationBehavior="inward" + app:indicatorColor="@color/colorAccent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + + </androidx.constraintlayout.widget.ConstraintLayout> + + <ScrollView + android:id="@+id/offsets_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + app:behavior_peekHeight="60dp" + app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> + + <LinearLayout + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="10dp" + android:orientation="horizontal" + android:paddingBottom="20dp"> + + <ImageView + android:id="@+id/tooltip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|center" + android:background="?selectableItemBackgroundBorderless" + app:srcCompat="@drawable/ic_info" /> + + <View + android:layout_width="100dp" + android:layout_height="5dp" + android:layout_gravity="center" + android:background="@color/colorAccent" /> + + </FrameLayout> + + <TextView + android:id="@+id/qr_code_offsets" + style="@style/body2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="QR Code offsets" /> + + <com.google.android.material.slider.Slider + android:id="@+id/qrOffsetXSlider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:stepSize="1" + android:valueFrom="0" + android:valueTo="1000" + app:labelBehavior="gone" /> + + <com.google.android.material.slider.Slider + android:id="@+id/qrOffsetYSlider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:stepSize="1" + android:valueFrom="0" + android:valueTo="1000" + app:labelBehavior="gone" /> + + <TextView + android:id="@+id/qr_code_length" + style="@style/body2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="Qr Code length" /> + + <com.google.android.material.slider.Slider + android:id="@+id/qrLengthSlider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:stepSize="100" + android:valueFrom="500" + android:valueTo="2000" + app:labelBehavior="gone" /> + + <View + android:layout_width="match_parent" + android:layout_height="5dp" + android:layout_marginVertical="10dp" + android:background="#BE818181" /> + + <TextView + android:id="@+id/info_text_offsets" + style="@style/body2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="Text offsets" /> + + <com.google.android.material.slider.Slider + android:id="@+id/txtOffsetXSlider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:stepSize="1" + android:valueFrom="0" + android:valueTo="1000" + app:labelBehavior="gone" /> + + <com.google.android.material.slider.Slider + android:id="@+id/txtOffsetYSlider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:stepSize="1" + android:valueFrom="0" + android:valueTo="1000" + app:labelBehavior="gone" /> + + <TextView + android:id="@+id/info_text_size" + style="@style/body2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="Font size" /> + + <com.google.android.material.slider.Slider + android:id="@+id/infoTextSizeSlider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:stepSize="1" + android:valueFrom="10" + android:valueTo="30" + app:labelBehavior="gone" /> + + <TextView + style="@style/body2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:text="Font Color" /> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/info_text_color_value" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginVertical="5dp" + android:hint="#000000 - FallbackColor=#000000" /> + </LinearLayout> + </ScrollView> +</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml index 1586ca6972c5d45da2f811603c2bce38e0fcf812..a8cdc52cfa9795a8acb3cced1acab554f81ed1d9 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/navigation/test_nav_graph.xml @@ -47,8 +47,8 @@ android:id="@+id/action_test_menu_fragment_to_deltaonboardingFragment" app:destination="@id/test_deltaonboarding_fragment" /> <action - android:id="@+id/action_test_menu_fragment_to_eventRegistrationTestFragment" - app:destination="@id/eventRegistrationTestFragment" /> + android:id="@+id/action_test_menu_fragment_to_presenceTracingTestFragment" + app:destination="@id/presenceTracingTestFragment" /> </fragment> <fragment @@ -128,19 +128,19 @@ android:label="DeltaonboardingFragment" tools:layout="@layout/fragment_test_deltaonboarding" /> <fragment - android:id="@+id/eventRegistrationTestFragment" - android:name="de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment" - android:label="EventRegistrationTestFragment" - tools:layout="@layout/fragment_test_eventregistration"> + android:id="@+id/presenceTracingTestFragment" + android:name="de.rki.coronawarnapp.test.presencetracing.ui.PresenceTracingTestFragment" + android:label="PresenceTracingTestFragment" + tools:layout="@layout/fragment_test_presence_tracing"> <action - android:id="@+id/action_eventRegistrationTestFragment_to_qrCodePosterFragmentTest" - app:destination="@id/qrCodePosterFragmentTest" /> + android:id="@+id/action_presenceTracingTestFragment_to_qrCodePosterTestFragment" + app:destination="@id/qrCodePosterTestFragment" /> </fragment> <fragment - android:id="@+id/qrCodePosterFragmentTest" - android:name="de.rki.coronawarnapp.ui.eventregistration.organizer.poster.QrCodePosterFragment" - android:label="qr_code_poster_fragment" - tools:layout="@layout/qr_code_poster_fragment"> + android:id="@+id/qrCodePosterTestFragment" + android:name="de.rki.coronawarnapp.test.presencetracing.ui.poster.QrCodePosterTestFragment" + android:label="QrCodePosterTestFragment" + tools:layout="@layout/fragment_test_qr_code_poster"> <argument android:name="traceLocationId" diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml index 2cefbd599e3a740713e55a733c3b04b38949d234..4ff4927e20f6d0a5aec02068692448d34c55e148 100644 --- a/Corona-Warn-App/src/main/AndroidManifest.xml +++ b/Corona-Warn-App/src/main/AndroidManifest.xml @@ -77,7 +77,7 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> - <intent-filter> + <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt index e0486049d69e35824cd324a64bdf7ae6a2772472..020bb3f955ec83abfa6992437d69db66c1f4a7bf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/ContactDiaryLocation.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.contactdiary.model +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import de.rki.coronawarnapp.util.lists.HasStableId import java.util.Locale @@ -8,7 +9,7 @@ interface ContactDiaryLocation : HasStableId { var locationName: String val phoneNumber: String? val emailAddress: String? - val traceLocationID: String? + val traceLocationID: TraceLocationId? } fun List<ContactDiaryLocation>.sortByNameAndIdASC(): List<ContactDiaryLocation> = diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt index c121e261124362fec889e560780bd48608339fa2..9ebd6f415d6f0ac8a6d5045215f9d776dc763445 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/model/DefaultContactDiaryLocation.kt @@ -1,11 +1,13 @@ package de.rki.coronawarnapp.contactdiary.model +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId + data class DefaultContactDiaryLocation( override val locationId: Long = 0L, override var locationName: String, override val phoneNumber: String? = null, override val emailAddress: String? = null, - override val traceLocationID: String? = null + override val traceLocationID: TraceLocationId? = null ) : ContactDiaryLocation { override val stableId: Long get() = locationId diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt index b09a84c8fb451d495de68e89deaed270168dc704..ba4f25464b0caf198370cd9ad539d02f6e25e840 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt @@ -5,6 +5,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import de.rki.coronawarnapp.util.trimToLength import kotlinx.parcelize.Parcelize @@ -15,7 +16,7 @@ data class ContactDiaryLocationEntity( @ColumnInfo(name = "locationName") override var locationName: String, override val phoneNumber: String?, override val emailAddress: String?, - @ColumnInfo(name = "traceLocationID") override val traceLocationID: String? + @ColumnInfo(name = "traceLocationID") override val traceLocationID: TraceLocationId? ) : ContactDiaryLocation, Parcelable { override val stableId: Long get() = locationId diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt index bb81b52052a6ab2d2583276dd2bb9046d4b162f9..09ca3ef2e455fa1e4ed4e540ad7feea2773f5f08 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModel.kt @@ -60,7 +60,7 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor( private val riskLevelPerDateFlow = riskLevelStorage.ewDayRiskStates private val traceLocationCheckInRiskFlow = riskLevelStorage.traceLocationCheckInRiskStates - private val allCheckInsFlow = checkInRepository.allCheckIns + private val checkInsWithinRetentionFlow = checkInRepository.checkInsWithinRetention val listItems = combine( flowOf(dates), @@ -68,7 +68,7 @@ class ContactDiaryOverviewViewModel @AssistedInject constructor( personEncountersFlow, riskLevelPerDateFlow, traceLocationCheckInRiskFlow, - allCheckInsFlow + checkInsWithinRetentionFlow ) { dateList, locationVisists, personEncounters, riskLevelPerDateList, traceLocationCheckInRiskList, checkInList -> mutableListOf<DiaryOverviewItem>().apply { add(OverviewSubHeaderItem) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainer.kt index dcf91e1f6f93e69204fb752aacc52d38168133aa..c0d63aa1750ad9f55db3913d85c47c95356ff99b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainer.kt @@ -16,31 +16,31 @@ internal data class AttestationContainer( safetyNetJws = report.jwsResult }.build() - override fun requirePass(reqs: SafetyNetRequirements) { - Timber.v("requirePass(%s)", reqs) + override fun requirePass(requirements: SafetyNetRequirements) { + Timber.v("requirePass(%s)", requirements) - if (reqs.requireBasicIntegrity && !report.basicIntegrity) { + if (requirements.requireBasicIntegrity && !report.basicIntegrity) { throw SafetyNetException( Type.BASIC_INTEGRITY_REQUIRED, "Requirement 'basicIntegrity' not met (${report.advice})." ) } - if (reqs.requireCTSProfileMatch && !report.ctsProfileMatch) { + if (requirements.requireCTSProfileMatch && !report.ctsProfileMatch) { throw SafetyNetException( Type.CTS_PROFILE_MATCH_REQUIRED, "Requirement 'ctsProfileMatch' not met (${report.advice})." ) } - if (reqs.requireBasicIntegrity && !report.evaluationTypes.contains("BASIC")) { + if (requirements.requireBasicIntegrity && !report.evaluationTypes.contains("BASIC")) { throw SafetyNetException( Type.EVALUATION_TYPE_BASIC_REQUIRED, "Evaluation type 'BASIC' not met (${report.advice})." ) } - if (reqs.requireEvaluationTypeHardwareBacked && !report.evaluationTypes.contains("HARDWARE_BACKED")) { + if (requirements.requireEvaluationTypeHardwareBacked && !report.evaluationTypes.contains("HARDWARE_BACKED")) { throw SafetyNetException( Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED, "Evaluation type 'HARDWARE_BACKED' not met (${report.advice})." diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt index 1f755923ad63b8c50438a965499c63fd4c005fda..8fea0b27af22d976d99d8a73a5c379cedd6759ea 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckIn.kt @@ -22,7 +22,8 @@ data class CheckIn( val checkInStart: Instant, val checkInEnd: Instant, val completed: Boolean, - val createJournalEntry: Boolean + val createJournalEntry: Boolean, + val isSubmitted: Boolean = false, ) { /** * Returns SHA-256 hash of [traceLocationId] which itself may also be SHA-256 hash. @@ -48,5 +49,6 @@ fun CheckIn.toEntity() = TraceLocationCheckInEntity( checkInStart = checkInStart, checkInEnd = checkInEnd, completed = completed, - createJournalEntry = createJournalEntry + createJournalEntry = createJournalEntry, + isSubmitted = isSubmitted, ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt index 3420d4832fce1f9b0765ef075c018eba0d047391..ea9d8b74b00d24032a7f341d41e7d0eee5f9df84 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepository.kt @@ -2,7 +2,9 @@ package de.rki.coronawarnapp.eventregistration.checkins import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity import de.rki.coronawarnapp.eventregistration.storage.entity.toCheckIn +import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -13,7 +15,8 @@ import javax.inject.Singleton @Singleton class CheckInRepository @Inject constructor( - traceLocationDatabaseFactory: TraceLocationDatabase.Factory + traceLocationDatabaseFactory: TraceLocationDatabase.Factory, + private val timeStamper: TimeStamper ) { private val traceLocationDatabase: TraceLocationDatabase by lazy { @@ -24,10 +27,27 @@ class CheckInRepository @Inject constructor( traceLocationDatabase.eventCheckInDao() } + /** + * Returns all stored check-ins + * + * Attention: this could also include check-ins that are older than + * the retention period. Therefore, you should probably use [checkInsWithinRetention] + */ val allCheckIns: Flow<List<CheckIn>> = checkInDao .allEntries() .map { list -> list.map { it.toCheckIn() } } + /** + * Returns check-ins that are within the retention period. Even though we have a worker that deletes all stale + * check-ins it's still possible to have stale check-ins in the database because the worker only runs once a day. + */ + val checkInsWithinRetention: Flow<List<CheckIn>> = allCheckIns.map { checkInList -> + val now = timeStamper.nowUTC + checkInList.filter { checkIn -> + checkIn.isWithinRetention(now) + } + } + suspend fun getCheckInById(checkInId: Long): CheckIn? { Timber.d("getCheckInById(checkInId=$checkInId)") return checkInDao.entryForId(checkInId)?.toCheckIn() @@ -45,6 +65,13 @@ class CheckInRepository @Inject constructor( checkInDao.updateEntityById(checkInId, update) } + suspend fun markCheckInAsSubmitted(checkInId: Long) { + Timber.d("markCheckInAsSubmitted(checkInId=$checkInId)") + checkInDao.updateEntity( + TraceLocationCheckInEntity.SubmissionUpdate(checkInId = checkInId, isSubmitted = true) + ) + } + suspend fun deleteCheckIns(checkIns: Collection<CheckIn>) = withContext(NonCancellable) { Timber.d("deleteCheckIns(checkIns=%s)", checkIns) checkInDao.deleteByIds(checkIns.map { it.id }) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRetention.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRetention.kt new file mode 100644 index 0000000000000000000000000000000000000000..875612a2a0b00a6512964fb347a1d956b245e997 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRetention.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.eventregistration.checkins + +import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds +import org.joda.time.Instant +import java.util.concurrent.TimeUnit + +private const val CHECK_IN_RETENTION_DAYS = 15 +private val CHECK_IN_RETENTION_SECONDS = TimeUnit.DAYS.toSeconds(CHECK_IN_RETENTION_DAYS.toLong()) + +/** + * returns true if the end date of the check-in isn't older than [CHECK_IN_RETENTION_DAYS], otherwise false + */ +fun CheckIn.isWithinRetention(now: Instant): Boolean { + val retentionThreshold = (now.seconds - CHECK_IN_RETENTION_SECONDS) + return checkInEnd.seconds >= retentionThreshold +} + +/** + * Returns true if a check-in is stale and therefore can be deleted, otherwise false + */ +fun CheckIn.isOutOfRetention(now: Instant) = !isWithinRetention(now) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultAutoCheckoutLength.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultAutoCheckoutLength.kt new file mode 100644 index 0000000000000000000000000000000000000000..0f5c0d6dfa510bfbc46a452678702c0947a39304 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/DefaultAutoCheckoutLength.kt @@ -0,0 +1,74 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import androidx.annotation.VisibleForTesting +import org.joda.time.Duration +import org.joda.time.Instant +import java.util.concurrent.TimeUnit +import kotlin.math.roundToLong + +/** + * Evaluates the default auto-checkout length depending on the current time + */ +@Suppress("ReturnCount") +fun TraceLocation.getDefaultAutoCheckoutLengthInMinutes(now: Instant): Int { + + // min valid value is 00:15h + val minDefaultAutoCheckOutLengthInMinutes = 15 + + // max valid value is 23:45h + val maxDefaultAutoCheckOutLengthInMinutes = (TimeUnit.HOURS.toMinutes(23) + 45).toInt() + + // for permanent traceLocations, a defaultCheckInLength is always available + if (defaultCheckInLengthInMinutes != null) { + + if (defaultCheckInLengthInMinutes < 15) { + return minDefaultAutoCheckOutLengthInMinutes + } + + if (defaultCheckInLengthInMinutes > maxDefaultAutoCheckOutLengthInMinutes) { + return maxDefaultAutoCheckOutLengthInMinutes + } + + return roundToNearest15Minutes(defaultCheckInLengthInMinutes) + } + // for temporary traceLocations, the defaultCheckInLength could be empty + else { + + // For QR-codes generated by CWA, endDate can never be null here. However, a QR code that was created or + // modified by a third party could have an endDate that is null. + if (endDate == null) { + return minDefaultAutoCheckOutLengthInMinutes + } + + if (now.isAfter(endDate)) { + return minDefaultAutoCheckOutLengthInMinutes + } + + val minutesUntilEndDate = Duration(now, endDate).standardMinutes.toInt() + + if (minutesUntilEndDate < minDefaultAutoCheckOutLengthInMinutes) { + return minDefaultAutoCheckOutLengthInMinutes + } + + if (minutesUntilEndDate > maxDefaultAutoCheckOutLengthInMinutes) { + return maxDefaultAutoCheckOutLengthInMinutes + } + + return roundToNearest15Minutes(minutesUntilEndDate) + } +} + +/** + * Rounds to the nearest 15 minute interval. + * for more details see AutoCheckoutHelperTest + */ +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +fun roundToNearest15Minutes(minutes: Int): Int { + val roundingStepInMinutes = 15 + return Duration + .standardMinutes( + (minutes.toFloat() / roundingStepInMinutes) + .roundToLong() * roundingStepInMinutes + ) + .standardMinutes.toInt() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt index b45d75d1b09df3dd21f4967411f32e9e4f8c7ef4..56ed45b2abe1f75be100a40cb3f8160b840571cf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/CheckInDao.kt @@ -37,6 +37,9 @@ interface CheckInDao { update(updated) } + @Update(entity = TraceLocationCheckInEntity::class) + suspend fun updateEntity(update: TraceLocationCheckInEntity.SubmissionUpdate) + @Query("DELETE FROM checkin") suspend fun deleteAll() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt index 2616685c8a0a15690dc2aa1d472b806d31b1019e..8ddcadd2a8b811cfae2b987771a001582c2c8bb0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationCheckInEntity.kt @@ -23,8 +23,16 @@ data class TraceLocationCheckInEntity( @ColumnInfo(name = "checkInStart") val checkInStart: Instant, @ColumnInfo(name = "checkInEnd") val checkInEnd: Instant, @ColumnInfo(name = "completed") val completed: Boolean, - @ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean -) + @ColumnInfo(name = "createJournalEntry") val createJournalEntry: Boolean, + @ColumnInfo(name = "submitted") val isSubmitted: Boolean, +) { + + @Entity + data class SubmissionUpdate( + @PrimaryKey @ColumnInfo(name = "id") val checkInId: Long, + @ColumnInfo(name = "submitted") val isSubmitted: Boolean, + ) +} fun TraceLocationCheckInEntity.toCheckIn() = CheckIn( id = id, @@ -41,5 +49,6 @@ fun TraceLocationCheckInEntity.toCheckIn() = CheckIn( checkInStart = checkInStart, checkInEnd = checkInEnd, completed = completed, - createJournalEntry = createJournalEntry + createJournalEntry = createJournalEntry, + isSubmitted = isSubmitted ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt index 46a93d873b0be3f72c3247e14814f260ba93723d..b0c5f879dcdd3c5af499bdcce2593283cddef3db 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt @@ -6,6 +6,8 @@ import de.rki.coronawarnapp.eventregistration.checkins.qrcode.toTraceLocations import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao import de.rki.coronawarnapp.eventregistration.storage.entity.toTraceLocationEntity +import de.rki.coronawarnapp.eventregistration.storage.retention.isWithinRetention +import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.coroutine.AppScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -18,7 +20,8 @@ import javax.inject.Singleton @Singleton class DefaultTraceLocationRepository @Inject constructor( traceLocationDatabaseFactory: TraceLocationDatabase.Factory, - @AppScope private val appScope: CoroutineScope + @AppScope private val appScope: CoroutineScope, + private val timeStamper: TimeStamper ) : TraceLocationRepository { private val traceLocationDatabase: TraceLocationDatabase by lazy { @@ -29,16 +32,39 @@ class DefaultTraceLocationRepository @Inject constructor( traceLocationDatabase.traceLocationDao() } + /** + * Reruns [TraceLocation] for [id] + * @throws [IllegalArgumentException] if location not found + */ override suspend fun traceLocationForId(id: Long): TraceLocation { - val checkIn = traceLocationDao.entryForId(id) + val traceLocationEntity = traceLocationDao.entryForId(id) ?: throw IllegalArgumentException("No traceLocation found for ID=$id") - return checkIn.toTraceLocation() + return traceLocationEntity.toTraceLocation() } + /** + * Returns all stored trace locations + * + * Attention: this could also include trace locations that are older than + * the retention period. Therefore, you should probably use [traceLocationsWithinRetention] + */ override val allTraceLocations: Flow<List<TraceLocation>> get() = traceLocationDao.allEntries().map { it.toTraceLocations() } + /** + * Returns trace locations that are within the retention period. Even though we have a worker that deletes all stale + * trace locations it's still possible to have stale trace-locations in the database because the worker only runs + * once a day. + */ + override val traceLocationsWithinRetention: Flow<List<TraceLocation>> + get() = allTraceLocations.map { traceLocationList -> + val now = timeStamper.nowUTC + traceLocationList.filter { traceLocation -> + traceLocation.isWithinRetention(now) + } + } + override suspend fun addTraceLocation(traceLocation: TraceLocation): TraceLocation { Timber.d("Add trace location: %s", traceLocation) val traceLocationEntity = traceLocation.toTraceLocationEntity() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt index 8baafc528adfbb6197519b26f96d5958c45a1655..df566a3cc3b8c982984871cfb3c2451fad3dc63d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt @@ -5,8 +5,21 @@ import kotlinx.coroutines.flow.Flow interface TraceLocationRepository { + /** + * Returns all stored trace locations + * + * Attention: this could also include trace locations that are older than + * the retention period. Therefore, you should probably use [traceLocationsWithinRetention] + */ val allTraceLocations: Flow<List<TraceLocation>> + /** + * Returns trace locations that are within the retention period. Even though we have a worker that deletes all stale + * trace locations it's still possible to have stale trace-locations in the database because the worker only runs + * once a day. + */ + val traceLocationsWithinRetention: Flow<List<TraceLocation>> + suspend fun traceLocationForId(id: Long): TraceLocation suspend fun addTraceLocation(traceLocation: TraceLocation): TraceLocation diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/CheckInCleaner.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/CheckInCleaner.kt index b40efe4887783d16930c261fb140c8b68b226bb3..af5e3f80a39252d0c07ccce930c0d8f968a3e092 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/CheckInCleaner.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/CheckInCleaner.kt @@ -2,11 +2,10 @@ package de.rki.coronawarnapp.eventregistration.storage.retention import dagger.Reusable import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository -import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds +import de.rki.coronawarnapp.eventregistration.checkins.isOutOfRetention import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.flow.first import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject @Reusable @@ -17,18 +16,15 @@ class CheckInCleaner @Inject constructor( suspend fun cleanUp() { Timber.d("Starting to clean up stale check-ins.") - val retentionThreshold = (timeStamper.nowUTC.seconds - RETENTION_SECONDS) + + val now = timeStamper.nowUTC val checkInsToDelete = checkInRepository.allCheckIns.first() - .filter { - it.checkInEnd.seconds < retentionThreshold - } + .filter { checkIn -> checkIn.isOutOfRetention(now) } + Timber.d("Cleaning up ${checkInsToDelete.size} stale check-ins.") + checkInRepository.deleteCheckIns(checkInsToDelete) - Timber.d("Clean up of stale check-ins completed.") - } - companion object { - private const val RETENTION_DAYS = 15 - private val RETENTION_SECONDS = TimeUnit.DAYS.toSeconds(RETENTION_DAYS.toLong()) + Timber.d("Clean up of stale check-ins completed.") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationCleaner.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationCleaner.kt index 6f73ddbf786282539650e6fdbf8cee3d7e74305a..69fac1678fb37a4bce0e309561003d1b68d44c36 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationCleaner.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationCleaner.kt @@ -2,11 +2,9 @@ package de.rki.coronawarnapp.eventregistration.storage.retention import dagger.Reusable import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository -import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.flow.first import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject @Reusable @@ -17,19 +15,16 @@ class TraceLocationCleaner @Inject constructor( suspend fun cleanUp() { Timber.d("Starting to clean up stale trace locations.") - val retentionThreshold = (timeStamper.nowUTC.seconds - RETENTION_SECONDS) + + val now = timeStamper.nowUTC traceLocationRepository.allTraceLocations.first() - .filter { - it.endDate != null && it.endDate.seconds < retentionThreshold + .filter { traceLocation -> + traceLocation.isOutOfRetention(now) }.forEach { Timber.d("Cleaning up stale trace location: %s", it) traceLocationRepository.deleteTraceLocation(it) } - Timber.d("Clean up of stale trace locations completed.") - } - companion object { - private const val RETENTION_DAYS = 15 - private val RETENTION_SECONDS = TimeUnit.DAYS.toSeconds(RETENTION_DAYS.toLong()) + Timber.d("Clean up of stale trace locations completed.") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationRetention.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationRetention.kt new file mode 100644 index 0000000000000000000000000000000000000000..b1c854e7f974551e34505eb05c1757e7c1cabdf8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationRetention.kt @@ -0,0 +1,27 @@ +package de.rki.coronawarnapp.eventregistration.storage.retention + +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds +import org.joda.time.Instant +import java.util.concurrent.TimeUnit + +private const val TRACE_LOCATION_RETENTION_DAYS = 15 +private val TRACE_LOCATION_RETENTION_SECONDS = TimeUnit.DAYS.toSeconds(TRACE_LOCATION_RETENTION_DAYS.toLong()) + +/** + * returns true if a trace location either has no end date or has an end date that isn't older than + * [TRACE_LOCATION_RETENTION_DAYS], otherwise false + */ +fun TraceLocation.isWithinRetention(now: Instant): Boolean { + val retentionThreshold = (now.seconds - TRACE_LOCATION_RETENTION_SECONDS) + return if (endDate == null) { + true + } else { + endDate.seconds >= retentionThreshold + } +} + +/** + * Returns true if a trace location is stale and therefore can be deleted, otherwise false + */ +fun TraceLocation.isOutOfRetention(now: Instant) = !isWithinRetention(now) 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 2c17160e7eabb7fbd73ed80a5502d416d4caa742..a6d942933d7aa3a595e3e5efa3a69d00337a53db 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 @@ -23,7 +23,12 @@ class TestResultAvailableNotificationService @Inject constructor( ) { suspend fun showTestResultAvailableNotification(testResult: TestResult) { - if (foregroundState.isInForeground.first()) return + Timber.d("showTestResultAvailableNotification(testResult=%s)", testResult) + + if (foregroundState.isInForeground.first()) { + Timber.d("App in foreground, skipping notification.") + return + } if (!cwaSettings.isNotificationsTestEnabled.value) { Timber.i("Don't show test result available notification because user doesn't want to be informed") @@ -42,6 +47,7 @@ class TestResultAvailableNotificationService @Inject constructor( setContentIntent(pendingIntent) }.build() + Timber.i("Showing TestResultAvailable notification!") notificationHelper.sendNotification( notificationId = NotificationConstants.TEST_RESULT_AVAILABLE_NOTIFICATION_ID, notification = notification, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt index 917e8c0eca407e43d98fd7cc1c3c96f8b4123e3c..4852be4a722be3c850ad18fbe2bc0784f1e9e668 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandler.kt @@ -1,18 +1,18 @@ package de.rki.coronawarnapp.presencetracing.checkins.checkout -import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import dagger.Reusable +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.util.TimeStamper import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton -@Singleton +@Reusable class CheckOutHandler @Inject constructor( private val repository: CheckInRepository, private val timeStamper: TimeStamper, - private val diaryRepository: ContactDiaryRepository, + private val contactJournalCheckInEntryCreator: ContactJournalCheckInEntryCreator ) { /** * Throw **[IllegalArgumentException]** if the check-in does not exist. @@ -21,18 +21,16 @@ class CheckOutHandler @Inject constructor( suspend fun checkOut(checkInId: Long, checkOutAt: Instant = timeStamper.nowUTC) { Timber.d("checkOut(checkInId=$checkInId, checkOutAt=%s)", checkOutAt) - var createJournalEntry = false + var checkIn: CheckIn? = null repository.updateCheckIn(checkInId) { - createJournalEntry = it.createJournalEntry it.copy( checkInEnd = checkOutAt, completed = true - ) + ).also { c -> checkIn = c } } - if (createJournalEntry) { - Timber.d("Creating journal entry for $checkInId") - // TODO Create journal entry + if (checkIn?.createJournalEntry == true) { + contactJournalCheckInEntryCreator.createEntry(checkIn!!) } // Remove auto-checkout timer? diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt new file mode 100644 index 0000000000000000000000000000000000000000..e553c983471c6c5c523868b2bf95a80a760bf070 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreator.kt @@ -0,0 +1,104 @@ +package de.rki.coronawarnapp.presencetracing.checkins.checkout + +import androidx.annotation.VisibleForTesting +import dagger.Reusable +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocationVisit +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import de.rki.coronawarnapp.eventregistration.checkins.split.splitByMidnightUTC +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc +import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull +import org.joda.time.Duration +import org.joda.time.Seconds +import org.joda.time.format.DateTimeFormat +import timber.log.Timber +import javax.inject.Inject +import kotlin.math.roundToLong + +@Reusable +class ContactJournalCheckInEntryCreator @Inject constructor( + private val diaryRepository: ContactDiaryRepository +) { + + suspend fun createEntry(checkIn: CheckIn) { + Timber.d("Creating journal entry for %s", checkIn) + + // 1. Create location if missing + val location: ContactDiaryLocation = diaryRepository.locations.first() + .find { it.traceLocationID == checkIn.traceLocationId } ?: checkIn.toLocation() + + // 2. Split CheckIn by Midnight UTC + val splitCheckIns = checkIn.splitByMidnightUTC() + Timber.d("Split %s into %s ", this, splitCheckIns) + + // 3. Create LocationVisit if missing + splitCheckIns + .createMissingLocationVisits(location) + .forEach { diaryRepository.addLocationVisit(it) } + } + + private suspend fun CheckIn.toLocation(): ContactDiaryLocation { + val location = DefaultContactDiaryLocation( + locationName = locationName(), + traceLocationID = traceLocationId + ) + Timber.d("Created new location %s and adding it to contact journal db", location) + return diaryRepository.addLocation(location) // Get location from db cause we need the id autogenerated by db + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun CheckIn.locationName(): String { + val nameParts = mutableListOf(description, address) + + if (traceLocationStart != null && traceLocationEnd != null) { + if (traceLocationStart.millis > 0 && traceLocationEnd.millis > 0) { + val formattedStartDate = traceLocationStart.toUserTimeZone().toString(DateTimeFormat.shortDateTime()) + val formattedEndDate = traceLocationEnd.toUserTimeZone().toString(DateTimeFormat.shortDateTime()) + nameParts.add("$formattedStartDate - $formattedEndDate") + } + } + + return nameParts.joinToString(separator = ", ") + } + + private fun CheckIn.toLocationVisit(location: ContactDiaryLocation): ContactDiaryLocationVisit { + // Use Seconds for more precision + val durationInMinutes = Seconds.secondsBetween(checkInStart, checkInEnd).seconds / 60.0 + val duration = (durationInMinutes / 15).roundToLong() * 15 + return DefaultContactDiaryLocationVisit( + date = checkInStart.toLocalDateUtc(), + contactDiaryLocation = location, + duration = Duration.standardMinutes(duration), + checkInID = id + ) + } + + private suspend fun List<CheckIn>.createMissingLocationVisits(location: ContactDiaryLocation): + List<ContactDiaryLocationVisit> { + Timber.d( + "createMissingLocationVisits(location=%s) for %s", + location, + this.joinToString(prefix = System.lineSeparator(), separator = System.lineSeparator()) + ) + val existingLocationVisits = diaryRepository.locationVisits.firstOrNull() ?: emptyList() + // Existing location visits shall not be updated, so just drop them + return filter { + existingLocationVisits.none { visit -> + visit.date == it.checkInStart.toLocalDateUtc() && + visit.contactDiaryLocation.locationId == location.locationId + } + } + .map { it.toLocationVisit(location) } + .also { + Timber.d( + "Created locations visits: %s", + it.joinToString(prefix = System.lineSeparator(), separator = System.lineSeparator()) + ) + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/auto/AutoCheckOut.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/auto/AutoCheckOut.kt index 2a27ff6dbc40c3377b74805eb21b24a6ceee52e7..b0b8d2190fda0913ad902bdba9c00576c2d55c4f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/auto/AutoCheckOut.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/auto/AutoCheckOut.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.sync.Mutex @@ -40,15 +39,10 @@ class AutoCheckOut @Inject constructor( fun setupMonitor() { repository.allCheckIns .onStart { Timber.tag(TAG).v("Monitoring check-ins.") } - .map { checkins -> - Timber.tag(TAG).v("CheckIns changed") - val completed = checkins.filter { it.completed }.map { it.id } - val notCompleted = checkins.filter { !it.completed }.map { it.id } - completed to notCompleted - } .distinctUntilChanged() .onEach { - Timber.tag(TAG).i("Check-in was added or removed, refreshing alarm.") + Timber.tag(TAG).i("Check-ins changed, checking for overdue items, refreshing alarm.") + processOverDueCheckouts() refreshAlarm() } .launchIn(appScope) @@ -93,7 +87,7 @@ class AutoCheckOut @Inject constructor( val nowUTC = timeStamper.nowUTC val snapshot = repository.allCheckIns.firstOrNull() ?: emptyList() snapshot - .filter { !it.completed && nowUTC.isAfter(it.checkInEnd) } + .filter { !it.completed && (nowUTC.isAfter(it.checkInEnd) || nowUTC.isEqual(it.checkInEnd)) } .sortedBy { it.checkInEnd } }.also { Timber.tag(TAG).d("${it.size} checkins are overdue for auto checkout: %s", it) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt index 62b25b76061904b9d6a11001752fdce79f8f3dda..541496f3bac86227cc1fe7204d8289961871def4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/PtRiskLevelResult.kt @@ -1,15 +1,20 @@ package de.rki.coronawarnapp.presencetracing.risk +import de.rki.coronawarnapp.presencetracing.risk.calculation.CheckInWarningOverlap import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingDayRisk import de.rki.coronawarnapp.risk.RiskState import org.joda.time.Instant import org.joda.time.LocalDate +/** + * @param presenceTracingDayRisk Only available for the last calculation, if successful, otherwise null + * @param checkInWarningOverlaps Only available for the last calculation, if successful, otherwise null + */ data class PtRiskLevelResult( val calculatedAt: Instant, val riskState: RiskState, - // only available for the last calculation if successful, otherwise null - val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null + val presenceTracingDayRisk: List<PresenceTracingDayRisk>? = null, + private val checkInWarningOverlaps: List<CheckInWarningOverlap>? = null, ) { val wasSuccessfullyCalculated: Boolean @@ -39,4 +44,7 @@ data class PtRiskLevelResult( RiskState.LOW_RISK -> numberOfDaysWithLowRisk else -> 0 } + + val checkInOverlapCount: Int + get() = checkInWarningOverlaps?.size ?: 0 } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt index 8c318df97711ea7ca4108ee90bcce03a155b7540..530ebc138071c5f2d9dead90fb658c773a659339 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcher.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext import org.joda.time.Instant import timber.log.Timber import java.lang.reflect.Modifier.PRIVATE +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.coroutines.CoroutineContext @@ -25,6 +26,8 @@ class CheckInWarningMatcher @Inject constructor( checkIns: List<CheckIn>, warningPackages: List<TraceWarningPackage> ): Result { + Timber.tag(TAG).d("Processing ${checkIns.size} checkins and ${warningPackages.size} warning pkgs.") + val splitCheckIns = checkIns.flatMap { it.splitByMidnightUTC() } val matchLists: List<List<MatchesPerPackage>?> = runMatchingLaunchers( @@ -34,13 +37,12 @@ class CheckInWarningMatcher @Inject constructor( ) val successful = if (matchLists.contains(null)) { - Timber.e("Calculation partially failed.") + Timber.tag(TAG).e("Calculation partially failed: %s", matchLists) false } else { - Timber.d("Matching was successful.") + Timber.tag(TAG).d("Matching was successful.") true } - return Result( successful = successful, processedPackages = matchLists.filterNotNull().flatten() @@ -67,11 +69,11 @@ class CheckInWarningMatcher @Inject constructor( try { packageChunk.map { val overlaps = findMatches(list, it) - Timber.d("%d overlaps for %s", overlaps.size, it.packageId) + Timber.tag(TAG).d("%d overlaps for %s", overlaps.size, it.packageId) MatchesPerPackage(warningPackage = it, overlaps = overlaps) } } catch (e: Throwable) { - Timber.e(e, "Failed to process packages $packageChunk") + Timber.tag(TAG).e(e, "Failed to process packages $packageChunk") null } } @@ -105,9 +107,9 @@ internal suspend fun findMatches( .mapNotNull { checkIn -> checkIn.calculateOverlap(warning, warningPackage.packageId).also { overlap -> if (overlap == null) { - Timber.v("No match found for $checkIn and $warning") + Timber.tag(TAG).v("No match found for $checkIn and $warning") } else { - Timber.w("Overlap was found $overlap") + Timber.tag(TAG).w("Overlap was found $overlap") } } } @@ -129,7 +131,12 @@ internal fun CheckIn.calculateOverlap( val overlapMillis = overlapEndMillis - overlapStartMillis if (overlapMillis <= 0) { - Timber.i("No overlap (%dms) with match %s (%s)", overlapMillis, description, traceLocationIdHash) + Timber.tag(TAG).i("Match without overlap (%dms) (%s, %s)", overlapMillis, description, traceLocationIdHash) + return null + } + + if (isSubmitted) { + Timber.tag(TAG).d("Overlap with our own CheckIn (%s and %s)", this, warning) return null } @@ -141,3 +148,8 @@ internal fun CheckIn.calculateOverlap( endTime = Instant.ofEpochMilli(overlapEndMillis) ) } + +// converts number of 10min intervals into milliseconds +internal fun Int.tenMinIntervalToMillis() = this * TimeUnit.MINUTES.toMillis(10L) + +private const val TAG = "CheckInWarningMatcher" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt deleted file mode 100644 index 4e529779dc78875644dda7905f5d250866b606f0..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingConversions.kt +++ /dev/null @@ -1,16 +0,0 @@ -package de.rki.coronawarnapp.presencetracing.risk.calculation - -import de.rki.coronawarnapp.risk.RiskState -import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel -import java.util.concurrent.TimeUnit - -// converts number of 10min intervals into milliseconds -internal fun Int.tenMinIntervalToMillis() = this * TimeUnit.MINUTES.toMillis(10L) - -fun RiskLevel.mapToRiskState(): RiskState { - return when (this) { - RiskLevel.UNSPECIFIED, RiskLevel.UNRECOGNIZED -> RiskState.CALCULATION_FAILED - RiskLevel.LOW -> RiskState.LOW_RISK - RiskLevel.HIGH -> RiskState.INCREASED_RISK - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt index 1cebd0aec5de5430225dbe41568f31b4e326b5a4..06b72ed4620c465999247db75079fae56aef49ee 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/calculation/PresenceTracingRiskMapper.kt @@ -4,6 +4,7 @@ import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.PresenceTracingRiskCalculationParamContainer import de.rki.coronawarnapp.risk.DefaultRiskLevels.Companion.inRange import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.risk.mapToRiskState import kotlinx.coroutines.flow.first import timber.log.Timber import javax.inject.Inject diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt index e0d9556af8f02e0ee7577eeebdcbee4a9c0f4a5b..8000745d6440e0e416ca6b65233d6e31106c01fe 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTask.kt @@ -12,7 +12,6 @@ import de.rki.coronawarnapp.presencetracing.warning.storage.TraceWarningReposito import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.task.TaskFactory -import de.rki.coronawarnapp.task.common.DefaultProgress import de.rki.coronawarnapp.util.TimeStamper import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.flow.Flow @@ -31,10 +30,10 @@ class PresenceTracingWarningTask @Inject constructor( private val presenceTracingRiskRepository: PresenceTracingRiskRepository, private val traceWarningRepository: TraceWarningRepository, private val checkInsRepository: CheckInRepository, -) : Task<DefaultProgress, PresenceTracingWarningTask.Result> { +) : Task<PresenceTracingWarningTaskProgress, PresenceTracingWarningTask.Result> { - private val internalProgress = ConflatedBroadcastChannel<DefaultProgress>() - override val progress: Flow<DefaultProgress> = internalProgress.asFlow() + private val internalProgress = ConflatedBroadcastChannel<PresenceTracingWarningTaskProgress>() + override val progress: Flow<PresenceTracingWarningTaskProgress> = internalProgress.asFlow() private var isCanceled = false @@ -59,15 +58,24 @@ class PresenceTracingWarningTask @Inject constructor( private suspend fun doWork(): Result { val nowUTC = timeStamper.nowUTC + checkCancel() - Timber.tag(TAG).d("Running package sync.") - syncTool.syncPackages() + Timber.tag(TAG).d("Syncing packages.") + internalProgress.send(PresenceTracingWarningTaskProgress.Downloading()) - checkCancel() + val syncResult = syncTool.syncPackages() + + if (syncResult.successful) { + Timber.tag(TAG).d("TraceWarningPackage sync successful: %s", syncResult) + } else { + Timber.tag(TAG).w("WarningPackage sync failed: %s", syncResult) + presenceTracingRiskRepository.reportCalculation(successful = false) + return Result(calculatedAt = nowUTC) + } presenceTracingRiskRepository.deleteStaleData() - val checkIns = checkInsRepository.allCheckIns.firstOrNull() ?: emptyList() + val checkIns = checkInsRepository.checkInsWithinRetention.firstOrNull() ?: emptyList() Timber.tag(TAG).d("There are %d check-ins to match against.", checkIns.size) if (checkIns.isEmpty()) { @@ -91,6 +99,8 @@ class PresenceTracingWarningTask @Inject constructor( } Timber.tag(TAG).d("Running check-in matcher.") + internalProgress.send(PresenceTracingWarningTaskProgress.Calculating()) + val matcherResult = checkInWarningMatcher.process( checkIns = checkIns, warningPackages = unprocessedPackages, @@ -141,13 +151,13 @@ class PresenceTracingWarningTask @Inject constructor( class Factory @Inject constructor( private val taskByDagger: Provider<PresenceTracingWarningTask>, private val appConfigProvider: AppConfigProvider - ) : TaskFactory<DefaultProgress, Task.Result> { + ) : TaskFactory<PresenceTracingWarningTaskProgress, Task.Result> { override suspend fun createConfig(): TaskFactory.Config = Config( executionTimeout = appConfigProvider.getAppConfig().overallDownloadTimeout ) - override val taskProvider: () -> Task<DefaultProgress, Task.Result> = { + override val taskProvider: () -> Task<PresenceTracingWarningTaskProgress, Task.Result> = { taskByDagger.get() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt new file mode 100644 index 0000000000000000000000000000000000000000..a7c9869954c5a4435f65ffcb772b09648bd64a85 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskProgress.kt @@ -0,0 +1,16 @@ +package de.rki.coronawarnapp.presencetracing.risk.execution + +import de.rki.coronawarnapp.task.Task +import de.rki.coronawarnapp.util.ui.CachedString +import de.rki.coronawarnapp.util.ui.LazyString + +sealed class PresenceTracingWarningTaskProgress : Task.Progress { + + data class Downloading( + override val primaryMessage: LazyString = CachedString { "" } + ) : PresenceTracingWarningTaskProgress() + + data class Calculating( + override val primaryMessage: LazyString = CachedString { "" } + ) : PresenceTracingWarningTaskProgress() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt index daf5c60bcd39437e7d54a410654397d5855bbbc9..9dbccbc3e1b0c6c2a191cccde25f7eff674f2d82 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/risk/storage/PresenceTracingRiskRepository.kt @@ -128,9 +128,15 @@ class PresenceTracingRiskRepository @Inject constructor( if (!lastSuccessfulFound && entity.riskState != RiskState.CALCULATION_FAILED) { lastSuccessfulFound = true // add risk per day to the last successful result - entity.toCheckInWarningOverlap(presenceTracingDayRisk.first()) + entity.toRiskLevelResult( + presenceTracingDayRisks = presenceTracingDayRisk.first(), + checkInWarningOverlaps = checkInWarningOverlaps.first(), + ) } else { - entity.toCheckInWarningOverlap(null) + entity.toRiskLevelResult( + presenceTracingDayRisks = null, + checkInWarningOverlaps = null, + ) } } } @@ -144,16 +150,22 @@ class PresenceTracingRiskRepository @Inject constructor( if (!lastSuccessfulFound && entity.riskState != RiskState.CALCULATION_FAILED) { lastSuccessfulFound = true // add risk per day to the last successful result - entity.toCheckInWarningOverlap(presenceTracingDayRisk.first()) + entity.toRiskLevelResult( + presenceTracingDayRisks = presenceTracingDayRisk.first(), + checkInWarningOverlaps = checkInWarningOverlaps.first(), + ) } else { - entity.toCheckInWarningOverlap(null) + entity.toRiskLevelResult( + presenceTracingDayRisks = null, + checkInWarningOverlaps = null, + ) } } } private fun addResult(result: PtRiskLevelResult) { Timber.i("Saving risk calculation from ${result.calculatedAt} with result ${result.riskState}.") - riskLevelResultDao.insert(result.toTraceTimeIntervalMatchEntity()) + riskLevelResultDao.insert(result.toRiskLevelEntity()) } suspend fun clearAllTables() { @@ -242,15 +254,17 @@ data class PresenceTracingRiskLevelResultEntity( @ColumnInfo(name = "riskStateCode") val riskState: RiskState ) -private fun PresenceTracingRiskLevelResultEntity.toCheckInWarningOverlap( - presenceTracingDayRisk: List<PresenceTracingDayRisk>? +private fun PresenceTracingRiskLevelResultEntity.toRiskLevelResult( + presenceTracingDayRisks: List<PresenceTracingDayRisk>?, + checkInWarningOverlaps: List<CheckInWarningOverlap>? ) = PtRiskLevelResult( calculatedAt = Instant.ofEpochMilli((calculatedAtMillis)), riskState = riskState, - presenceTracingDayRisk = presenceTracingDayRisk + presenceTracingDayRisk = presenceTracingDayRisks, + checkInWarningOverlaps = checkInWarningOverlaps, ) -private fun PtRiskLevelResult.toTraceTimeIntervalMatchEntity() = PresenceTracingRiskLevelResultEntity( +private fun PtRiskLevelResult.toRiskLevelEntity() = PresenceTracingRiskLevelResultEntity( calculatedAtMillis = calculatedAt.millis, riskState = riskState ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageDownloader.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageDownloader.kt index 581df13ed6c0afec399e579fc8a42a67da801d47..466d98764ac22f19d1b371292e3c64a9005d0c82 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageDownloader.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageDownloader.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import org.joda.time.Duration import timber.log.Timber +import java.io.File import javax.inject.Inject @Reusable @@ -32,9 +33,7 @@ class TraceWarningPackageDownloader @Inject constructor( val successful: Boolean, val newPackages: Collection<TraceWarningPackageMetadata> ) { - override fun toString(): String { - return "DownloadResult(successful=$successful, newPackages.size=${newPackages.size})" - } + override fun toString(): String = "DownloadResult(successful=$successful, newPackages.size=${newPackages.size})" } suspend fun launchDownloads( @@ -42,6 +41,8 @@ class TraceWarningPackageDownloader @Inject constructor( hourIntervals: List<HourInterval>, downloadTimeout: Duration ): DownloadResult { + Timber.tag(TAG).d("Launching %d downloads ($location): %s", hourIntervals.size, hourIntervals) + val launcher: CoroutineScope.(HourInterval) -> Deferred<TraceWarningPackageMetadata?> = { hourInterval -> async { val metadata = repository.createMetadata(location, hourInterval) @@ -51,8 +52,6 @@ class TraceWarningPackageDownloader @Inject constructor( } } - Timber.tag(TAG).d("Launching %d downloads.", hourIntervals.size) - val launchedDownloads: Collection<Deferred<TraceWarningPackageMetadata?>> = hourIntervals.map { warningPackageId -> withContext(context = dispatcherProvider.IO) { @@ -81,12 +80,17 @@ class TraceWarningPackageDownloader @Inject constructor( hourInterval = metaData.hourInterval ) + val saveTo = repository.getPathForMetaData(metaData) + if (!downloadInfo.isEmptyPkg) { val fileMap = downloadInfo.readBody().unzip().readIntoMap() val rawProtoBuf = getValidatedBinary(metaData, fileMap) - writeProtoBufToFile(metaData, rawProtoBuf) + writeProtoBufToFile(metaData, rawProtoBuf, saveTo) } else { Timber.tag(TAG).v("Empty package for %s", metaData) + if (saveTo.exists() && saveTo.delete()) { + Timber.tag(TAG).w("Download file exists for a package that should be empty, deleting: %s", saveTo) + } } Timber.tag(TAG).v("Download finished: %s -> %s", metaData, downloadInfo) @@ -102,13 +106,13 @@ class TraceWarningPackageDownloader @Inject constructor( private fun writeProtoBufToFile( metaData: TraceWarningPackageMetadata, rawProtoBuf: ByteArray, + saveTo: File, ) { if (rawProtoBuf.isEmpty()) { Timber.tag(TAG).d("rawProtoBuf was empty for %s", metaData.packageId) return } - val saveTo = repository.getPathForMetaData(metaData) if (saveTo.exists()) { Timber.tag(TAG).w("File existed, overwriting: %s", saveTo) if (saveTo.delete()) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageSyncTool.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageSyncTool.kt index ea2d87f227017e3399c5c4b12468448f47914ec4..4e2f9e118ae1fb0842a1783269d69ed6d0175381 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageSyncTool.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/TraceWarningPackageSyncTool.kt @@ -41,7 +41,7 @@ class TraceWarningPackageSyncTool @Inject constructor( internal suspend fun syncPackagesForLocation(location: LocationCode): SyncResult { Timber.tag(TAG).d("syncTraceWarningPackages(location=%s)", location) - val oldestCheckIn = checkInRepository.allCheckIns.first().minByOrNull { it.checkInStart }.also { + val oldestCheckIn = checkInRepository.checkInsWithinRetention.first().minByOrNull { it.checkInStart }.also { Timber.tag(TAG).d("Our oldest check-in is %s", it) } @@ -63,9 +63,13 @@ class TraceWarningPackageSyncTool @Inject constructor( return SyncResult(successful = false) } - val firstRelevantInterval: HourInterval = max( - oldestCheckIn.checkInStart.deriveHourInterval(), - intervalDiscovery.oldest + val oldestCheckInInterval = oldestCheckIn.checkInStart.deriveHourInterval() + val firstRelevantInterval: HourInterval = max(oldestCheckInInterval, intervalDiscovery.oldest) + Timber.tag(TAG).d( + "Oldest-server=%s & Oldest-local=%s => first-relevant=%s", + intervalDiscovery.oldest, + oldestCheckInInterval, + firstRelevantInterval ) cleanUpIrrelevantPackages(location, firstRelevantInterval) @@ -77,7 +81,7 @@ class TraceWarningPackageSyncTool @Inject constructor( val missingHourIntervals = determineIntervalsToDownload( location = location, - firstRelevant = oldestCheckIn.checkInStart.deriveHourInterval(), + firstRelevant = firstRelevantInterval, lastRelevant = intervalDiscovery.latest ) @@ -148,12 +152,17 @@ class TraceWarningPackageSyncTool @Inject constructor( firstRelevant: HourInterval, lastRelevant: HourInterval ): List<HourInterval> { - val metadatas = repository.getMetaDataForLocation(location) - - return (firstRelevant..lastRelevant).filter { interval -> - // If there is no metadata, it's unknown, so we want to download it - metadatas.none { it.hourInterval == interval } - } + val metadatas = repository.getMetaDataForLocation(location).filter { it.isDownloaded } + Timber.tag(TAG).d("We already have downloads for %s", metadatas.joinToString(", ") { it.packageId }) + + return (firstRelevant..lastRelevant) + .filter { interval -> + // If there is no metadata, it's unknown, so we want to download it + metadatas.none { it.hourInterval == interval } + } + .also { + Timber.tag(TAG).d("Missing intervals for %s are %s", location, it) + } } private suspend fun requireStorageSpaceFor(size: Int): DeviceStorage.CheckResult { @@ -167,7 +176,10 @@ class TraceWarningPackageSyncTool @Inject constructor( data class SyncResult( val successful: Boolean, val newPackages: Collection<TraceWarningPackageMetadata> = emptyList() - ) + ) { + override fun toString(): String = + "SyncResult(successful=$successful, newPackages=${newPackages.joinToString(",") { it.packageId }})" + } companion object { private const val TAG = "TraceWarningSyncTool" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/server/TraceWarningPackageDownload.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/server/TraceWarningPackageDownload.kt index 02e5d44480f76ebbcd58e5a1621bbd2783b1ce25..d729af13c4cd12f5c35218a423e6d2a3ff97e677 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/server/TraceWarningPackageDownload.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/presencetracing/warning/download/server/TraceWarningPackageDownload.kt @@ -10,7 +10,7 @@ data class TraceWarningPackageDownload(val response: Response<ResponseBody>) { val etag by lazy { headers.values("ETag").singleOrNull() } - val isEmptyPkg by lazy { headers.values("cwa-empty-pkg").singleOrNull() == "1" } + val isEmptyPkg by lazy { headers.values("Content-Length").singleOrNull() == "0" } fun readBody(): InputStream = requireNotNull(response.body()) { "Response body was null" }.byteStream() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt index 72a049c16999f42880d6a0d159cf2501ecda5088..6005f4f3c77ec5fff21ed95b590dc8bab43deeb8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/receiver/ExposureStateUpdateReceiver.kt @@ -51,6 +51,7 @@ class ExposureStateUpdateReceiver : BroadcastReceiver() { scope.launch(context = scope.coroutineContext) { try { + @Suppress("DEPRECATION") intent.getStringExtra(EXTRA_TOKEN)?.let { Timber.tag(TAG).w("Received unknown token from ENF: %s", it) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt index b4e6305fb069fdd90d9136baa9a2549a2250023a..1e2a96561f7506bb77d98c6e253feececd26b436 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/CombinedEwPtRisk.kt @@ -13,8 +13,8 @@ data class CombinedEwPtDayRisk( ) data class CombinedEwPtRiskLevelResult( - val ptRiskLevelResult: PtRiskLevelResult, - val ewRiskLevelResult: EwRiskLevelResult + private val ptRiskLevelResult: PtRiskLevelResult, + private val ewRiskLevelResult: EwRiskLevelResult ) { val riskState: RiskState by lazy { @@ -56,6 +56,15 @@ data class CombinedEwPtRiskLevelResult( else -> null } } + + /** + * The combination of matched exposure windows and overlaps. + * If we have matches > 0, but are still in a low risk state, + * the UI displays additional information in the risk details screen. + */ + val matchedRiskCount: Int by lazy { + ewRiskLevelResult.matchedKeyCount + ptRiskLevelResult.checkInOverlapCount + } } data class LastCombinedRiskResults( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt index e71d6f7a47b0ebce7445f3615ba4d8777a60af21..facbcb948aa9fc58660aeca8428744b9114b55ff 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskState.kt @@ -1,7 +1,17 @@ package de.rki.coronawarnapp.risk +import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping.RiskLevel + enum class RiskState { LOW_RISK, INCREASED_RISK, CALCULATION_FAILED } + +fun RiskLevel.mapToRiskState(): RiskState { + return when (this) { + RiskLevel.UNSPECIFIED, RiskLevel.UNRECOGNIZED -> RiskState.CALCULATION_FAILED + RiskLevel.LOW -> RiskState.LOW_RISK + RiskLevel.HIGH -> RiskState.INCREASED_RISK + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt index d5d0d71f03319705140b0a456969d7e9c2289b4f..af277a073144c8f1a9aa075768c14e2c18a4013c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorage.kt @@ -5,7 +5,6 @@ import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult import de.rki.coronawarnapp.presencetracing.risk.TraceLocationCheckInRisk import de.rki.coronawarnapp.presencetracing.risk.calculation.PresenceTracingDayRisk -import de.rki.coronawarnapp.presencetracing.risk.calculation.mapToRiskState import de.rki.coronawarnapp.presencetracing.risk.storage.PresenceTracingRiskRepository import de.rki.coronawarnapp.risk.CombinedEwPtDayRisk import de.rki.coronawarnapp.risk.CombinedEwPtRiskLevelResult @@ -13,6 +12,7 @@ import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.EwRiskLevelTaskResult import de.rki.coronawarnapp.risk.LastCombinedRiskResults import de.rki.coronawarnapp.risk.RiskState +import de.rki.coronawarnapp.risk.mapToRiskState import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult import de.rki.coronawarnapp.risk.result.ExposureWindowDayRisk import de.rki.coronawarnapp.risk.storage.internal.RiskResultDatabase @@ -256,10 +256,13 @@ internal fun combineRisk( } } -internal fun combine(left: RiskState?, right: RiskState?): RiskState { - return if (left == RiskState.INCREASED_RISK || right == RiskState.INCREASED_RISK) RiskState.INCREASED_RISK - else if (left == RiskState.LOW_RISK || right == RiskState.LOW_RISK) RiskState.LOW_RISK - else RiskState.CALCULATION_FAILED +internal fun combine(vararg states: RiskState?): RiskState { + if (states.any { it == RiskState.CALCULATION_FAILED }) return RiskState.CALCULATION_FAILED + if (states.any { it == RiskState.INCREASED_RISK }) return RiskState.INCREASED_RISK + + require(states.filterNotNull().all { it == RiskState.LOW_RISK }) + + return RiskState.LOW_RISK } internal fun max(left: Instant, right: Instant): Instant { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt index 49488cc0e03fd3dd3dcb9f3add4fcc5edb6b154d..6f9fdeed6e2231cc1f4f7bba4c3ea874a405d299 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/TracingRepository.kt @@ -1,11 +1,14 @@ package de.rki.coronawarnapp.storage +import android.annotation.SuppressLint import android.content.Context import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.detectiontracker.lastSubmission +import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningTask +import de.rki.coronawarnapp.presencetracing.risk.execution.PresenceTracingWarningTaskProgress import de.rki.coronawarnapp.risk.RiskLevelTask import de.rki.coronawarnapp.task.TaskController import de.rki.coronawarnapp.task.TaskInfo @@ -21,7 +24,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.joda.time.Duration import timber.log.Timber @@ -45,26 +48,49 @@ class TracingRepository @Inject constructor( private val backgroundModeStatus: BackgroundModeStatus ) { + @SuppressLint("BinaryOperationInTimber") val tracingProgress: Flow<TracingProgress> = combine( - taskController.tasks.map { it.isDownloadDiagnosisKeysTaskRunning() }, + taskController.tasks, enfClient.isPerformingExposureDetection(), - taskController.tasks.map { it.isRiskLevelTaskRunning() } - ) { isDownloading, isExposureDetecting, isRiskLeveling -> + ) { taskInfos, isExposureDetecting -> + + val isEWDownloading = taskInfos.isEWDownloadingPackages() + val isEWCalculatingRisk = taskInfos.isEWCalculatingRisk() + + val isPTDownloading = taskInfos.isPTDownloadingPackages() + val isPTCalculatingRisk = taskInfos.isPTCalculatingRisk() + when { - isDownloading -> TracingProgress.Downloading - isExposureDetecting || isRiskLeveling -> TracingProgress.ENFIsCalculating + isEWDownloading || isPTDownloading -> TracingProgress.Downloading + isExposureDetecting || isEWCalculatingRisk || isPTCalculatingRisk -> TracingProgress.IsCalculating else -> TracingProgress.Idle + }.also { + Timber.tag(TAG).v( + "TracingProgress: $it, isExposureDetecting=$isExposureDetecting, " + + "isEWDownloading=$isEWDownloading, isEWCalculatingRisk=$isEWCalculatingRisk, " + + "isPTDownloading=$isPTDownloading, isPTCalculatingRisk=$isPTCalculatingRisk" + ) } } - private fun List<TaskInfo>.isRiskLevelTaskRunning() = any { - it.taskState.isActive && it.taskState.request.type == RiskLevelTask::class + private suspend fun List<TaskInfo>.isPTDownloadingPackages() = any { + it.taskState.isActive && it.taskState.request.type == PresenceTracingWarningTask::class && + it.progress.firstOrNull() is PresenceTracingWarningTaskProgress.Downloading } - private fun List<TaskInfo>.isDownloadDiagnosisKeysTaskRunning() = any { + private suspend fun List<TaskInfo>.isPTCalculatingRisk() = any { + it.taskState.isActive && it.taskState.request.type == PresenceTracingWarningTask::class && + it.progress.firstOrNull() is PresenceTracingWarningTaskProgress.Calculating + } + + private fun List<TaskInfo>.isEWDownloadingPackages() = any { it.taskState.isActive && it.taskState.request.type == DownloadDiagnosisKeysTask::class } + private fun List<TaskInfo>.isEWCalculatingRisk() = any { + it.taskState.isActive && it.taskState.request.type == RiskLevelTask::class + } + /** * Refresh the diagnosis keys. For that isRefreshing is set to true which is displayed in the ui. * Afterwards the RetrieveDiagnosisKeysTransaction and the RiskLevelTransaction are started. 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 a860a2edb3b0e5692ea03bcb94157c091882638f..5f801d507b6da853a6d4ec7602f0766e263bd0da 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 @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.submission.task import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.CheckInsTransformer @@ -142,7 +143,7 @@ class SubmissionTask @Inject constructor( ) Timber.tag(TAG).d("Transformed keys with symptoms %s from %s to %s", symptoms, keys, transformedKeys) - val checkIns = checkInsRepository.allCheckIns.first() + val checkIns = checkInsRepository.checkInsWithinRetention.first() val transformedCheckIns = checkInsTransformer.transform(checkIns, symptoms) Timber.tag(TAG).d("Transformed CheckIns from: %s to: %s", checkIns, transformedCheckIns) @@ -165,9 +166,17 @@ class SubmissionTask @Inject constructor( Timber.tag(TAG).d("Submission successful, deleting submission data.") tekHistoryStorage.clear() - checkInsRepository.clear() submissionSettings.symptoms.update { null } + Timber.tag(TAG).d("Marking %d submitted CheckIns.", checkIns.size) + checkIns.forEach { checkIn -> + try { + checkInsRepository.markCheckInAsSubmitted(checkIn.id) + } catch (e: Exception) { + e.reportProblem(TAG, "CheckIn $checkIn could not be marked as submitted") + } + } + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) setSubmissionFinished() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt index d94e448b7c7e0789219c48b7f1344edf27a82801..54f584682f70f72917c1c9805010ea5b3774e093 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt @@ -45,7 +45,7 @@ class SubmissionStateProvider @Inject constructor( eval.isResultNegative() -> TestNegative eval.isSubmissionDone() -> SubmissionDone(testRegisteredOn = testRegistrationDate) eval.isPending() -> TestPending - else -> if (CWADebug.isDeviceForTestersBuild) throw IllegalStateException() else TestPending + else -> if (CWADebug.isDeviceForTestersBuild) throw IllegalStateException(eval.toString()) else TestPending } } .onStart { Timber.v("SubmissionStateProvider FLOW start") } @@ -123,5 +123,10 @@ class SubmissionStateProvider @Inject constructor( } else -> false } + + override fun toString() = + "Evaluation(deviceUiState=$deviceUiState, " + + "isDeviceRegistered=$isDeviceRegistered, " + + "hasTestResultBeenSeen=$hasTestResultBeenSeen)" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt index aa4416de0b312c310b2623548007880b4f582d02..955d17a6a6c56d4550a857fafebfc055fd5bf71d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/TracingProgress.kt @@ -2,9 +2,15 @@ package de.rki.coronawarnapp.tracing sealed class TracingProgress { - object Idle : TracingProgress() + object Idle : TracingProgress() { + override fun toString(): String = "TracingProgress.Idle" + } - object Downloading : TracingProgress() + object Downloading : TracingProgress() { + override fun toString(): String = "TracingProgress.Downloading" + } - object ENFIsCalculating : TracingProgress() + object IsCalculating : TracingProgress() { + override fun toString(): String = "TracingProgress.IsCalculating" + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt index a12be64f0bd184ca6149d1d56e8e598d1382f6b2..58cf7f5372fe8b00a875ead825d7347ae7343e20 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingState.kt @@ -226,13 +226,13 @@ data class TracingInProgress( fun getProgressCardHeadline(c: Context): String = when (tracingProgress) { TracingProgress.Downloading -> R.string.risk_card_progress_download_headline - TracingProgress.ENFIsCalculating -> R.string.risk_card_progress_calculation_headline + TracingProgress.IsCalculating -> R.string.risk_card_progress_calculation_headline TracingProgress.Idle -> null }?.let { c.getString(it) } ?: "" fun getProgressCardBody(c: Context): String = when (tracingProgress) { TracingProgress.Downloading -> R.string.risk_card_progress_download_body - TracingProgress.ENFIsCalculating -> R.string.risk_card_progress_calculation_body + TracingProgress.IsCalculating -> R.string.risk_card_progress_calculation_body TracingProgress.Idle -> null }?.let { c.getString(it) } ?: "" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt index f47238e681de2d5ec7077d10bf0ba2e1d60aa649..7537905dc3a80932bd577072e9899170b12124e1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProvider.kt @@ -47,7 +47,7 @@ class TracingDetailsItemProvider @Inject constructor( mutableListOf<DetailsItem>().apply { if (status != Status.TRACING_INACTIVE && latestCalc.riskState == RiskState.LOW_RISK && - latestCalc.ewRiskLevelResult.matchedKeyCount > 0 + latestCalc.matchedRiskCount > 0 ) { add(AdditionalInfoLowRiskBox.Item) } @@ -81,7 +81,7 @@ class TracingDetailsItemProvider @Inject constructor( } latestCalc.riskState == RiskState.LOW_RISK -> DetailsLowRiskBox.Item( riskState = latestCalc.riskState, - matchedKeyCount = latestCalc.ewRiskLevelResult.matchedKeyCount + matchedRiskCount = latestCalc.matchedRiskCount ) latestCalc.riskState == RiskState.INCREASED_RISK -> DetailsIncreasedRiskBox.Item( riskState = latestCalc.riskState, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt index 574698d978b25369e7610465708645e40beb0f21..d3c84c6c04e994cc07c6b3dc07136817159041fa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/riskdetails/DetailsLowRiskBox.kt @@ -33,7 +33,8 @@ class DetailsLowRiskBox( payloads: List<Any> ) -> Unit = { item, _ -> info = item - if (item.matchedKeyCount > 0) { + // Low risk, but matched risk item? More info! Don't worry! + if (item.matchedRiskCount > 0) { riskDetailsInformationLowriskBodyUrl.visibility = View.VISIBLE riskDetailsInformationLowriskBodyUrl.convertToHyperlink( context.getString(R.string.risk_details_explanation_faq_link) @@ -46,13 +47,13 @@ class DetailsLowRiskBox( data class Item( val riskState: RiskState, - val matchedKeyCount: Int + val matchedRiskCount: Int ) : RiskDetailsStateItem { fun getRiskDetailsRiskLevelBody(c: Context): String { // TODO consider pt encounters? return c.getString( - if (matchedKeyCount > 0) R.string.risk_details_information_body_low_risk_with_encounter + if (matchedRiskCount > 0) R.string.risk_details_information_body_low_risk_with_encounter else R.string.risk_details_information_body_low_risk ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt index e6f0123e69c4734517875010ea148e6006d78206..07b90907c3e3a2d6e3e9141c29603f5e9ba9f4fb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/EventRegistrationUIModule.kt @@ -16,6 +16,8 @@ import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocatio import de.rki.coronawarnapp.ui.eventregistration.organizer.category.TraceLocationCategoryFragmentModule import de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragment import de.rki.coronawarnapp.ui.eventregistration.organizer.create.TraceLocationCreateFragmentModule +import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragment +import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragmentModule import de.rki.coronawarnapp.ui.eventregistration.organizer.list.TraceLocationsFragment import de.rki.coronawarnapp.ui.eventregistration.organizer.list.TraceLocationsFragmentModule import de.rki.coronawarnapp.ui.eventregistration.organizer.poster.QrCodePosterFragment @@ -55,4 +57,7 @@ internal abstract class EventRegistrationUIModule { @ContributesAndroidInjector(modules = [QrCodePosterFragmentModule::class]) abstract fun qrCodePosterFragment(): QrCodePosterFragment + + @ContributesAndroidInjector(modules = [QrCodeDetailFragmentModule::class]) + abstract fun showEventDetail(): QrCodeDetailFragment } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt index be14a1abc59bc5fa0d162fd4638e7c4f23105ffe..5af35e5be9608b289d8430ec27353589a67d2d08 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInEvent.kt @@ -11,7 +11,7 @@ sealed class CheckInEvent { data class ConfirmCheckIn(val verifiedTraceLocation: VerifiedTraceLocation) : CheckInEvent() - data class EditCheckIn(val checkInId: Long) : CheckInEvent() + data class EditCheckIn(val checkInId: Long, val position: Int) : CheckInEvent() data class ConfirmSwipeItem(val checkIn: CheckIn, val position: Int) : CheckInEvent() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt index b39c06703c9b30c4dd27fd5f0a4802e59f545375..4a99d53591edb92a549e77939ecd19b82604adb6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsFragment.kt @@ -17,12 +17,14 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.DefaultItemAnimator import com.google.android.material.transition.Hold +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.BuildConfig import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationAttendeeCheckinsFragmentBinding import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.items.CameraPermissionVH import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.items.CheckInsItem +import de.rki.coronawarnapp.ui.eventregistration.attendee.edit.EditCheckInFragmentArgs import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.list.isSwipeable import de.rki.coronawarnapp.util.list.onSwipeItem @@ -58,11 +60,6 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag private val binding: TraceLocationAttendeeCheckinsFragmentBinding by viewBindingLazy() private val checkInsAdapter = CheckInsAdapter() - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - exitTransition = Hold() - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupMenu(binding.toolbar) @@ -91,11 +88,14 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag private fun onNavigationEvent(event: CheckInEvent?) { when (event) { - is CheckInEvent.ConfirmCheckIn -> doNavigate( - CheckInsFragmentDirections.actionCheckInsFragmentToConfirmCheckInFragment( - verifiedTraceLocation = event.verifiedTraceLocation + is CheckInEvent.ConfirmCheckIn -> { + setupAxisTransition() + doNavigate( + CheckInsFragmentDirections.actionCheckInsFragmentToConfirmCheckInFragment( + verifiedTraceLocation = event.verifiedTraceLocation + ) ) - ) + } is CheckInEvent.ConfirmSwipeItem -> showRemovalConfirmation(event.checkIn, event.position) @@ -103,15 +103,24 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag is CheckInEvent.ConfirmRemoveAll -> showRemovalConfirmation(null, null) - is CheckInEvent.EditCheckIn -> doNavigate( - CheckInsFragmentDirections.actionCheckInsFragmentToEditCheckInFragment( - editCheckInId = event.checkInId + is CheckInEvent.EditCheckIn -> { + setupHoldTransition() + val navigatorExtras = binding.checkInsList.layoutManager + ?.findViewByPosition(event.position)?.run { + FragmentNavigatorExtras(this to transitionName) + } + findNavController().navigate( + R.id.action_checkInsFragment_to_editCheckInFragment, + EditCheckInFragmentArgs(event.checkInId).toBundle(), + null, + navigatorExtras ) - ) + } - is CheckInEvent.ShowInformation -> + is CheckInEvent.ShowInformation -> { + setupAxisTransition() doNavigate(CheckInsFragmentDirections.actionCheckInsFragmentToCheckInOnboardingFragment(false)) - + } is CheckInEvent.OpenDeviceSettings -> openDeviceSettings() } } @@ -142,6 +151,7 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag private fun bindFAB() { binding.scanCheckinQrcodeFab.apply { setOnClickListener { + setupHoldTransition() findNavController().navigate( R.id.action_checkInsFragment_to_scanCheckInQrCodeFragment, null, @@ -152,6 +162,16 @@ class CheckInsFragment : Fragment(R.layout.trace_location_attendee_checkins_frag } } + private fun setupHoldTransition() { + exitTransition = Hold() + reenterTransition = Hold() + } + + private fun setupAxisTransition() { + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + private fun bindRecycler() { binding.checkInsList.apply { adapter = checkInsAdapter diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt index e78ae163bec10c1d32b585fc1482ed5ea204597f..cad63ea577b336f62629b952f6668ac15b133842 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModel.kt @@ -41,6 +41,7 @@ class CheckInsViewModel @AssistedInject constructor( val events = SingleLiveEvent<CheckInEvent>() val errorEvent = SingleLiveEvent<Throwable>() + private val cameraItem by lazy { cameraPermissionItem() } init { deepLink?.let { @@ -56,13 +57,13 @@ class CheckInsViewModel @AssistedInject constructor( val checkins: LiveData<List<CheckInsItem>> = combine( intervalFlow(1000), - checkInsRepository.allCheckIns, + checkInsRepository.checkInsWithinRetention, cameraPermissionProvider.deniedPermanently ) { _, checkIns, denied -> mutableListOf<CheckInsItem>().apply { // Camera permission item if (denied) { - add(cameraPermissionItem()) + add(cameraItem) } // CheckIns items addAll(mapCheckIns(checkIns)) @@ -105,7 +106,9 @@ class CheckInsViewModel @AssistedInject constructor( when { !checkin.completed -> ActiveCheckInVH.Item( checkin = checkin, - onCardClicked = { events.postValue(CheckInEvent.EditCheckIn(it.id)) }, + onCardClicked = { checkIn, position -> + events.postValue(CheckInEvent.EditCheckIn(checkIn.id, position)) + }, onRemoveItem = { events.postValue(CheckInEvent.ConfirmRemoveItem(it)) }, onCheckout = { doCheckOutNow(it) }, onSwipeItem = { checkIn, position -> @@ -114,7 +117,9 @@ class CheckInsViewModel @AssistedInject constructor( ) else -> PastCheckInVH.Item( checkin = checkin, - onCardClicked = { events.postValue(CheckInEvent.EditCheckIn(it.id)) }, + onCardClicked = { checkIn, position -> + events.postValue(CheckInEvent.EditCheckIn(checkIn.id, position)) + }, onRemoveItem = { events.postValue(CheckInEvent.ConfirmRemoveItem(it)) }, onSwipeItem = { checkIn, position -> events.postValue(CheckInEvent.ConfirmSwipeItem(checkIn, position)) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/ActiveCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/ActiveCheckInVH.kt index 8b2576d1e692a848d11cf23c3595e6ca4ac3ab21..dce82fc756c047ceedd73a1533318fcc2c203892 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/ActiveCheckInVH.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/ActiveCheckInVH.kt @@ -9,9 +9,9 @@ import de.rki.coronawarnapp.util.TimeAndDateExtensions.toUserTimeZone import de.rki.coronawarnapp.util.list.SwipeConsumer import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer import org.joda.time.Duration +import org.joda.time.DurationFieldType import org.joda.time.Instant import org.joda.time.PeriodType -import org.joda.time.format.DateTimeFormat import org.joda.time.format.PeriodFormat import org.joda.time.format.PeriodFormatterBuilder @@ -37,27 +37,35 @@ class ActiveCheckInVH(parent: ViewGroup) : val checkInStartUserTZ = curItem.checkin.checkInStart.toUserTimeZone() - val checkinDuration = Duration(checkInStartUserTZ, Instant.now()) - highlightDuration.text = highlightDurationForamtter.print(checkinDuration.toPeriod()) + highlightDuration.text = kotlin.run { + val currentDuration = Duration(checkInStartUserTZ, Instant.now()) + val saneDuration = if (currentDuration.isShorterThan(Duration.ZERO)) { + Duration.ZERO + } else { + currentDuration + } + highlightDurationFormatter.print(saneDuration.toPeriod()) + } description.text = curItem.checkin.description address.text = curItem.checkin.address - val startDate = checkInStartUserTZ.toLocalDate() - traceLocationCardHighlightView.setCaption(startDate.toString(DateTimeFormat.mediumDate())) checkoutInfo.text = run { - val checkoutAt = curItem.checkin.checkInEnd - val checkoutIn = Duration(Instant.now(), checkoutAt).let { + val checkoutIn = Duration(curItem.checkin.checkInStart, curItem.checkin.checkInEnd).let { val periodType = when { - it.isLongerThan(Duration.standardHours(1)) -> PeriodType.hours() + it.isLongerThan(Duration.standardHours(1)) -> PeriodType.forFields( + arrayOf(DurationFieldType.hours(), DurationFieldType.minutes()) + ) it.isLongerThan(Duration.standardDays(1)) -> PeriodType.days() else -> PeriodType.minutes() } it.toPeriod(periodType) } + val startDate = checkInStartUserTZ.toLocalDate() context.getString( - R.string.trace_location_checkins_card_automatic_checkout_info, + R.string.trace_location_checkins_card_automatic_checkout_info_format, + startDate.toString("dd.MM.yy"), checkInStartUserTZ.toLocalTime().toString("HH:mm"), hourPeriodFormatter.print(checkoutIn) ) @@ -72,12 +80,15 @@ class ActiveCheckInVH(parent: ViewGroup) : checkoutAction.setOnClickListener { curItem.onCheckout(curItem.checkin) } - itemView.setOnClickListener { curItem.onCardClicked(curItem.checkin) } + itemView.apply { + setOnClickListener { curItem.onCardClicked(curItem.checkin, adapterPosition) } + transitionName = item.checkin.id.toString() + } } data class Item( val checkin: CheckIn, - val onCardClicked: (CheckIn) -> Unit, + val onCardClicked: (CheckIn, Int) -> Unit, val onRemoveItem: (CheckIn) -> Unit, val onCheckout: (CheckIn) -> Unit, val onSwipeItem: (CheckIn, Int) -> Unit, @@ -90,7 +101,7 @@ class ActiveCheckInVH(parent: ViewGroup) : } companion object { - private val highlightDurationForamtter = PeriodFormatterBuilder().apply { + private val highlightDurationFormatter = PeriodFormatterBuilder().apply { printZeroAlways() minimumPrintedDigits(2) appendHours() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt index 7613181df503d685c44a4d591abf0222a9a09b9c..4ae061dcb109ee351fb2f8d3560018981e1386b8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/items/PastCheckInVH.kt @@ -46,12 +46,15 @@ class PastCheckInVH(parent: ViewGroup) : } } - itemView.setOnClickListener { curItem.onCardClicked(curItem.checkin) } + itemView.apply { + setOnClickListener { curItem.onCardClicked(curItem.checkin, adapterPosition) } + transitionName = item.checkin.id.toString() + } } data class Item( val checkin: CheckIn, - val onCardClicked: (CheckIn) -> Unit, + val onCardClicked: (CheckIn, Int) -> Unit, val onRemoveItem: (CheckIn) -> Unit, val onSwipeItem: (CheckIn, Int) -> Unit, ) : CheckInsItem, HasPayloadDiffer, SwipeConsumer { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt index a411d1b2ea41b78288cff2c99248ab6113d8c069..5b9767a72dcf21e160b669bcbaea5cad6c0ceab3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInFragment.kt @@ -6,6 +6,7 @@ import androidx.core.view.isGone import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentConfirmCheckInBinding import de.rki.coronawarnapp.ui.durationpicker.DurationPicker @@ -31,6 +32,13 @@ class ConfirmCheckInFragment : Fragment(R.layout.fragment_confirm_check_in), Aut } ) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + private val binding: FragmentConfirmCheckInBinding by viewBindingLazy() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -51,7 +59,7 @@ class ConfirmCheckInFragment : Fragment(R.layout.fragment_confirm_check_in), Aut viewModel.createJournalEntryToggled(isChecked) } - confirmCheckinSettingsCardCheckoutTime.setOnClickListener { + confirmCheckinSettingsCardCheckoutTimeRow.setOnClickListener { viewModel.dateSelectorClicked() } @@ -83,6 +91,7 @@ class ConfirmCheckInFragment : Fragment(R.layout.fragment_confirm_check_in), Aut uiState.eventInFutureDateText, uiState.eventInFutureTimeText ) + confirmCheckinConfirmButton.isEnabled = uiState.confirmButtonEnabled } } @@ -99,7 +108,6 @@ class ConfirmCheckInFragment : Fragment(R.layout.fragment_confirm_check_in), Aut ) { val durationPicker = DurationPicker.Builder() .duration(defaultValue ?: "00:00") - .minutes() .title(getString(R.string.duration_dialog_title)) .build() durationPicker.show(parentFragmentManager, DURATION_PICKER_TAG) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt index 840682e97c68268d05e0cfc5ce38376f7c1a3eba..a8654db5b89c460c92f3ac006d3f383fa238bce6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/confirm/ConfirmCheckInViewModel.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.eventregistration.checkins.qrcode.VerifiedTraceLocation +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.getDefaultAutoCheckoutLengthInMinutes import de.rki.coronawarnapp.ui.durationpicker.toContactDiaryFormat import de.rki.coronawarnapp.ui.durationpicker.toReadableDuration import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.mapTraceLocationToTitleRes @@ -28,9 +29,10 @@ class ConfirmCheckInViewModel @AssistedInject constructor( ) : CWAViewModel() { private val traceLocation = MutableStateFlow(verifiedTraceLocation.traceLocation) private val createJournalEntry = MutableStateFlow(true) - private val checkInLength = MutableStateFlow( + + private val autoCheckOutLength = MutableStateFlow( Duration.standardMinutes( - verifiedTraceLocation.traceLocation.defaultCheckInLengthInMinutes?.toLong() ?: 0L + verifiedTraceLocation.traceLocation.getDefaultAutoCheckoutLengthInMinutes(timeStamper.nowUTC).toLong() ) ) @@ -40,14 +42,15 @@ class ConfirmCheckInViewModel @AssistedInject constructor( val uiState = combine( traceLocation, createJournalEntry, - checkInLength + autoCheckOutLength ) { traceLocation, createEntry, checkInLength -> UiState( traceLocation = traceLocation, createJournalEntry = createEntry, checkInEndOffset = checkInLength, eventInPastVisible = traceLocation.isAfterEndTime(timeStamper.nowUTC), - eventInFutureVisible = traceLocation.isBeforeStartTime(timeStamper.nowUTC) + eventInFutureVisible = traceLocation.isBeforeStartTime(timeStamper.nowUTC), + confirmButtonEnabled = checkInLength.standardMinutes > 0 ) }.asLiveData() @@ -62,7 +65,7 @@ class ConfirmCheckInViewModel @AssistedInject constructor( verifiedTraceLocation.toCheckIn( checkInStart = now, createJournalEntry = createJournalEntry.value, - checkInEnd = now + checkInLength.value + checkInEnd = now + autoCheckOutLength.value ) ) events.postValue(ConfirmCheckInNavigation.ConfirmNavigation) @@ -74,18 +77,16 @@ class ConfirmCheckInViewModel @AssistedInject constructor( } fun dateSelectorClicked() { - openDatePickerEvent.value = checkInLength.value.toContactDiaryFormat() + openDatePickerEvent.value = autoCheckOutLength.value.toContactDiaryFormat() } fun durationUpdated(duration: Duration) { - checkInLength.value = duration + autoCheckOutLength.value = duration } private fun VerifiedTraceLocation.toCheckIn( checkInStart: Instant, - checkInEnd: Instant = checkInStart.plus( - Duration.standardMinutes(traceLocation.defaultCheckInLengthInMinutes?.toLong() ?: 3L) - ), + checkInEnd: Instant, completed: Boolean = false, createJournalEntry: Boolean = true ): CheckIn { @@ -120,7 +121,8 @@ class ConfirmCheckInViewModel @AssistedInject constructor( private val checkInEndOffset: Duration, val createJournalEntry: Boolean, val eventInPastVisible: Boolean, - val eventInFutureVisible: Boolean + val eventInFutureVisible: Boolean, + val confirmButtonEnabled: Boolean ) { val description get() = traceLocation.description val typeRes get() = mapTraceLocationToTitleRes(traceLocation.type) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInFragment.kt index 0e822c842aca62574fad8ef7c5078b89262a2f97..4f5471d1952764b0b9c54cf3487a93316e08c6f4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInFragment.kt @@ -10,6 +10,7 @@ import com.google.android.material.appbar.AppBarLayout import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.TimeFormat +import com.google.android.material.transition.MaterialContainerTransform import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentEditCheckInBinding import de.rki.coronawarnapp.util.di.AutoInject @@ -36,6 +37,13 @@ class EditCheckInFragment : Fragment(R.layout.fragment_edit_check_in), AutoInjec } ) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + sharedElementEnterTransition = MaterialContainerTransform() + sharedElementReturnTransition = MaterialContainerTransform() + } + private val binding: FragmentEditCheckInBinding by viewBindingLazy() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -71,6 +79,7 @@ class EditCheckInFragment : Fragment(R.layout.fragment_edit_check_in), AutoInjec editCheckinConfirmButton.setOnClickListener { viewModel.onSaveClicked() } + root.transitionName = navArgs.editCheckInId.toString() } viewModel.events.observe2(this) { navEvent -> @@ -94,7 +103,9 @@ class EditCheckInFragment : Fragment(R.layout.fragment_edit_check_in), AutoInjec editCheckinDurationEditHintCard.isGone = !uiState.diaryWarningVisible - editCheckinConfirmButton.isEnabled = uiState.canSaveChanges + editCheckinConfirmButton.isEnabled = uiState.saveButtonEnabled + + editCheckinWrongInputWarning.isGone = !uiState.wrongInputErrorShown } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInViewModel.kt index a266e7e6d8ab74f5f64edcefa1e50e96e7963c4b..7cfc15b894c9aeb71ae03e455d3deed3ebb896f6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/edit/EditCheckInViewModel.kt @@ -14,6 +14,7 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull +import org.joda.time.Days import org.joda.time.Instant import org.joda.time.LocalDate import org.joda.time.LocalTime @@ -33,10 +34,10 @@ class EditCheckInViewModel @AssistedInject constructor( val checkIn = checkInRepository.checkInForId(editCheckInId ?: 0) if (checkInStartTime.value == null) { - checkInStartTime.value = checkIn.checkInStart + checkInStartTime.value = checkIn.checkInStart.toDateTime().toInstant() } if (checkInEndTime.value == null) { - checkInEndTime.value = checkIn.checkInEnd + checkInEndTime.value = checkIn.checkInEnd.toDateTime().toInstant() } checkInFlow.value = checkIn @@ -50,13 +51,13 @@ class EditCheckInViewModel @AssistedInject constructor( val uiState = combine( checkInFlow.filterNotNull(), - checkInStartTime, - checkInEndTime + checkInStartTime.filterNotNull(), + checkInEndTime.filterNotNull() ) { checkIn, checkInStartTime, checkInEndTime -> UiState( checkIn = checkIn, - checkInStartInstant = checkInStartTime ?: checkIn.checkInStart, - checkInEndInstant = checkInEndTime ?: checkIn.checkInEnd + checkInStartInstant = checkInStartTime, + checkInEndInstant = checkInEndTime ) }.asLiveData() @@ -144,7 +145,15 @@ class EditCheckInViewModel @AssistedInject constructor( val checkInStartTime: String get() = checkInStartInstant.toDateTime().toString(timeFormatter) val checkInEndDate: String get() = checkInEndInstant.toDateTime().toString(dateFormatter) val checkInEndTime: String get() = checkInEndInstant.toDateTime().toString(timeFormatter) - val canSaveChanges: Boolean get() = checkInStartInstant.isBefore(checkInEndInstant) + val saveButtonEnabled: Boolean get() = isInputValid() + val wrongInputErrorShown: Boolean get() = !saveButtonEnabled + + private fun isInputValid(): Boolean { + val startBeforeEnd = checkInStartInstant.isBefore(checkInEndInstant) + val lessThan24h = Days.daysBetween(checkInStartInstant, checkInEndInstant).days < 1 + + return startBeforeEnd and lessThan24h + } } sealed class DateTimePickerEvent { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/onboarding/CheckInOnboardingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/onboarding/CheckInOnboardingFragment.kt index c66e18b87292ea60bd1ecf994c8588fe33770faf..1a9eb4cd3eb753544367c454dbdc177f2c9f16c3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/onboarding/CheckInOnboardingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/attendee/onboarding/CheckInOnboardingFragment.kt @@ -4,12 +4,14 @@ import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentTraceLocationOnboardingBinding import de.rki.coronawarnapp.util.ContextExtensions.getDrawableCompat import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 +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.cwaViewModels @@ -23,9 +25,20 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo private val binding: FragmentTraceLocationOnboardingBinding by viewBindingLazy() private val args by navArgs<CheckInOnboardingFragmentArgs>() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (viewModel.isOnboardingComplete && args.uri != null) { + doNavigate(CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToCheckInsFragment(args.uri)) + } + with(binding) { checkInOnboardingAcknowledge.setOnClickListener { viewModel.onAcknowledged() } // TODO if consent is already given: should the text be changed? @@ -36,7 +49,7 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo checkInOnboardingToolbar.apply { navigationIcon = context.getDrawableCompat(R.drawable.ic_close) navigationContentDescription = getString(R.string.accessibility_close) - setNavigationOnClickListener { viewModel.onBackButtonPress() } + setNavigationOnClickListener { popBackStack() } } } } @@ -45,7 +58,7 @@ class CheckInOnboardingFragment : Fragment(R.layout.fragment_trace_location_onbo doNavigate( when (navEvent) { CheckInOnboardingNavigation.AcknowledgedNavigation -> - CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToCheckInsFragment() + CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToCheckInsFragment(args.uri) CheckInOnboardingNavigation.DataProtectionNavigation -> CheckInOnboardingFragmentDirections.actionCheckInOnboardingFragmentToPrivacyFragment() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt index 31f49804d9db7fed35bad84e2e5799f301b78d1d..ffcb22118b5d722e610c063325d14e7dd7b5120f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/category/adapter/category/TraceLocationCategory.kt @@ -19,6 +19,7 @@ import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.CategoryItem import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationUIType.EVENT import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationUIType.LOCATION +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @Parcelize @@ -28,7 +29,7 @@ data class TraceLocationCategory( @StringRes val title: Int, @StringRes val subtitle: Int? = null ) : CategoryItem, Parcelable { - override val stableId = hashCode().toLong() + @IgnoredOnParcel override val stableId = hashCode().toLong() } enum class TraceLocationUIType { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt index 69f0567110f5a16412582ac7cdda392f09351ae2..385d826151839cd32662301c8d045d8eef15e153 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateFragment.kt @@ -25,7 +25,6 @@ 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.DateTimeZone import org.joda.time.Duration import org.joda.time.LocalDate import org.joda.time.LocalDateTime @@ -47,6 +46,7 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag } ) + @Suppress("NestedBlockDepth") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -121,8 +121,8 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag placeInputEdit.setText(it.address) } viewModel.apply { - begin = LocalDateTime(it.startDate) - end = LocalDateTime(it.endDate) + begin = it.startDate?.let { time -> LocalDateTime(time) } + end = it.endDate?.let { time -> LocalDateTime(time) } checkInLength = Duration.standardMinutes(it.defaultCheckInLengthInMinutes?.toLong() ?: 0L) } } @@ -159,14 +159,14 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag MaterialDatePicker .Builder .datePicker() - .setSelection(defaultValue?.toDateTime(DateTimeZone.UTC)?.millis) + .setSelection(defaultValue?.toDateTime()?.millis) .apply { if (minConstraint != null) { setCalendarConstraints( CalendarConstraints.Builder() .setValidator( DateValidatorPointForward - .from(minConstraint.withMillisOfDay(0).toDateTime(DateTimeZone.UTC).millis) + .from(minConstraint.withMillisOfDay(0).toDateTime().millis) ) .build() ) @@ -202,7 +202,7 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag private fun showDurationPicker() { DurationPicker.Builder() - .duration(viewModel.checkInLength?.toContactDiaryFormat() ?: "") + .duration(viewModel.checkInLength.toContactDiaryFormat()) .title(getString(R.string.tracelocation_organizer_add_event_length_of_stay)) .build() .apply { @@ -216,7 +216,7 @@ class TraceLocationCreateFragment : Fragment(R.layout.trace_location_create_frag override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState( outState.apply { - putLong(LENGTH_OF_STAY, viewModel.checkInLength?.standardMinutes ?: 0L) + putLong(LENGTH_OF_STAY, viewModel.checkInLength.standardMinutes) putSerializable(BEGIN, viewModel.begin) putSerializable(END, viewModel.end) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt index 55a2af7908ad3c6ac7486287e62fb4af892c8a0a..da0c661715bac8e462d43a71c31ca012a5012c24 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/create/TraceLocationCreateViewModel.kt @@ -19,7 +19,6 @@ 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.DateTimeZone import org.joda.time.Duration import org.joda.time.LocalDateTime import timber.log.Timber @@ -64,8 +63,8 @@ class TraceLocationCreateViewModel @AssistedInject constructor( type = category.type, description = description, address = address, - startDate = begin?.toDateTime(DateTimeZone.UTC)?.toInstant(), - endDate = end?.toDateTime(DateTimeZone.UTC)?.toInstant(), + startDate = begin?.toDateTime()?.toInstant(), + endDate = end?.toDateTime()?.toInstant(), defaultCheckInLengthInMinutes = checkInLength.standardMinutes.toInt() ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailFragment.kt index bd2763bc00c30b84cdc58a650948906d7fa478dd..82521d81b8aa135937772bd9b30e7343f5632cdd 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailFragment.kt @@ -12,6 +12,7 @@ import androidx.navigation.fragment.navArgs import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener import com.google.android.material.transition.MaterialContainerTransform +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationOrganizerQrCodeDetailFragmentBinding import de.rki.coronawarnapp.util.ContextExtensions.getDrawableCompat @@ -72,25 +73,29 @@ class QrCodeDetailFragment : Fragment(R.layout.trace_location_organizer_qr_code_ } qrCodePrintButton.setOnClickListener { + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + viewModel.onPrintQrCode() } - } - viewModel.qrCodeBitmap.observe2(this) { - binding.progressBar.hide() - binding.qrCodeImage.apply { - val resourceId = RoundedBitmapDrawableFactory.create(resources, it) - resourceId.cornerRadius = it.width * 0.1f - setImageDrawable(resourceId) + qrCodeCloneButton.setOnClickListener { + viewModel.duplicateTraceLocation() } + + root.transitionName = navArgs.traceLocationId.toString() } viewModel.routeToScreen.observe2(this) { when (it) { QrCodeDetailNavigationEvents.NavigateBack -> popBackStack() - QrCodeDetailNavigationEvents.NavigateToDuplicateFragment -> { /* TODO */ - } + is QrCodeDetailNavigationEvents.NavigateToDuplicateFragment -> doNavigate( + QrCodeDetailFragmentDirections.actionQrCodeDetailFragmentToTraceLocationCreateFragment( + it.category, + it.traceLocation + ) + ) is QrCodeDetailNavigationEvents.NavigateToQrCodePosterFragment -> doNavigate( QrCodeDetailFragmentDirections.actionQrCodeDetailFragmentToQrCodePosterFragment(it.locationId) @@ -128,6 +133,15 @@ class QrCodeDetailFragment : Fragment(R.layout.trace_location_organizer_qr_code_ } else { eventDate.isGone = true } + + uiState.bitmap?.let { + binding.progressBar.hide() + binding.qrCodeImage.apply { + val resourceId = RoundedBitmapDrawableFactory.create(resources, it) + resourceId.cornerRadius = it.width * 0.1f + setImageDrawable(resourceId) + } + } } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailNavigationEvents.kt index 68027a1fd55345d94dd670307491137c2f9b8a04..1939ed098a96a614fb192fb6771ee661c6cbb9de 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailNavigationEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailNavigationEvents.kt @@ -1,7 +1,11 @@ package de.rki.coronawarnapp.ui.eventregistration.organizer.details +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.TraceLocationCategory + sealed class QrCodeDetailNavigationEvents { object NavigateBack : QrCodeDetailNavigationEvents() data class NavigateToQrCodePosterFragment(val locationId: Long) : QrCodeDetailNavigationEvents() - object NavigateToDuplicateFragment : QrCodeDetailNavigationEvents() + data class NavigateToDuplicateFragment(val traceLocation: TraceLocation, val category: TraceLocationCategory) : + QrCodeDetailNavigationEvents() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailViewModel.kt index 739078b1e3d5e2716236e7fca68f3bf355c1d141..30f3363daca6adda72f5a868f791d1e571166590 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/details/QrCodeDetailViewModel.kt @@ -3,7 +3,6 @@ package de.rki.coronawarnapp.ui.eventregistration.organizer.details import android.graphics.Bitmap import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asLiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -12,15 +11,14 @@ import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.eventregistration.storage.repo.DefaultTraceLocationRepository import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.traceLocationCategories 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 kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.filterNotNull import org.joda.time.Instant import timber.log.Timber +import java.lang.Exception class QrCodeDetailViewModel @AssistedInject constructor( @Assisted private val traceLocationId: Long, @@ -29,74 +27,38 @@ class QrCodeDetailViewModel @AssistedInject constructor( private val traceLocationRepository: DefaultTraceLocationRepository ) : CWAViewModel() { - private val traceLocationFlow = MutableStateFlow<TraceLocation?>(null) - private val titleFlow = MutableStateFlow<String?>(null) - private val subtitleFlow = MutableStateFlow<String?>(null) - private val startTimeFlow = MutableStateFlow<Instant?>(null) - private val endTimeFlow = MutableStateFlow<Instant?>(null) - private val bitmapLiveData = MutableLiveData<Bitmap>() + private var traceLocation: TraceLocation? = null + private val mutableUiState = MutableLiveData<UiState>() + val uiState: LiveData<UiState> + get() = mutableUiState + val routeToScreen: SingleLiveEvent<QrCodeDetailNavigationEvents> = SingleLiveEvent() init { - launch { - val traceLocation = traceLocationRepository.traceLocationForId(traceLocationId) - - if (titleFlow.value == null) { - titleFlow.value = traceLocation.description - } - - if (subtitleFlow.value == null) { - subtitleFlow.value = traceLocation.address - } - - if (startTimeFlow.value == null) { - startTimeFlow.value = traceLocation.startDate - } - - if (endTimeFlow.value == null) { - endTimeFlow.value = traceLocation.endDate - } - - traceLocationFlow.value = traceLocation - - createQrCode(traceLocation) + loadTraceLocation() } } - val uiState = combine( - traceLocationFlow.filterNotNull(), - startTimeFlow, - endTimeFlow - ) { traceLocation, startTime, endTime -> - UiState( - traceLocation = traceLocation, - startInstant = startTime ?: traceLocation.startDate, - endInstant = endTime ?: traceLocation.endDate - ) - }.asLiveData() - - data class UiState( - private val traceLocation: TraceLocation, - private val startInstant: Instant?, - private val endInstant: Instant? - ) { - val description: String get() = traceLocation.description - val address: String get() = traceLocation.address - val startDateTime: Instant? get() = startInstant - val endDateTime: Instant? get() = endInstant + private suspend fun loadTraceLocation() { + try { + traceLocation = traceLocationRepository.traceLocationForId(traceLocationId).also { + mutableUiState.postValue(UiState(it)) + createQrCode(it) + } + } catch (exception: Exception) { + Timber.d(exception, "No location found") + exception.report(ExceptionCategory.INTERNAL) + } } - val qrCodeBitmap: LiveData<Bitmap> = bitmapLiveData - val routeToScreen: SingleLiveEvent<QrCodeDetailNavigationEvents> = SingleLiveEvent() - /** - * Creates a QR Code [Bitmap] ,result is delivered by [qrCodeBitmap] + * Creates a QR Code [Bitmap] ,result is delivered by [uiState] */ private fun createQrCode(traceLocation: TraceLocation) = launch(context = dispatcher.IO) { try { val input = traceLocation.locationUrl Timber.d("input=$input") - bitmapLiveData.postValue(qrCodeGenerator.createQrCode(input)) + mutableUiState.postValue(UiState(traceLocation, qrCodeGenerator.createQrCode(input))) } catch (e: Exception) { Timber.d(e, "Qr code creation failed") e.report(ExceptionCategory.INTERNAL) @@ -113,6 +75,27 @@ class QrCodeDetailViewModel @AssistedInject constructor( ) } + fun duplicateTraceLocation() { + traceLocation?.let { + val category = traceLocationCategories.find { category -> category.type == it.type } + if (category == null) { + Timber.e("Category not found, traceLocation = $traceLocation") + } else { + routeToScreen.postValue(QrCodeDetailNavigationEvents.NavigateToDuplicateFragment(it, category)) + } + } + } + + data class UiState( + private val traceLocation: TraceLocation, + val bitmap: Bitmap? = null + ) { + val description: String get() = traceLocation.description + val address: String get() = traceLocation.address + val startDateTime: Instant? get() = traceLocation.startDate + val endDateTime: Instant? get() = traceLocation.endDate + } + @AssistedFactory interface Factory : CWAViewModelFactory<QrCodeDetailViewModel> { fun create( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationEvent.kt index 5909c982933269feaeeb47a6fa3d1f3aebc0e2f8..a4576f0ae318ae641605d530e667d128c0bae926 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationEvent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationEvent.kt @@ -6,6 +6,8 @@ sealed class TraceLocationEvent { data class DuplicateItem(val traceLocation: TraceLocation) : TraceLocationEvent() + data class SelfCheckIn(val traceLocation: TraceLocation) : TraceLocationEvent() + data class ConfirmDeleteItem(val traceLocation: TraceLocation) : TraceLocationEvent() data class ConfirmSwipeItem(val traceLocation: TraceLocation, val position: Int) : TraceLocationEvent() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsFragment.kt index 1847e8f7b3912ec7198198b0986705753f2e1952..32fccb915730437caa226c162b27f14acfd248d6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsFragment.kt @@ -7,12 +7,15 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.core.view.isGone import androidx.fragment.app.Fragment +import androidx.navigation.NavOptions import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.findNavController import com.google.android.material.transition.Hold +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsListFragmentBinding import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment import de.rki.coronawarnapp.ui.eventregistration.organizer.category.adapter.category.traceLocationCategories import de.rki.coronawarnapp.ui.eventregistration.organizer.details.QrCodeDetailFragmentArgs import de.rki.coronawarnapp.util.DialogHelper @@ -40,7 +43,9 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - exitTransition = Hold() + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -82,11 +87,9 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_ showDeleteSingleDialog(it.traceLocation, it.position) } is TraceLocationEvent.StartQrCodeDetailFragment -> { - + setupHoldTransition() val navigatorExtras = binding.recyclerView.findViewHolderForAdapterPosition(it.position)?.itemView ?.run { - // Set it on the fly to avoid confusion of recycler's items - this.transitionName = "trace_location_container_transition" FragmentNavigatorExtras(this to this.transitionName) } @@ -98,18 +101,33 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_ ) } is TraceLocationEvent.DuplicateItem -> { + setupAxisTransition() openCreateEventFragment(it.traceLocation) } - is TraceLocationEvent.StartQrCodePosterFragment -> doNavigate( - TraceLocationsFragmentDirections.actionTraceLocationsFragmentToQrCodePosterFragment( - it.traceLocation.id + is TraceLocationEvent.StartQrCodePosterFragment -> { + setupAxisTransition() + doNavigate( + + TraceLocationsFragmentDirections.actionTraceLocationsFragmentToQrCodePosterFragment( + it.traceLocation.id + ) ) - ) + } + + is TraceLocationEvent.SelfCheckIn -> { + findNavController().navigate( + CheckInsFragment.createCheckInUri(it.traceLocation.locationUrl), + NavOptions.Builder() + .setPopUpTo(R.id.checkInsFragment, true) + .build() + ) + } } } binding.qrCodeFab.apply { setOnClickListener { + setupHoldTransition() findNavController().navigate( R.id.action_traceLocationsFragment_to_traceLocationCategoryFragment, null, @@ -120,6 +138,16 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_ } } + private fun setupHoldTransition() { + exitTransition = Hold() + reenterTransition = Hold() + } + + private fun setupAxisTransition() { + exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + override fun onResume() { super.onResume() binding.contentContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) @@ -130,6 +158,7 @@ class TraceLocationsFragment : Fragment(R.layout.trace_location_organizer_trace_ setOnMenuItemClickListener { when (it.itemId) { R.id.menu_information -> { + setupAxisTransition() findNavController().navigate( R.id.action_traceLocationOrganizerListFragment_to_traceLocationInfoFragment ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsViewModel.kt index d4167ccd56634c989b0e015f00cffb71b9aeebaa..e00d80c9b5d4e9293c70382f3fda8a941c0de911 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/TraceLocationsViewModel.kt @@ -4,36 +4,44 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository import de.rki.coronawarnapp.ui.eventregistration.organizer.list.items.TraceLocationItem import de.rki.coronawarnapp.ui.eventregistration.organizer.list.items.TraceLocationVH -import de.rki.coronawarnapp.util.TimeStamper 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 kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map class TraceLocationsViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, + checkInsRepository: CheckInRepository, private val traceLocationRepository: TraceLocationRepository, - private val timeStamper: TimeStamper ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val events = SingleLiveEvent<TraceLocationEvent>() - val traceLocations: LiveData<List<TraceLocationItem>> = traceLocationRepository.allTraceLocations + val traceLocations: LiveData<List<TraceLocationItem>> = traceLocationRepository.traceLocationsWithinRetention .map { traceLocations -> - traceLocations - .filter { it.endDate == null || it.endDate.isAfter(timeStamper.nowUTC.toDateTime().minusDays(15)) } - .sortedBy { it.description } + traceLocations.sortedBy { it.description } } - .map { traceLocations -> + .combine(checkInsRepository.allCheckIns) { traceLocations, checkIns -> traceLocations.map { traceLocation -> + Pair( + traceLocation, + checkIns.firstOrNull { traceLocation.locationId == it.traceLocationId && !it.completed } == null + ) + } + } + .map { traceLocations -> + traceLocations.map { item -> TraceLocationVH.Item( - traceLocation = traceLocation, - onCheckIn = { /* TODO */ }, + traceLocation = item.first, + canCheckIn = item.second, + onCheckIn = { events.postValue(TraceLocationEvent.SelfCheckIn(it)) }, onDuplicate = { events.postValue(TraceLocationEvent.DuplicateItem(it)) }, onShowPrint = { events.postValue(TraceLocationEvent.StartQrCodePosterFragment(it)) }, onDeleteItem = { events.postValue(TraceLocationEvent.ConfirmDeleteItem(it)) }, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/items/TraceLocationVH.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/items/TraceLocationVH.kt index da8cca4747fd735671ea02fdfed288116addb87a..7a1f9a80aa342f55f77f772262c52328be1c6564 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/items/TraceLocationVH.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/list/items/TraceLocationVH.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.eventregistration.organizer.list.items import android.view.ViewGroup import androidx.core.view.isGone +import androidx.core.view.isVisible import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationOrganizerTraceLocationsItemBinding import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation @@ -34,11 +35,11 @@ class TraceLocationVH(parent: ViewGroup) : duration.isGone = false duration.text = if (startTime.toLocalDate() == endTime.toLocalDate()) { - icon.setCaption(startTime.toString("dd.MM.yy")) context.getString( - R.string.trace_location_organizer_list_item_duration, - startTime.toLocalTime().toString("HH:mm"), - endTime.toLocalTime().toString("HH:mm") + R.string.trace_location_organizer_list_item_duration_same_day, + startTime.toString("dd.MM.yy"), + startTime.toString("HH:mm"), + endTime.toString("HH:mm") ) } else { icon.setCaption(null) @@ -62,12 +63,17 @@ class TraceLocationVH(parent: ViewGroup) : } } + checkinAction.isVisible = item.canCheckIn checkinAction.setOnClickListener { item.onCheckIn(item.traceLocation) } - itemView.setOnClickListener { item.onCardClicked(item.traceLocation, adapterPosition) } + itemView.apply { + setOnClickListener { item.onCardClicked(item.traceLocation, adapterPosition) } + transitionName = item.traceLocation.id.toString() + } } data class Item( val traceLocation: TraceLocation, + val canCheckIn: Boolean, val onCheckIn: (TraceLocation) -> Unit, val onDuplicate: (TraceLocation) -> Unit, val onShowPrint: (TraceLocation) -> Unit, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/poster/QrCodePosterFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/poster/QrCodePosterFragment.kt index b5840444875465bb97ae4112c546b037c8686b89..1a526d2770b001a4af2011ce29b83b7476a7eb17 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/poster/QrCodePosterFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/poster/QrCodePosterFragment.kt @@ -12,6 +12,7 @@ import androidx.core.content.getSystemService import androidx.core.widget.TextViewCompat import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.QrCodePosterFragmentBinding import de.rki.coronawarnapp.exception.ExceptionCategory @@ -44,6 +45,13 @@ class QrCodePosterFragment : Fragment(R.layout.qr_code_poster_fragment), AutoInj private val binding: QrCodePosterFragmentBinding by viewBindingLazy() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/qrinfo/TraceLocationQRInfoFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/qrinfo/TraceLocationQRInfoFragment.kt index 1474fe53afc7a68b2c02942654624992747acb58..f454e6d41507365359bd1cb60c002d432e01e4c1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/qrinfo/TraceLocationQRInfoFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/eventregistration/organizer/qrinfo/TraceLocationQRInfoFragment.kt @@ -5,6 +5,7 @@ import android.view.View import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController +import com.google.android.material.transition.MaterialSharedAxis import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.TraceLocationOrganizerQrCodeInfoFragmentBinding import de.rki.coronawarnapp.ui.eventregistration.organizer.TraceLocationOrganizerSettings @@ -26,6 +27,13 @@ class TraceLocationQRInfoFragment : Fragment(R.layout.trace_location_organizer_q @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory private val vm: TraceLocationQRInfoViewModel by cwaViewModels { viewModelFactory } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) + returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.apply { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt index 4b534fb0eda9941b9cda93f24b1e6a24c7617f6f..7b8d11a2af4cee4a59ee4afdb404fc07d4302109 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityViewModel.kt @@ -42,7 +42,7 @@ class MainActivityViewModel @AssistedInject constructor( private val mutableIsTraceLocationOnboardingDone = MutableLiveData<Boolean>() val isTraceLocationOnboardingDone: LiveData<Boolean> = mutableIsTraceLocationOnboardingDone - val activeCheckIns = checkInRepository.allCheckIns + val activeCheckIns = checkInRepository.checkInsWithinRetention .map { checkins -> checkins.filter { !it.completed }.size } .asLiveData(context = dispatcherProvider.Default) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt index 28d72a5afa6bf54b0aa7bb9dacd06d60a0dc853f..efbc89f6af264e192cf7cf28b39f953de9e2da43 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker +import de.rki.coronawarnapp.presencetracing.warning.storage.TraceWarningRepository import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.storage.OnboardingSettings @@ -57,7 +58,8 @@ class DataReset @Inject constructor( private val submissionSettings: SubmissionSettings, private val traceLocationRepository: TraceLocationRepository, private val checkInRepository: CheckInRepository, - private val traceLocationSettings: TraceLocationSettings + private val traceLocationSettings: TraceLocationSettings, + private val traceWarningRepository: TraceWarningRepository, ) { private val mutex = Mutex() @@ -96,6 +98,7 @@ class DataReset @Inject constructor( bugReportingSettings.clear() + traceWarningRepository.clear() traceLocationRepository.deleteAllTraceLocations() checkInRepository.clear() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt index 92a26a40aa5248039f8ec83ac3422ec7ea9b874a..4c30c5eb0baa94bba40db9ee9e4061f6b0237b17 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/database/CommonConverters.kt @@ -4,7 +4,9 @@ import androidx.room.TypeConverter import com.google.gson.Gson import com.google.gson.reflect.TypeToken import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import de.rki.coronawarnapp.util.serialization.fromJson +import okio.ByteString.Companion.decodeBase64 import org.joda.time.Instant import org.joda.time.LocalDate import org.joda.time.LocalTime @@ -68,4 +70,10 @@ class CommonConverters { @TypeConverter fun fromLocationCode(code: LocationCode?): String? = code?.identifier + + @TypeConverter + fun toTraceLocationId(value: String?): TraceLocationId? = value?.decodeBase64() + + @TypeConverter + fun fromTraceLocationId(traceLocationId: TraceLocationId?): String? = traceLocationId?.base64() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultSystemInfoProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultSystemInfoProvider.kt index 5a8fccf5f9f309fadafdfb33c012833a41e394ad..8712735b3f2a5a97fb687de0711533f6c5a1dd18 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultSystemInfoProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/DefaultSystemInfoProvider.kt @@ -1,12 +1,11 @@ package de.rki.coronawarnapp.util.device -import android.content.Context import android.content.res.Resources import android.os.Build import java.util.Locale import javax.inject.Inject -class DefaultSystemInfoProvider @Inject constructor(context: Context) : SystemInfoProvider { +class DefaultSystemInfoProvider @Inject constructor() : SystemInfoProvider { override val locale: Locale get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationLegalHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationLegalHelper.kt index 6a8e220b8486603651dae1ec54f7ad6f67ef1a33..72bdddee2b38640b442ac4aee9d0cb728a6690d7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationLegalHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/formatter/FormatterInformationLegalHelper.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.util.formatter -import android.content.Context import android.view.View import de.rki.coronawarnapp.ui.information.InformationLegalPresentation import de.rki.coronawarnapp.util.device.DefaultSystemInfoProvider @@ -15,10 +14,9 @@ import de.rki.coronawarnapp.util.device.DefaultSystemInfoProvider */ fun formatVisibilityLanguageBased( - context: Context, isContactFormView: Boolean? ): Int { - InformationLegalPresentation(DefaultSystemInfoProvider(context)).apply { + InformationLegalPresentation(DefaultSystemInfoProvider()).apply { if (!showBackupLinkToContactForm) { return if (isContactFormView == true) { View.VISIBLE 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 348777db1e7947374017e72c7a262459deeee0a5..0b719ddd8021c35549a621f4c051c423cdf07c26 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 @@ -95,6 +95,8 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( tracingSettings.initialPollingForTestResultTimeStamp, currentMillis ) + Timber.tag(TAG).d("Calculated days: %d", calculateDays) + if (calculateDays >= BackgroundConstants.POLLING_VALIDITY_MAX_DAYS) { Timber.tag(TAG) .d(" $id Maximum of ${BackgroundConstants.POLLING_VALIDITY_MAX_DAYS} days for polling exceeded.") diff --git a/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml b/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml index b179c9fcb9fb183b95db937bf6908e7f289ee40b..4473a4a36525220e724360fd058d988259918a9c 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_confirm_check_in.xml @@ -206,9 +206,9 @@ android:background="?android:attr/listDivider" /> <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/confirm_checkin_settings_card_checkout_time_row" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> + android:layout_height="wrap_content"> <TextView style="@style/body2" @@ -262,4 +262,4 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_edit_check_in.xml b/Corona-Warn-App/src/main/res/layout/fragment_edit_check_in.xml index 89f49d4ca8894f916057eb240bc49c8f5f93f857..c81751a1d61be0f975de25004fc941fcc2f3bb90 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_edit_check_in.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_edit_check_in.xml @@ -223,7 +223,6 @@ style="@style/Card.NoElevation" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="24dp" android:layout_marginHorizontal="@dimen/spacing_normal" android:layout_marginTop="@dimen/spacing_tiny"> @@ -234,6 +233,17 @@ android:text="@string/edit_checkin_duration_edit_hint_card_text" /> </LinearLayout> + <TextView + android:id="@+id/edit_checkin_wrong_input_warning" + style="@style/body2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="@string/edit_checkin_wrong_input_warning_text" + android:textColor="@color/colorTextSemanticRed" + android:visibility="gone" /> + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml index de95af0364cb3e978990aa01ff4e975c10982a87..95ba2919e5222e310134520cb6fb1d7e250f2d40 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_settings_tracing.xml @@ -177,6 +177,7 @@ android:id="@+id/settings_tracing_status_bluetooth" gone="@{!settingsTracingState.isBluetoothCardVisible()}" layout="@layout/include_tracing_status_card" + android:layout_marginBottom="@dimen/spacing_small" android:layout_width="0dp" android:layout_height="wrap_content" app:body="@{@string/settings_tracing_status_bluetooth_body}" @@ -193,6 +194,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:focusable="false" + android:layout_marginTop="@dimen/spacing_small" android:text="@string/risk_details_headline_period_logged" app:layout_constraintEnd_toStartOf="@id/guideline_end" app:layout_constraintStart_toStartOf="@id/guideline_start" @@ -213,7 +215,6 @@ <TextView android:id="@id/risk_details_period_logged_body_notice" style="@style/subtitle" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_normal" @@ -226,7 +227,6 @@ <TextView android:id="@+id/risk_details_period_logged_days" style="@style/subtitle" - gone="@{!settingsTracingState.isTracingStatusTextVisible()}" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_normal" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml b/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml index 8100354a0dc61451dc5d49bc5adf28237a694085..3d8399ed798a5a067a043be3e65539c5df86eccb 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_trace_location_onboarding.xml @@ -2,7 +2,8 @@ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/colorBackground"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/check_in_onboarding_toolbar" @@ -19,36 +20,36 @@ style="@style/buttonPrimary" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/trace_location_onboarding_body_confirm" android:layout_marginHorizontal="24dp" android:layout_marginBottom="24dp" + android:text="@string/trace_location_onboarding_body_confirm" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintStart_toStartOf="parent" /> <androidx.core.widget.NestedScrollView android:id="@+id/check_in_onboarding_scroll_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginBottom="20dp" - app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_toolbar" - app:layout_constraintBottom_toTopOf="@+id/check_in_onboarding_acknowledge" > + app:layout_constraintBottom_toTopOf="@+id/check_in_onboarding_acknowledge" + app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_toolbar"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content"> <ImageView android:id="@+id/check_in_onboarding_image" android:layout_width="0dp" android:layout_height="190dp" - android:src="@drawable/trace_location_onboarding" android:layout_marginHorizontal="24dp" android:layout_marginTop="4dp" android:contentDescription="@string/trace_location_onboarding_content_description" - app:layout_constraintTop_toTopOf="parent" + android:src="@drawable/trace_location_onboarding" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/check_in_onboarding_title" @@ -56,11 +57,11 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="center" - android:layout_marginTop="24dp" android:layout_marginHorizontal="24dp" + android:layout_marginTop="24dp" android:text="@string/trace_location_onboarding_title2" - app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_image" /> <TextView @@ -68,11 +69,11 @@ style="@style/subtitleMedium" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="22dp" android:layout_marginHorizontal="24dp" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" + android:layout_marginTop="22dp" android:text="@string/trace_location_onboarding_subheadline" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_title" /> <ImageView @@ -83,8 +84,8 @@ android:layout_marginTop="@dimen/spacing_large" android:importantForAccessibility="no" android:src="@drawable/ic_qr_tracing_static" - app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_subtitle" - app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_subtitle" /> <TextView android:id="@+id/check_in_onboarding_warning" @@ -93,9 +94,9 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:text="@string/trace_location_onboarding_body_warning" - app:layout_constraintTop_toTopOf="@id/check_in_warning_image" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/check_in_warning_image" - app:layout_constraintEnd_toEndOf="parent" /> + app:layout_constraintTop_toTopOf="@id/check_in_warning_image" /> <ImageView android:id="@+id/check_in_stay_image" @@ -105,8 +106,8 @@ android:layout_marginTop="@dimen/spacing_medium" android:importantForAccessibility="no" android:src="@drawable/ic_qr_time" - app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_warning" - app:layout_constraintStart_toStartOf="parent" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_warning" /> <TextView android:id="@+id/check_in_onboarding_stay" @@ -115,17 +116,17 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:text="@string/trace_location_onboarding_body_stay" - app:layout_constraintTop_toTopOf="@id/check_in_stay_image" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/check_in_warning_image" - app:layout_constraintEnd_toEndOf="parent" /> + app:layout_constraintTop_toTopOf="@id/check_in_stay_image" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/check_in_onboarding_card_container" style="@style/cardTracing" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="24dp" android:layout_marginHorizontal="24dp" + android:layout_marginTop="24dp" android:background="#F5F5F5" android:orientation="vertical" app:layout_constraintEnd_toEndOf="parent" @@ -146,20 +147,20 @@ style="@style/subtitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:text="@string/trace_location_onboarding_body_intro" android:layout_marginTop="4dp" - app:layout_constraintStart_toStartOf="parent" + android:text="@string/trace_location_onboarding_body_intro" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_card_title" /> <ImageView android:id="@+id/check_in_onboarding_bulletpoint1" android:layout_width="8dp" android:layout_height="8dp" - android:src="@drawable/bullet_point" android:layout_marginTop="24dp" - app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_card_subtitle" - app:layout_constraintStart_toStartOf="parent" /> + android:src="@drawable/bullet_point" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_card_subtitle" /> <TextView android:id="@+id/check_in_onboarding_body2" @@ -169,8 +170,8 @@ android:layout_marginStart="15dp" android:layout_marginTop="16dp" android:focusable="true" - android:textStyle="bold" android:text="@string/trace_location_onboarding_body_consent2" + android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/check_in_onboarding_bulletpoint1" app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_card_subtitle" /> @@ -192,10 +193,10 @@ android:id="@+id/check_in_onboarding_bulletpoint2" android:layout_width="8dp" android:layout_height="8dp" - android:src="@drawable/bullet_point" android:layout_marginTop="22dp" - app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body3" - app:layout_constraintStart_toStartOf="parent" /> + android:src="@drawable/bullet_point" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body3" /> <TextView android:id="@+id/check_in_onboarding_body4" @@ -205,8 +206,8 @@ android:layout_marginStart="15dp" android:layout_marginTop="16dp" android:focusable="true" - android:textStyle="bold" android:text="@string/trace_location_onboarding_body_consent4" + android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/check_in_onboarding_bulletpoint2" app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body3" /> @@ -215,10 +216,10 @@ android:id="@+id/check_in_onboarding_bulletpoint3" android:layout_width="8dp" android:layout_height="8dp" - android:src="@drawable/bullet_point" android:layout_marginTop="22dp" - app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body4" - app:layout_constraintStart_toStartOf="parent" /> + android:src="@drawable/bullet_point" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body4" /> <TextView android:id="@+id/check_in_onboarding_body5" @@ -228,8 +229,8 @@ android:layout_marginStart="15dp" android:layout_marginTop="16dp" android:focusable="true" - android:textStyle="bold" android:text="@string/trace_location_onboarding_body_consent5" + android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/check_in_onboarding_bulletpoint3" app:layout_constraintTop_toBottomOf="@id/check_in_onboarding_body4" /> @@ -255,8 +256,8 @@ android:layout_marginHorizontal="24dp" android:layout_marginTop="30dp" android:focusable="true" - app:titleText="@string/contact_diary_onboarding_legal_information" - app:layout_constraintTop_toBottomOf="@+id/check_in_onboarding_card_container"/> + app:layout_constraintTop_toBottomOf="@+id/check_in_onboarding_card_container" + app:titleText="@string/contact_diary_onboarding_legal_information" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.core.widget.NestedScrollView> diff --git a/Corona-Warn-App/src/main/res/layout/include_contact_form.xml b/Corona-Warn-App/src/main/res/layout/include_contact_form.xml index 0f3654c19de12d7e5f914941da5ed8361c447bc5..0af8211cc6d358a0b26c03d88cbfbc6cf44749e9 100644 --- a/Corona-Warn-App/src/main/res/layout/include_contact_form.xml +++ b/Corona-Warn-App/src/main/res/layout/include_contact_form.xml @@ -28,7 +28,7 @@ android:linksClickable="true" android:text="@string/information_legal_subtitle_contact_label" android:textColorLink="@color/colorTextTint" - android:visibility="@{FormatterInformationLegalHelper.formatVisibilityLanguageBased(context, true)}" + android:visibility="@{FormatterInformationLegalHelper.formatVisibilityLanguageBased(true)}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> @@ -42,7 +42,7 @@ android:linksClickable="true" android:text="@string/information_legal_subtitle_contact_form_non_en_de" android:textColorLink="@color/colorTextTint" - android:visibility="@{FormatterInformationLegalHelper.formatVisibilityLanguageBased(context, false)}" + android:visibility="@{FormatterInformationLegalHelper.formatVisibilityLanguageBased(false)}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/information_legal_contact_form" /> diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml index 0d86c8e3555782de8c1716a1d36f5ea1d37e271a..1e665b70bab77733c13522cf12757475f33c3306 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_fragment.xml @@ -4,7 +4,8 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/content_container" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/colorBackground"> <com.google.android.material.appbar.MaterialToolbar android:id="@+id/toolbar" @@ -58,6 +59,7 @@ android:layout_marginTop="36dp" android:text="@string/trace_location_checkins_empty_label" android:textStyle="bold" /> + <TextView style="@style/body2Medium" android:layout_width="wrap_content" diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_active.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_active.xml index b7d630bc5d2fbd3dc967a92b8ef92b176fb8220b..af99c65414025eef1cc19e0c7a24f7952a6df4ce 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_active.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_active.xml @@ -5,8 +5,8 @@ style="@style/contactDiaryCardRipple" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginVertical="8dp" android:layout_marginHorizontal="16dp" + android:layout_marginVertical="8dp" android:focusable="true"> <de.rki.coronawarnapp.ui.eventregistration.common.TraceLocationCardHighlightView @@ -17,10 +17,8 @@ android:layout_marginTop="16dp" android:layout_marginBottom="8dp" android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@+id/checkout_action" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:text="21.01.2021"> + app:layout_constraintTop_toTopOf="parent"> <TextView android:id="@+id/highlight_duration_label" @@ -38,6 +36,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" /> + <TextView android:id="@+id/highlight_duration" style="@style/headline5Bold" @@ -91,8 +90,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@id/traceLocationCardHighlightView" app:layout_constraintTop_toBottomOf="@id/address" - app:layout_constraintVertical_bias="0.0" - tools:text="18:00 - Automatisch auschecken nach 3 Std." /> + tools:text="21.01.2021 18:00 - Automatisch auschecken nach 3 Std." /> <ImageButton android:id="@+id/menu_action" diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_camera.xml b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_camera.xml index 35b62a8bd4b8fcdc11117edfdb277bb9800574b5..955e46ce2a15b69e5df49207f30728cde5a1e351 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_camera.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_attendee_checkins_item_camera.xml @@ -8,10 +8,7 @@ android:layout_marginHorizontal="16dp" android:layout_marginVertical="8dp" android:focusable="true" - android:paddingStart="24dp" - android:paddingTop="32dp" - android:paddingEnd="24dp" - android:paddingBottom="24dp"> + android:padding="16dp"> <TextView android:id="@+id/title" @@ -33,7 +30,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/title" /> - + <Button android:id="@+id/open_settings" style="@style/buttonPrimary" diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml index 58782363908fa6bf30e610630d4ad2c63b2b6fcc..9396dc1576dfc32de53f628d19d795c828a87d19 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_detail_fragment.xml @@ -6,8 +6,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/trace_location_gradient_background" - android:contentDescription="@string/trace_location_event_detail_title_accessibility" - android:transitionName="trace_location_container_transition"> + android:contentDescription="@string/trace_location_event_detail_title_accessibility"> <androidx.coordinatorlayout.widget.CoordinatorLayout android:id="@+id/coordinator_layout" @@ -61,9 +60,10 @@ <TextView android:id="@+id/subtitle" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" + android:layout_marginHorizontal="24dp" android:layout_marginBottom="8dp" android:gravity="center" android:textColor="@android:color/white" diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml index 8cafcb270f82400a7b370d53a0977e87eefefa7c..594d4846fbd20bf048ddf06d8c1f6a14c415bad9 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_qr_code_info_fragment.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:contentDescription="@string/tracelocation_organizer_category_title" + android:background="@color/colorBackground" tools:context=".ui.eventregistration.organizer.qrinfo.TraceLocationQRInfoFragment"> <com.google.android.material.appbar.MaterialToolbar @@ -133,7 +134,7 @@ android:text="@string/trace_location_qr_info_time_text" app:layout_constraintTop_toTopOf="@id/trace_location_qr_time_sheet_icon" app:layout_constraintStart_toEndOf="@id/trace_location_qr_time_sheet_icon" - app:layout_constraintEnd_toEndOf="parent"/> + app:layout_constraintEnd_toEndOf="parent" /> <include android:id="@+id/privacy_card" @@ -160,7 +161,7 @@ app:layout_constraintTop_toBottomOf="@id/privacy_card" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintBottom_toBottomOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml index 8434f98f0f523dbcd52056d8547b74c966ebb31b..41503487bbafe2aa59352e6ad1e2056e3615f754 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_item.xml @@ -17,8 +17,7 @@ android:layout_marginTop="16dp" android:layout_marginBottom="8dp" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - tools:text="21.01.2021"> + app:layout_constraintTop_toTopOf="parent"> <ImageView android:layout_width="42dp" @@ -85,6 +84,11 @@ app:barrierDirection="bottom" app:constraint_referenced_ids="address,duration,icon" /> + <Space + android:layout_width="match_parent" + android:layout_height="16dp" + app:layout_constraintTop_toBottomOf="@id/button_barrier" /> + <com.google.android.material.button.MaterialButton android:id="@+id/checkin_action" style="@style/Widget.MaterialComponents.Button.OutlinedButton" diff --git a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_list_fragment.xml b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_list_fragment.xml index 2c3516cab07bf0aa2e3e00d29b07c8fdc6ab8556..7cc778f51698899505c2beef24786640fd99ec0a 100644 --- a/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_list_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/trace_location_organizer_trace_locations_list_fragment.xml @@ -5,6 +5,7 @@ android:id="@+id/content_container" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/colorBackground" android:focusable="true"> <com.google.android.material.appbar.MaterialToolbar diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml index c18fabd786e88c731fbc6114c590a0884e54e11a..47d969b248ad4f16080a27e47cba8e25ecc04fb5 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_details_item_riskdetails_low_view.xml @@ -60,6 +60,7 @@ android:clickable="true" android:focusable="true" android:linksClickable="true" + tools:visibility="visible" android:visibility="gone" android:text="@string/risk_details_explanation_dialog_faq_body" app:layout_constraintEnd_toEndOf="parent" diff --git a/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml index cb34d67595e1f9efb9d3d33b0cce121e8e26c77b..bbf7abe775c1afe85f8994a4eba64e519d054a59 100644 --- a/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/trace_location_attendee_nav_graph.xml @@ -9,6 +9,7 @@ android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.onboarding.CheckInOnboardingFragment" android:label="CheckInOnboardingFragment" tools:layout="@layout/fragment_trace_location_onboarding"> + <deepLink app:uri="coronawarnapp://check-ins/{uri}" /> <action android:id="@+id/action_checkInOnboardingFragment_to_checkInsFragment" app:destination="@id/checkInsFragment" @@ -21,6 +22,11 @@ android:name="showBottomNav" android:defaultValue="true" app:argType="boolean" /> + <argument + android:name="uri" + android:defaultValue="@null" + app:argType="string" + app:nullable="true" /> </fragment> <fragment android:id="@+id/checkInPrivacyFragment" @@ -56,7 +62,6 @@ android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.checkins.CheckInsFragment" android:label="CheckInsFragment" tools:layout="@layout/trace_location_attendee_checkins_fragment"> - <deepLink app:uri="coronawarnapp://check-ins/{uri}" /> <action android:id="@+id/action_checkInsFragment_to_scanCheckInQrCodeFragment" app:destination="@id/scanCheckInQrCodeFragment" /> diff --git a/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml index aa52e6cb304971cded06b59023927f8fe76811eb..ae96af2c68e982128fd1f41093a65f7e5f34bbfa 100644 --- a/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/trace_location_organizer_nav_graph.xml @@ -82,6 +82,9 @@ <action android:id="@+id/action_qrCodeDetailFragment_to_qrCodePosterFragment" app:destination="@id/qrCodePosterFragment" /> + <action + android:id="@+id/action_qrCodeDetailFragment_to_traceLocationCreateFragment" + app:destination="@id/traceLocationCreateFragment" /> </fragment> <fragment diff --git a/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml index 24439e7a46fc462d778b60cb7ade0e48eb4cda1b..b3079ef9d7a5dff1c548e034dc68d049a34d8522 100644 --- a/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/contact_diary_strings.xml @@ -76,6 +76,13 @@ <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"въз оÑнова на повече от едно излагане на ниÑък риÑк."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body_extended">"Ðе е задължително те да Ñа Ñвързани Ñ Ð¾Ñ‚Ð±ÐµÐ»Ñзаните от Ð’Ð°Ñ Ñ…Ð¾Ñ€Ð° и меÑта."</string> + <!-- XTXT: Body for contact diary overview screen trace location risk information prelude --> + <string name="contact_diary_trace_location_risk_body">"въз оÑнова на приÑÑŠÑтвието им в:"</string> + <!-- XTXT: Indicates this trace location caused a high risk --> + <string name="contact_diary_trace_location_risk_high">"повишен риÑк"</string> + <!-- XTXT: Indicates this trace location caused a lows risk --> + <string name="contact_diary_trace_location_risk_low">"ниÑък риÑк"</string> + <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/main/res/values-bg/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-bg/event_registration_strings.xml index 1193e91a4727e0d30c9a030d836b20eb6a029097..8ca317a1a4d95c44a95798c7a7ebb8572d2cb6aa 100644 --- a/Corona-Warn-App/src/main/res/values-bg/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/event_registration_strings.xml @@ -135,6 +135,33 @@ <string name="create_trace_location_card_subtitle">"Планирате Ñъбитие или ръководите бизнеÑ? Създайте QR код, за да могат поÑетителите да Ñе региÑтрират, когато приÑтигнат."</string> <!-- XTXT: Text for trace location creation card --> <string name="create_trace_location_card_button">"Създаване на QR код"</string> + <!-- XTXT: Text for content description of tile image --> + <string name="create_trace_location_card_content_description">"Eдин човек Ñочи към флипчарт."</string> + + <!-- Trace Location QR Code Info Screen--> + <!-- XTXT: Text for content description of info screen image --> + <string name="trace_location_qr_info_content_description">"Eдин човек поÑочва флипчарт, а други двама ÑедÑÑ‚ в близоÑÑ‚ един до друг и гледат към него."</string> + <!-- XHED: Headline for trace location creation card --> + <string name="trace_location_qr_info_headline_text">"Повече безопаÑноÑÑ‚ за Ð’Ð°Ñ Ð¸ Вашите поÑетители"</string> + <!-- XTXT: Text for subtitle of qr info screen --> + <string name="trace_location_qr_info_subtitle_text">"Създайте QR код, който поÑетителите Ви могат да Ñканират при приÑтигането Ñи. Това ще им позволи при необходимоÑÑ‚.да предупредÑÑ‚ оÑтаналите поÑетители или да бъдат предупредени от Ñ‚ÑÑ…."</string> + <!-- XTXT: Text for tracing icon of qr info screen --> + <string name="trace_location_qr_info_tracing_text">"Ð’ÑÑка региÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ñе взема предвид Ñъщо и при изчиÑлÑване на нивото на риÑка. При поÑтавÑне на диагноза ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ Ð½Ð° лице, което Ñе е региÑтрирало от дадено мÑÑто или Ñъбитие, ще бъдат уведомени вÑички оÑтанали поÑетители, които по Ñъщото време или в рамките на 30 минути Ñлед това Ñа Ñе намирали в едно и Ñъщо помещение Ñ Ð½ÐµÐ³Ð¾."</string> + <!-- XTXT: Text for qr icon of qr info screen --> + <string name="trace_location_qr_info_qr_code_text">"Можете да предоÑтавите QR кода на поÑетителите Ñи от Ñмартфон или отпечатан на хартиÑ."</string> + <!-- XTXT: Text for time sheet icon of qr info screen --> + <string name="trace_location_qr_info_time_text">"Ðко желаете да използвате QR код дългоÑрочно, Ñ‚Ñ€Ñбва да генерирате такъв ежедневно извън работно време."</string> + <!-- XTXT: Text for I understand button --> + <string name="acknowledge_button">"Ðапред"</string> + + <!-- XHED: Title of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_title">"РегиÑтрации"</string> + <!-- XTXT: Description of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_description">"ИзвеÑÑ‚Ð¸Ñ Ð·Ð° региÑтрациите Ви, като автоматично прекратÑване на региÑтрациÑта."</string> + <!-- XHED: Title of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_title">"РегиÑтрациÑта Ви беше прекратена автоматично."</string> + <!-- XTXT: Description of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_description">"МолÑ, коригирайте продължителноÑтта на преÑÑ‚Ð¾Ñ Ñи, ако е необходимо."</string> <!-- #################################### Event Organiser Trace Locations List @@ -173,6 +200,11 @@ <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">"РегиÑтрациÑ"</string> + <!-- XTXT: Event organizer detail qr-code: duration with date --> + <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s"</string> + <!-- XTXT: Event organizer detail qr-code: duration with multiple date --> + <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s"</string> + <!-- XBUT: Event organiser list item: menu: information button --> <string name="trace_location_organizer_list_item_menu_duplicate_btn">"Дублиране"</string> <!-- XBUT: Event organiser list item: menu: show print button --> @@ -180,12 +212,42 @@ <!-- XBUT: Event organiser list item: menu: clear button --> <string name="trace_location_organizer_list_item_menu_clear_btn">"Изтриване"</string> + <!-- Trace Location Checkin Confirmation --> + <!-- XHED: Checkin Confirm: title for screen --> + <string name="confirm_checkin_title_text">"РегиÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð·Ð°:"</string> + <!-- XTXT: Checkin Confirm: text for event is in future info card --> + <string name="confirm_checkin_event_in_future_card_text">"Това Ñъбитие започва в %2$s на %1$s. Желаете ли да Ñе региÑтрирате още Ñега?"</string> + <!-- XTXT: Checkin Confirm: text for event is in past info card --> + <string name="confirm_checkin_event_in_past_card_text">"Това Ñъбитие вече е отминало. Желаете ли да Ñе региÑтрирате ÑÑŠÑ Ð·Ð°Ð´Ð½Ð° дата?"</string> + <!-- XTXT: Checkin Confirm: label for save to contact diary toggle --> + <string name="confirm_checkin_settings_card_checkout_toggle_label">"Да Ñе Ñъздаде ли Ð·Ð°Ð¿Ð¸Ñ Ð² дневника на контактите Ñлед прекратÑване на региÑтрациÑта?"</string> + <!-- XTXT: Checkin Confirm: label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_label">"Ðвтоматично прекратÑване на региÑтрациÑта Ñлед"</string> + <!-- XTXT: Checkin Confirm: time length label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_tag">"ч"</string> + <!-- XBUT: Checkin Confirm: Checkin Button --> + <string name="confirm_checkin_confirm_button_text">"РегиÑтрациÑ"</string> + + <!-- Trace Location Checkin Editing --> + <!-- XHED: Checkin Edit: title for screen --> + <string name="edit_checkin_title_text">"ПромÑна на продължителноÑтта на преÑтой за:"</string> + <!-- XTXT: Checkin Edit: label for checkout time --> + <string name="edit_checkin_edit_card_checkout_time_label">"РегиÑтрирано напуÑкане"</string> + <!-- XTXT: Checkin Edit: label for checkin time --> + <string name="edit_checkin_edit_card_checkin_time_label">"РегиÑтрирано приÑтигане"</string> + <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> + <string name="edit_checkin_duration_edit_hint_card_text">"ПродължителноÑтта на преÑÑ‚Ð¾Ñ Ð½Ñма да бъде коригирана автоматично във Ð²Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº на контактите."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"Може да оÑтанете региÑтрирани в рамките на 24 чаÑа. ЧаÑÑŠÑ‚ на прекратÑване на региÑтрирането Ñ‚Ñ€Ñбва да е по-къÑен от този на региÑтрирането."</string> + <!-- XBUT: Checkin Edit: Save Button --> + <string name="edit_checkin_confirm_button_text">"Запазване"</string> + <!-- Organizer Flow: Event Detail Screen--> <!-- XTXT: Organizer Flow : Accessibility description for qr-code illustration --> <string name="trace_location_event_detail_qr_code_accessibility">"QR код"</string> <!-- XHED: Organizer Flow : Accessibility title for event-overview --> <string name="trace_location_event_detail_title_accessibility">"Преглед на Ñъбитие"</string> - !-- XBUT: Organizer Flow : Title for show print version button --> + <!-- XBUT: Organizer Flow : Title for show print version button --> <string name="trace_location_event_detail_show_print_qr_code_button">"Показване на верÑÐ¸Ñ Ð·Ð° печат"</string> <!-- XBUT: Organizer Flow : Title for save as template button --> <string name="trace_location_event_detail_save_as_template_button">"Използване като шаблон"</string> diff --git a/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml index 8d48b68c7db7a875f359bf2c7fcba5f59c9bccbd..a1cbf8e4d5a5715198514f33ef5bb6d4876aecbf 100644 --- a/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml @@ -21,7 +21,7 @@ <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"CWA Ви позволÑва да Ñе региÑтрирате, когато приÑтигате на определени меÑта и ÑъбитиÑ. Организаторите на ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¸ ÑобÑтвениците на магазини могат да използват приложението за Ñъздаване на QR код, който поÑетителите да Ñканират, когато приÑтигат, за да региÑтрират приÑÑŠÑтвието Ñи. При поиÑкване приложението може Ñъщо да Ñъздава Ð·Ð°Ð¿Ð¸Ñ Ð² дневника. Ðко на нÑкой от поÑетителите на Ñъответното мÑÑто или Ñъбитие бъде поÑтавена диагноза коронавируÑ, вÑички лица, които Ñа Ñе региÑтрирали на Ñъщото мÑÑто по Ñъщото време, ще бъдат уведомени."</item> + <item>"CWA Ви позволÑва да Ñе региÑтрирате, когато приÑтигате на определени меÑта и ÑъбитиÑ. Организаторите на ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¸ ÑобÑтвениците на магазини могат да използват приложението за Ñъздаване на QR код, който поÑетителите да Ñканират, когато приÑтигат, за да региÑтрират приÑÑŠÑтвието Ñи. При поиÑкване приложението може Ñъщо да Ñъздава Ð·Ð°Ð¿Ð¸Ñ Ð² дневника. Ðко на нÑкой от поÑетителите на Ñъответното мÑÑто или Ñъбитие бъде поÑтавена диагноза коронавируÑ, други лица, които Ñа Ñе региÑтрирали на Ñъщото мÑÑто по Ñъщото време, може да бъдат предупредени автоматично."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> diff --git a/Corona-Warn-App/src/main/res/values-bg/strings.xml b/Corona-Warn-App/src/main/res/values-bg/strings.xml index 59084387a6fcf9573020c7feca7c97253c26b34d..646797d6076689e0bb7ec0078da5c49ab7d18f6e 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -30,6 +30,10 @@ <!-- #################################### Notification ###################################### --> + <!-- XHED: Title of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_title">"Общи"</string> + <!-- XTXT: Description of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_description">"ÐапомнÑниÑ, бележки и общи извеÑÑ‚Ð¸Ñ - отноÑно ÑтатуÑа Ви на риÑк, например."</string> <!-- XHED: Notification title --> <string name="notification_headline">"Corona-Warn-App"</string> <!-- XTXT: Notification body --> @@ -1793,13 +1797,13 @@ <string name="statistics_reproduction_title">"Ð’ момента"</string> <!-- XTXT: Caption for statistics reproduction card --> <string name="statistics_reproduction_average">"Ñреден брой заразени от вÑÑко заразено лице"</string> - <!-- XTXT: Statistics trend increasing (Accessibilty) --> <string name="statistics_trend_increasing">"ТенденциÑ: Ðагоре"</string> <!-- XTXT: Statistics trend decreasing (Accessibilty) --> <string name="statistics_trend_decreasing">"ТенденциÑ: Ðадолу"</string> <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">"ТенденциÑ: Стабилизиране"</string> + <!-- XHED: Title for BottomNav main screen title --> <string name="bottom_nav_home_title">"Ðачален екран"</string> <!-- XHED: Title for BottomNav diary screen title --> @@ -1955,4 +1959,18 @@ <string name="trace_location_onboarding_body_stay">"МолÑ, отбележете продължителноÑтта на преÑÑ‚Ð¾Ñ Ñи възможно най-точно, за да може да получите адекватни предупреждениÑ."</string> <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">"Приемам"</string> + <!-- XHED: Trace location check-ins camera permission card title--> + <string name="trace_location_attendee_camera_card_title">"Разрешаване на доÑтъп до камерата"</string> + <!-- XTXT: Trace location check-ins camera permission card susbtitle--> + <string name="trace_location_attendee_camera_card_subtitle">"За да използвате камерата на телефона Ñи за Ñканиране на QR кодове, Ñ‚Ñ€Ñбва да разрешите на приложението да получи доÑтъп до Ð½ÐµÑ Ð¾Ñ‚ наÑтройките на уÑтройÑтвото Ñи."</string> + <!-- XBUT: Trace location check-ins camera permission card button--> + <string name="trace_location_attendee_camera_card_button">"Отворете менюто за наÑтройки"</string> + <!-- YMSG: Trace location onboarding image description--> + <string name="trace_location_onboarding_content_description">"Трима души ÑтоÑÑ‚ до виÑока маÑа и двама от Ñ‚ÑÑ… гледат телефоните Ñи."</string> + <!-- XMIT: Trace location poster print --> + <string name="trace_location_organiser_poster_print">"Печат"</string> + <!-- XMIT: Trace location poster share --> + <string name="trace_location_organiser_poster_share">"СподелÑне"</string> + <!-- XHED: Trace location poster title --> + <string name="trace_location_organiser_poster_title">"ВерÑÐ¸Ñ Ð·Ð° печат"</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml index 2c21432140c2dd68198ee608f07da3079797bd9e..732c3f2c57841116a81ebae569f4bdc3f7defcef 100644 --- a/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/event_registration_strings.xml @@ -43,6 +43,8 @@ <string name="trace_location_checkins_card_highlight_duration_label">Dauer</string> <!-- XTXT: My check-ins card: Active event, checkin information, automatic checkout info --> <string name="trace_location_checkins_card_automatic_checkout_info">"%1$s - Automatisch auschecken nach %2$s."</string> + <!-- XTXT: My check-ins card: Active event, checkin information, automatic checkout info --> + <string name="trace_location_checkins_card_automatic_checkout_info_format">"%1$s %2$s - Automatisch auschecken nach %3$s."</string> <!-- XHED: Title of the category list screen of the event creation --> <string name="tracelocation_organizer_category_title">QR-Code erstellen</string> @@ -185,7 +187,7 @@ <!-- XHED: Event organiser qr codes list: delete single popup title --> <string name="trace_location_organiser_list_delete_single_popup_title">"Wollen Sie den QR-Code wirklich entfernen?"</string> <!-- XTXT: Event organiser qr codes list: delete single popup message --> - <string name="trace_location_organiser_list_delete_single_popup_message">"Bitte denken Sie daran, dass der QR-Code “Jahrestreffen der deutschen SAP Anwendergruppe†danach nicht mehr zum Einchecken verwendet werden kann."</string> + <string name="trace_location_organiser_list_delete_single_popup_message">"Bitte denken Sie daran, dass der QR-Code danach nicht mehr zum Einchecken verwendet werden kann."</string> <!-- XHED: Event organiser qr codes list: delete all popup title --> <string name="trace_location_organiser_list_delete_all_popup_title">"Wollen Sie wirklich alle QR-Codes entfernen?"</string> @@ -198,6 +200,10 @@ <!-- XTXT: Event organiser list item: duration--> <string name="trace_location_organizer_list_item_duration">"%1$s - %2$s Uhr"</string> + + <!-- XTXT: Event organiser list item: duration same day--> + <string name="trace_location_organizer_list_item_duration_same_day">"%1$s %2$s - %3$s Uhr"</string> + <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">Selbst einchecken</string> @@ -238,6 +244,8 @@ <string name="edit_checkin_edit_card_checkin_time_label">"Eingecheckt"</string> <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> <string name="edit_checkin_duration_edit_hint_card_text">"Die Aufenthaltsdauer wird nicht automatisch in Ihrem Kontakt-Tagebuch angepasst."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"Sie können für maximal 24 Stunden eingecheckt sein und die Auscheck-Zeit muss nach der Eincheck-Zeit liegen."</string> <!-- XBUT: Checkin Edit: Save Button --> <string name="edit_checkin_confirm_button_text">"Speichern"</string> 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 1c44b7cb590f80e1f6ec25b83366bbf828c6e603..74344fff12023323d98b03dfa9982c49bac570a5 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -1974,7 +1974,7 @@ <!-- YMSG: Onboarding trace location warning --> <string name="trace_location_onboarding_body_warning">Jeder Check-in wird bei der Ermittlung des Risikostatus zusätzlich berücksichtigt. Wird eine eingecheckte Person später positiv getestet, können Sie gewarnt werden, wenn Sie sich zur gleichen Zeit oder bis zu 30 Minuten nach der positiv getesteten Person im gleichen Raum aufgehalten haben.</string> <!-- YMSG: Onboarding trace location warning --> - <string name="trace_location_onboarding_body_stay">Bitte geben Sie Ihre Check-in- und Check-out-Zeit so genau wie möglich an, damit gezielt gewarnt werden kann.</string> + <string name="trace_location_onboarding_body_stay">Bitte geben Sie Ihre Aufenthaltszeit so genau wie möglich an, damit gezielt gewarnt werden kann.</string> <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">Einverstanden</string> <!-- XHED: Trace location check-ins camera permission card title--> diff --git a/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml index 44bcb0bcc8a0945703e533219caa962beefce532..97bab3bbeda1f7ab22d67db2b520de5abd020266 100644 --- a/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/contact_diary_strings.xml @@ -76,6 +76,13 @@ <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"based on multiple exposures with low risk."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body_extended">"They are not necessarily related to the people and places you have recorded."</string> + <!-- XTXT: Body for contact diary overview screen trace location risk information prelude --> + <string name="contact_diary_trace_location_risk_body">"based on their presence at:"</string> + <!-- XTXT: Indicates this trace location caused a high risk --> + <string name="contact_diary_trace_location_risk_high">"increased risk"</string> + <!-- XTXT: Indicates this trace location caused a lows risk --> + <string name="contact_diary_trace_location_risk_low">"low risk"</string> + <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/main/res/values-en/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-en/event_registration_strings.xml index ecc733f2aed79d13c087be1f0096e7c8a8ca1753..56f2df42950a06ceec76d37f81b3c7309d73c445 100644 --- a/Corona-Warn-App/src/main/res/values-en/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/event_registration_strings.xml @@ -135,6 +135,33 @@ <string name="create_trace_location_card_subtitle">"Are you planning an event or do you run a business? Create a QR code so your guests can check in when they arrive."</string> <!-- XTXT: Text for trace location creation card --> <string name="create_trace_location_card_button">"Create QR Code"</string> + <!-- XTXT: Text for content description of tile image --> + <string name="create_trace_location_card_content_description">"A person points to a flip chart."</string> + + <!-- Trace Location QR Code Info Screen--> + <!-- XTXT: Text for content description of info screen image --> + <string name="trace_location_qr_info_content_description">"One person points to a flip chart, while two people are sitting adjacent and looking at the flip chart."</string> + <!-- XHED: Headline for trace location creation card --> + <string name="trace_location_qr_info_headline_text">"Increased Safety for You and Your Guests"</string> + <!-- XTXT: Text for subtitle of qr info screen --> + <string name="trace_location_qr_info_subtitle_text">"Create a QR code that your guests can scan when they arrive. This will enable them to warn other guests or be warned by them if necessary."</string> + <!-- XTXT: Text for tracing icon of qr info screen --> + <string name="trace_location_qr_info_tracing_text">"Every check-in is also taken into account when calculating the risk status. If someone who checked in to this event or place is diagnosed with coronavirus later, the guests can be notified if they were in the same room at the same time or up to 30 minutes after the diagnosed person."</string> + <!-- XTXT: Text for qr icon of qr info screen --> + <string name="trace_location_qr_info_qr_code_text">"You can provide the QR code to your guests on your smartphone or as a printed form."</string> + <!-- XTXT: Text for time sheet icon of qr info screen --> + <string name="trace_location_qr_info_time_text">"If you want to use a QR code for the long term, you should generate it again each day, outside of your operating hours."</string> + <!-- XTXT: Text for I understand button --> + <string name="acknowledge_button">"Next"</string> + + <!-- XHED: Title of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_title">"Check-Ins"</string> + <!-- XTXT: Description of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_description">"Notifications about your check-ins, such as automatic check-out."</string> + <!-- XHED: Title of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_title">"You were checked out automatically."</string> + <!-- XTXT: Description of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_description">"Please adjust the length of your stay if needed."</string> <!-- #################################### Event Organiser Trace Locations List @@ -173,6 +200,11 @@ <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">"Check In"</string> + <!-- XTXT: Event organizer detail qr-code: duration with date --> + <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s"</string> + <!-- XTXT: Event organizer detail qr-code: duration with multiple date --> + <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s"</string> + <!-- XBUT: Event organiser list item: menu: information button --> <string name="trace_location_organizer_list_item_menu_duplicate_btn">"Duplicate"</string> <!-- XBUT: Event organiser list item: menu: show print button --> @@ -180,6 +212,36 @@ <!-- XBUT: Event organiser list item: menu: clear button --> <string name="trace_location_organizer_list_item_menu_clear_btn">"Delete"</string> + <!-- Trace Location Checkin Confirmation --> + <!-- XHED: Checkin Confirm: title for screen --> + <string name="confirm_checkin_title_text">"Check in for:"</string> + <!-- XTXT: Checkin Confirm: text for event is in future info card --> + <string name="confirm_checkin_event_in_future_card_text">"This event doesn’t start until %2$s on %1$s. Do you want to check in already?"</string> + <!-- XTXT: Checkin Confirm: text for event is in past info card --> + <string name="confirm_checkin_event_in_past_card_text">"This event is already over. Do you want to check in retroactively?"</string> + <!-- XTXT: Checkin Confirm: label for save to contact diary toggle --> + <string name="confirm_checkin_settings_card_checkout_toggle_label">"Record in my contact journal after check-out?"</string> + <!-- XTXT: Checkin Confirm: label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_label">"Check out automatically after"</string> + <!-- XTXT: Checkin Confirm: time length label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_tag">"hrs"</string> + <!-- XBUT: Checkin Confirm: Checkin Button --> + <string name="confirm_checkin_confirm_button_text">"Check In"</string> + + <!-- Trace Location Checkin Editing --> + <!-- XHED: Checkin Edit: title for screen --> + <string name="edit_checkin_title_text">"Change length of stay for:"</string> + <!-- XTXT: Checkin Edit: label for checkout time --> + <string name="edit_checkin_edit_card_checkout_time_label">"Checked Out"</string> + <!-- XTXT: Checkin Edit: label for checkin time --> + <string name="edit_checkin_edit_card_checkin_time_label">"Checked In"</string> + <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> + <string name="edit_checkin_duration_edit_hint_card_text">"The length of stay will not be adjusted automatically in your contact journal."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"You can remain checked in for up to 24 hours. The check-out time must be later than the check-in time."</string> + <!-- XBUT: Checkin Edit: Save Button --> + <string name="edit_checkin_confirm_button_text">"Save"</string> + <!-- Organizer Flow: Event Detail Screen--> <!-- XTXT: Organizer Flow : Accessibility description for qr-code illustration --> <string name="trace_location_event_detail_qr_code_accessibility">"QR Code"</string> diff --git a/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml index 6ad6582b2b85950b84f3b80eeee64dbcafcb1c1e..1fccdbaa35212bf36afc13952d4bb11e59e7dc6f 100644 --- a/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml @@ -21,7 +21,7 @@ <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"The CWA now enables you to check in to events and places. Event organizers and store owners can use the app to create a QR code. Guests can then scan this QR code when they arrive to register their presence. The app can also create a journal entry upon request. If someone who checked in to this event or place is diagnosed with coronavirus later, everyone who was checked in at the same time is notified automatically."</item> + <item>"The CWA now enables you to check in to events and places. Event organizers and store owners can use the app to create a QR code. Guests can then scan this QR code when they arrive to register their presence. The app can also create a journal entry upon request. If someone who checked in to this event or place is diagnosed with coronavirus later, other people who were checked in at the same time can be warned automatically."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> diff --git a/Corona-Warn-App/src/main/res/values-en/strings.xml b/Corona-Warn-App/src/main/res/values-en/strings.xml index db8c8318742a4bde8a5bfaf3249e296224e83bd0..15aa30d87239bd87eb73419ef462a17123181184 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -30,6 +30,10 @@ <!-- #################################### Notification ###################################### --> + <!-- XHED: Title of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_title">"General"</string> + <!-- XTXT: Description of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_description">"Reminders, notes, and general notifications – about your risk status, for example."</string> <!-- XHED: Notification title --> <string name="notification_headline">"Corona-Warn-App"</string> <!-- XTXT: Notification body --> @@ -301,9 +305,9 @@ <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently. Exposure logging covers the last 14 days."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> - <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s days ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> - <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active in times during which you encountered other people, your risk of infection can be calculated for this period."</string> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active at times during which you encountered other people, your risk of infection can be calculated for this period."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment">"The app automatically deletes older logs, as these are no longer relevant for infection prevention."</string> <!-- XTXT: risk details - infection period days logged/14 --> @@ -1793,13 +1797,13 @@ <string name="statistics_reproduction_title">"Currently"</string> <!-- XTXT: Caption for statistics reproduction card --> <string name="statistics_reproduction_average">"average infections by each infected person"</string> - <!-- XTXT: Statistics trend increasing (Accessibilty) --> <string name="statistics_trend_increasing">"Trend: Upwards"</string> <!-- XTXT: Statistics trend decreasing (Accessibilty) --> <string name="statistics_trend_decreasing">"Trend: Downwards"</string> <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">"Trend: Steady"</string> + <!-- XHED: Title for BottomNav main screen title --> <string name="bottom_nav_home_title">"Start Screen"</string> <!-- XHED: Title for BottomNav diary screen title --> @@ -1955,4 +1959,18 @@ <string name="trace_location_onboarding_body_stay">"Please specify your length of stay as precisely as possible to enable targeted warnings."</string> <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">"Accept"</string> + <!-- XHED: Trace location check-ins camera permission card title--> + <string name="trace_location_attendee_camera_card_title">"Allow Access to Camera"</string> + <!-- XTXT: Trace location check-ins camera permission card susbtitle--> + <string name="trace_location_attendee_camera_card_subtitle">"To use your smartphone camera to scan QR codes, you have to allow access to the camera in the device settings for the app."</string> + <!-- XBUT: Trace location check-ins camera permission card button--> + <string name="trace_location_attendee_camera_card_button">"Open Settings"</string> + <!-- YMSG: Trace location onboarding image description--> + <string name="trace_location_onboarding_content_description">"Three persons are standing at a high table, two of them are looking at their smartphones."</string> + <!-- XMIT: Trace location poster print --> + <string name="trace_location_organiser_poster_print">"Print"</string> + <!-- XMIT: Trace location poster share --> + <string name="trace_location_organiser_poster_share">"Share"</string> + <!-- XHED: Trace location poster title --> + <string name="trace_location_organiser_poster_title">"Print Version"</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml index d692b474a74c82904f040eca98cc3835f57e8abb..dcec3c8b4cf4e134170a6e6d278facaee44e05dd 100644 --- a/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/contact_diary_strings.xml @@ -76,6 +76,13 @@ <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"na podstawie wielu narażeÅ„ o niskim ryzyku."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body_extended">"Niekoniecznie sÄ… one zwiÄ…zane z zarejestrowanymi przez Ciebie osobami i miejscami."</string> + <!-- XTXT: Body for contact diary overview screen trace location risk information prelude --> + <string name="contact_diary_trace_location_risk_body">"na podstawie ich obecnoÅ›ci w:"</string> + <!-- XTXT: Indicates this trace location caused a high risk --> + <string name="contact_diary_trace_location_risk_high">"podwyższone ryzyko"</string> + <!-- XTXT: Indicates this trace location caused a lows risk --> + <string name="contact_diary_trace_location_risk_low">"niskie ryzyko"</string> + <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/main/res/values-pl/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-pl/event_registration_strings.xml index 8fff5ab6706aeb3a6a149f614f93f1b803a61ced..4e9ce1076fc61d75dba61ac001080394a2cb94ab 100644 --- a/Corona-Warn-App/src/main/res/values-pl/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/event_registration_strings.xml @@ -135,6 +135,33 @@ <string name="create_trace_location_card_subtitle">"Planujesz wydarzenie lub prowadzisz firmÄ™? Utwórz kod QR, aby Twoi goÅ›cie mogli zameldować siÄ™ po przybyciu na miejsce."</string> <!-- XTXT: Text for trace location creation card --> <string name="create_trace_location_card_button">"Utwórz kod QR"</string> + <!-- XTXT: Text for content description of tile image --> + <string name="create_trace_location_card_content_description">"Osoba wskazuje na tablicÄ™ flipchart."</string> + + <!-- Trace Location QR Code Info Screen--> + <!-- XTXT: Text for content description of info screen image --> + <string name="trace_location_qr_info_content_description">"Jedna osoba wskazuje na tablicÄ™ flipchart, a dwie inne osoby siedzÄ… obok i patrzÄ… na flipchart."</string> + <!-- XHED: Headline for trace location creation card --> + <string name="trace_location_qr_info_headline_text">"WiÄ™ksze bezpieczeÅ„stwo dla Ciebie i Twoich goÅ›ci"</string> + <!-- XTXT: Text for subtitle of qr info screen --> + <string name="trace_location_qr_info_subtitle_text">"Utwórz kod QR, który Twoi goÅ›cie bÄ™dÄ… mogli zeskanować po przybyciu. DziÄ™ki temu bÄ™dÄ… mogli ostrzegać innych goÅ›ci lub otrzymywać od nich ostrzeżenia, jeÅ›li zajdzie taka potrzeba."</string> + <!-- XTXT: Text for tracing icon of qr info screen --> + <string name="trace_location_qr_info_tracing_text">"Każde zameldowanie jest również brane pod uwagÄ™ przy obliczaniu statusu ryzyka. JeÅ›li u osoby, która zameldowaÅ‚a swojÄ… obecność w danym wydarzeniu lub miejscu, zostanie później zdiagnozowany koronawirus, goÅ›cie zostanÄ… powiadomieni, jeÅ›li przebywali w tym samym pomieszczeniu w tym samym czasie lub do 30 minut po opuszczeniu pomieszczenia przez zdiagnozowanÄ… osobÄ™."</string> + <!-- XTXT: Text for qr icon of qr info screen --> + <string name="trace_location_qr_info_qr_code_text">"Kod QR możesz przekazać goÅ›ciom za pomocÄ… smartfona lub w formie drukowanej."</string> + <!-- XTXT: Text for time sheet icon of qr info screen --> + <string name="trace_location_qr_info_time_text">"UżywajÄ…c kodu QR przez dÅ‚uższy czas, musisz generować go ponownie każdego dnia poza godzinami pracy."</string> + <!-- XTXT: Text for I understand button --> + <string name="acknowledge_button">"Dalej"</string> + + <!-- XHED: Title of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_title">"Zameldowania"</string> + <!-- XTXT: Description of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_description">"Powiadomienia o zameldowaniach i wymeldowaniach, np. o automatycznym wymeldowaniu."</string> + <!-- XHED: Title of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_title">"NastÄ…piÅ‚o automatyczne wymeldowanie."</string> + <!-- XTXT: Description of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_description">"W razie potrzeby dostosuj dÅ‚ugość pobytu."</string> <!-- #################################### Event Organiser Trace Locations List @@ -173,6 +200,11 @@ <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">"Zamelduj siÄ™"</string> + <!-- XTXT: Event organizer detail qr-code: duration with date --> + <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s"</string> + <!-- XTXT: Event organizer detail qr-code: duration with multiple date --> + <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s"</string> + <!-- XBUT: Event organiser list item: menu: information button --> <string name="trace_location_organizer_list_item_menu_duplicate_btn">"Duplikuj"</string> <!-- XBUT: Event organiser list item: menu: show print button --> @@ -180,12 +212,42 @@ <!-- XBUT: Event organiser list item: menu: clear button --> <string name="trace_location_organizer_list_item_menu_clear_btn">"UsuÅ„"</string> + <!-- Trace Location Checkin Confirmation --> + <!-- XHED: Checkin Confirm: title for screen --> + <string name="confirm_checkin_title_text">"Zamelduj siÄ™ na:"</string> + <!-- XTXT: Checkin Confirm: text for event is in future info card --> + <string name="confirm_checkin_event_in_future_card_text">"To wydarzenie zaczyna siÄ™ dopiero %2$s w dniu %1$s. Czy chcesz siÄ™ zameldować już teraz?"</string> + <!-- XTXT: Checkin Confirm: text for event is in past info card --> + <string name="confirm_checkin_event_in_past_card_text">"To wydarzenie już siÄ™ skoÅ„czyÅ‚o. Czy chcesz siÄ™ zameldować z datÄ… wstecznÄ…?"</string> + <!-- XTXT: Checkin Confirm: label for save to contact diary toggle --> + <string name="confirm_checkin_settings_card_checkout_toggle_label">"Zapisać w dzienniku kontaktów po wymeldowaniu?"</string> + <!-- XTXT: Checkin Confirm: label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_label">"Wymelduj automatycznie po"</string> + <!-- XTXT: Checkin Confirm: time length label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_tag">"godz."</string> + <!-- XBUT: Checkin Confirm: Checkin Button --> + <string name="confirm_checkin_confirm_button_text">"Zamelduj siÄ™"</string> + + <!-- Trace Location Checkin Editing --> + <!-- XHED: Checkin Edit: title for screen --> + <string name="edit_checkin_title_text">"ZmieÅ„ dÅ‚ugość pobytu dla:"</string> + <!-- XTXT: Checkin Edit: label for checkout time --> + <string name="edit_checkin_edit_card_checkout_time_label">"Wymeldowano"</string> + <!-- XTXT: Checkin Edit: label for checkin time --> + <string name="edit_checkin_edit_card_checkin_time_label">"Zameldowano"</string> + <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> + <string name="edit_checkin_duration_edit_hint_card_text">"DÅ‚ugość pobytu nie zostanie automatycznie skorygowana w Twoim dzienniku kontaktów."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"Maksymalny czas trwania zameldowania wynosi 24 godziny. Godzina wymeldowania musi być późniejsza niż godzina zameldowania."</string> + <!-- XBUT: Checkin Edit: Save Button --> + <string name="edit_checkin_confirm_button_text">"Zapisz"</string> + <!-- Organizer Flow: Event Detail Screen--> <!-- XTXT: Organizer Flow : Accessibility description for qr-code illustration --> <string name="trace_location_event_detail_qr_code_accessibility">"Kod QR"</string> <!-- XHED: Organizer Flow : Accessibility title for event-overview --> <string name="trace_location_event_detail_title_accessibility">"Widok wydarzenia"</string> - !-- XBUT: Organizer Flow : Title for show print version button --> + <!-- XBUT: Organizer Flow : Title for show print version button --> <string name="trace_location_event_detail_show_print_qr_code_button">"WyÅ›wietl wersjÄ™ do druku"</string> <!-- XBUT: Organizer Flow : Title for save as template button --> <string name="trace_location_event_detail_save_as_template_button">"Użyj jako szablonu"</string> diff --git a/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml index 1ebf43917e62a93e1abafb0300b731da43b92fe9..0363f66e3a4d211dd76f266b8dd1bde827db5ea6 100644 --- a/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml @@ -21,7 +21,7 @@ <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"CWA umożliwia teraz meldowanie obecnoÅ›ci w wydarzeniach i miejscach. Organizatorzy wydarzeÅ„ i wÅ‚aÅ›ciciele sklepów mogÄ… wykorzystać tÄ™ aplikacjÄ™ do utworzenia kodu QR. GoÅ›cie mogÄ… zeskanować ten kod QR po przybyciu na miejsce, aby zarejestrować swojÄ… obecność. Na życzenie aplikacja może również utworzyć wpis w dzienniku. JeÅ›li u osoby, która zameldowaÅ‚a swojÄ… obecność w danym wydarzeniu lub miejscu, zostanie później zdiagnozowany koronawirus, wszystkie osoby, które zameldowaÅ‚y siÄ™ w tym samym czasie, zostanÄ… o tym automatycznie powiadomione."</item> + <item>"CWA umożliwia teraz meldowanie obecnoÅ›ci w wydarzeniach i miejscach. Organizatorzy wydarzeÅ„ i wÅ‚aÅ›ciciele sklepów mogÄ… wykorzystać tÄ™ aplikacjÄ™ do utworzenia kodu QR. GoÅ›cie mogÄ… zeskanować ten kod QR po przybyciu na miejsce, aby zarejestrować swojÄ… obecność. Na życzenie aplikacja może również utworzyć wpis w dzienniku. JeÅ›li u osoby, która zameldowaÅ‚a swojÄ… obecność w danym wydarzeniu lub miejscu, zostanie później zdiagnozowany koronawirus, inne osoby, które zameldowaÅ‚y siÄ™ w tym samym czasie, otrzymajÄ… automatyczne ostrzeżenie."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> diff --git a/Corona-Warn-App/src/main/res/values-pl/strings.xml b/Corona-Warn-App/src/main/res/values-pl/strings.xml index 2b395e1915458f83f0ca29f89863697a767f377b..c0c4d62a5e992784ad81c673262b3cad75116089 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -30,6 +30,10 @@ <!-- #################################### Notification ###################################### --> + <!-- XHED: Title of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_title">"Ogólne"</string> + <!-- XTXT: Description of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_description">"Przypomnienia, notatki i ogólne powiadomienia – na przykÅ‚ad o statusie ryzyka."</string> <!-- XHED: Notification title --> <string name="notification_headline">"Corona-Warn-App"</string> <!-- XTXT: Notification body --> @@ -301,7 +305,7 @@ <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Ryzyko zakażenia można obliczyć tylko dla okresów, w których rejestrowanie narażenia byÅ‚o aktywne. Dlatego też funkcja rejestrowania powinna być stale aktywna. Rejestrowanie narażenia obejmuje 14 ostatnich dni."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> - <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Aplikacja Corona-Warn-App zostaÅ‚a zainstalowana %s temu. Ryzyko zakażenia jest obliczane dla okresów, w których aktywne byÅ‚o rejestrowanie narażenia. Oblicza siÄ™ je w przypadku kontaktowania siÄ™ z innymi ludźmi przy aktywnej funkcji rejestrowania narażenia."</string> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Aplikacja Corona-Warn-App zostaÅ‚a zainstalowana %s dni temu. Ryzyko zakażenia jest obliczane dla okresów, w których aktywne byÅ‚o rejestrowanie narażenia. Oblicza siÄ™ je w przypadku kontaktowania siÄ™ z innymi ludźmi przy aktywnej funkcji rejestrowania narażenia."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> <string name="risk_details_information_body_period_logged_assessment_over_14_days">"JeÅ›li rejestrowanie narażenia byÅ‚o aktywne podczas kontaktowania siÄ™ z innymi ludźmi, można obliczyć ryzyko zakażenia dla tego okresu."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> @@ -1793,13 +1797,13 @@ <string name="statistics_reproduction_title">"Aktualnie"</string> <!-- XTXT: Caption for statistics reproduction card --> <string name="statistics_reproduction_average">"Å›rednia liczba zakażeÅ„ przez zakażonÄ… osobÄ™"</string> - <!-- XTXT: Statistics trend increasing (Accessibilty) --> <string name="statistics_trend_increasing">"Trend: RosnÄ…cy"</string> <!-- XTXT: Statistics trend decreasing (Accessibilty) --> <string name="statistics_trend_decreasing">"Trend: MalejÄ…cy"</string> <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">"Trend: Stabilny"</string> + <!-- XHED: Title for BottomNav main screen title --> <string name="bottom_nav_home_title">"Ekran poczÄ…tkowy"</string> <!-- XHED: Title for BottomNav diary screen title --> @@ -1955,4 +1959,18 @@ <string name="trace_location_onboarding_body_stay">"OkreÅ›l dÅ‚ugość pobytu jak najdokÅ‚adniej, aby ostrzeżenia mogÅ‚y zostać odpowiednio dopasowane."</string> <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">"Akceptuj"</string> + <!-- XHED: Trace location check-ins camera permission card title--> + <string name="trace_location_attendee_camera_card_title">"Zezwól na dostÄ™p do aparatu"</string> + <!-- XTXT: Trace location check-ins camera permission card susbtitle--> + <string name="trace_location_attendee_camera_card_subtitle">"Aby używać aparatu w smartfonie do skanowania kodów QR, musisz zezwolić na dostÄ™p do aparatu w ustawieniach urzÄ…dzenia dla aplikacji."</string> + <!-- XBUT: Trace location check-ins camera permission card button--> + <string name="trace_location_attendee_camera_card_button">"Otwórz opcjÄ™ Ustawienia"</string> + <!-- YMSG: Trace location onboarding image description--> + <string name="trace_location_onboarding_content_description">"Trzy osoby stojÄ… przy wysokim stole, dwie z nich patrzÄ… na swoje smartfony."</string> + <!-- XMIT: Trace location poster print --> + <string name="trace_location_organiser_poster_print">"Drukuj"</string> + <!-- XMIT: Trace location poster share --> + <string name="trace_location_organiser_poster_share">"UdostÄ™pnij"</string> + <!-- XHED: Trace location poster title --> + <string name="trace_location_organiser_poster_title">"Wersja do druku"</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml index c7f0d2b21c4b6fe785418c674c31ed2adba0d33c..3811f313e6f41eb4828478d4690cedf83dd0bde4 100644 --- a/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/contact_diary_strings.xml @@ -76,6 +76,13 @@ <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"pe baza mai multor expuneri cu risc redus."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body_extended">"Acestea nu sunt legate neapărat de persoanele È™i locurile pe care le-aÈ›i înregistrat."</string> + <!-- XTXT: Body for contact diary overview screen trace location risk information prelude --> + <string name="contact_diary_trace_location_risk_body">"pe baza prezenÈ›ei la:"</string> + <!-- XTXT: Indicates this trace location caused a high risk --> + <string name="contact_diary_trace_location_risk_high">"risc crescut"</string> + <!-- XTXT: Indicates this trace location caused a lows risk --> + <string name="contact_diary_trace_location_risk_low">"risc redus"</string> + <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/main/res/values-ro/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-ro/event_registration_strings.xml index f65ea84b0e901665098615bf983b2cad4b00db27..d3211806019b512d0c83c7f53041c81edb686e03 100644 --- a/Corona-Warn-App/src/main/res/values-ro/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/event_registration_strings.xml @@ -135,6 +135,33 @@ <string name="create_trace_location_card_subtitle">"PlanificaÈ›i un eveniment sau aveÈ›i o afacere? CreaÈ›i un cod QR pentru ca invitaÈ›ii dvs. să poată face check-in când sosesc."</string> <!-- XTXT: Text for trace location creation card --> <string name="create_trace_location_card_button">"Creare cod QR"</string> + <!-- XTXT: Text for content description of tile image --> + <string name="create_trace_location_card_content_description">"O persoană arată către un flipchart."</string> + + <!-- Trace Location QR Code Info Screen--> + <!-- XTXT: Text for content description of info screen image --> + <string name="trace_location_qr_info_content_description">"O persoană arată către un flipchart, în timp ce două persoane stau în apropiere È™i privesc flipchartul."</string> + <!-- XHED: Headline for trace location creation card --> + <string name="trace_location_qr_info_headline_text">"Siguranță crescută pentru dvs. È™i invitaÈ›ii dvs."</string> + <!-- XTXT: Text for subtitle of qr info screen --> + <string name="trace_location_qr_info_subtitle_text">"CreaÈ›i un cod QR pe care îl pot scana invitaÈ›ii dvs. atunci când sosesc. Acesta îi va ajuta să îi avertizeze pe ceilalÈ›i invitaÈ›i sau să fie avertizaÈ›i de ei, dacă este necesar."</string> + <!-- XTXT: Text for tracing icon of qr info screen --> + <string name="trace_location_qr_info_tracing_text">"La calcularea stării riscului este luat în calcul È™i fiecare check-in. Dacă o persoană care a făcut check-in la acest eveniment sau în acest loc este diagnosticată ulterior cu coronavirus, invitaÈ›ii pot fi notificaÈ›i dacă au fost în aceeaÈ™i cameră în acelaÈ™i timp sau în decurs de 30 de minute după persoana diagnosticată."</string> + <!-- XTXT: Text for qr icon of qr info screen --> + <string name="trace_location_qr_info_qr_code_text">"PuteÈ›i furniza codul QR invitaÈ›ilor dvs. de pe smartphone-ul dvs. sau în formă tipărită."</string> + <!-- XTXT: Text for time sheet icon of qr info screen --> + <string name="trace_location_qr_info_time_text">"Dacă doriÈ›i să utilizaÈ›i un cod QR pe termen lung, generaÈ›i-l din nou în fiecare zi, în afara programului dvs. de lucru."</string> + <!-- XTXT: Text for I understand button --> + <string name="acknowledge_button">"ÃŽnainte"</string> + + <!-- XHED: Title of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_title">"Check-inuri"</string> + <!-- XTXT: Description of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_description">"Notificări despre check-inurile dvs., precum check-outul automat."</string> + <!-- XHED: Title of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_title">"AÈ›i făcut check-out automat."</string> + <!-- XTXT: Description of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_description">"AjustaÈ›i durata È™ederii dvs. dacă este necesar."</string> <!-- #################################### Event Organiser Trace Locations List @@ -173,6 +200,11 @@ <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">"Check-in"</string> + <!-- XTXT: Event organizer detail qr-code: duration with date --> + <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s"</string> + <!-- XTXT: Event organizer detail qr-code: duration with multiple date --> + <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s"</string> + <!-- XBUT: Event organiser list item: menu: information button --> <string name="trace_location_organizer_list_item_menu_duplicate_btn">"Duplicare"</string> <!-- XBUT: Event organiser list item: menu: show print button --> @@ -180,12 +212,42 @@ <!-- XBUT: Event organiser list item: menu: clear button --> <string name="trace_location_organizer_list_item_menu_clear_btn">"Ștergere"</string> + <!-- Trace Location Checkin Confirmation --> + <!-- XHED: Checkin Confirm: title for screen --> + <string name="confirm_checkin_title_text">"Check-in pentru:"</string> + <!-- XTXT: Checkin Confirm: text for event is in future info card --> + <string name="confirm_checkin_event_in_future_card_text">"Acest eveniment nu începe până la %2$s pe %1$s. DoriÈ›i să faceÈ›i deja check-inul?"</string> + <!-- XTXT: Checkin Confirm: text for event is in past info card --> + <string name="confirm_checkin_event_in_past_card_text">"Aceste eveniment s-a încheiat deja. DoriÈ›i să faceÈ›i check-in retroactiv?"</string> + <!-- XTXT: Checkin Confirm: label for save to contact diary toggle --> + <string name="confirm_checkin_settings_card_checkout_toggle_label">"ÃŽnregistraÈ›i în jurnalul meu de contacte după check-out?"</string> + <!-- XTXT: Checkin Confirm: label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_label">"Check-out automat după"</string> + <!-- XTXT: Checkin Confirm: time length label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_tag">"ore"</string> + <!-- XBUT: Checkin Confirm: Checkin Button --> + <string name="confirm_checkin_confirm_button_text">"Check-in"</string> + + <!-- Trace Location Checkin Editing --> + <!-- XHED: Checkin Edit: title for screen --> + <string name="edit_checkin_title_text">"Modificare lungime È™edere pentru:"</string> + <!-- XTXT: Checkin Edit: label for checkout time --> + <string name="edit_checkin_edit_card_checkout_time_label">"Check-out făcut"</string> + <!-- XTXT: Checkin Edit: label for checkin time --> + <string name="edit_checkin_edit_card_checkin_time_label">"Check-in făcut"</string> + <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> + <string name="edit_checkin_duration_edit_hint_card_text">"Lungimea È™ederii nu va fi ajustată automat în jurnalul dvs. de contacte."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"PuteÈ›i rămâne cu check-inul făcut maximum 24 de ore. Ora de check-out trebuie să fie după ora de check-in."</string> + <!-- XBUT: Checkin Edit: Save Button --> + <string name="edit_checkin_confirm_button_text">"Salvare"</string> + <!-- Organizer Flow: Event Detail Screen--> <!-- XTXT: Organizer Flow : Accessibility description for qr-code illustration --> <string name="trace_location_event_detail_qr_code_accessibility">"Cod QR"</string> <!-- XHED: Organizer Flow : Accessibility title for event-overview --> <string name="trace_location_event_detail_title_accessibility">"Vizualizare eveniment"</string> - !-- XBUT: Organizer Flow : Title for show print version button --> + <!-- XBUT: Organizer Flow : Title for show print version button --> <string name="trace_location_event_detail_show_print_qr_code_button">"AfiÈ™are versiune de tipărire"</string> <!-- XBUT: Organizer Flow : Title for save as template button --> <string name="trace_location_event_detail_save_as_template_button">"Utilizare ca È™ablon"</string> diff --git a/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml index dd71c100b52d851f2dfb7f1915b246faadc7ef94..ca0f51243fa69b448026fb667961916e8e1a2df4 100644 --- a/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml @@ -21,7 +21,7 @@ <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"Acum, aplicaÈ›ia Corona-Warn vă permite să faceÈ›i check-in la evenimente È™i în locuri. Organizatorii de evenimente È™i proprietarii de magazine pot utiliza aplicaÈ›ia pentru a crea un cod QR. Apoi, invitaÈ›ii pot scana acest cod QR atunci când sosesc, pentru a-È™i înregistra prezenÈ›a. De asemenea, aplicaÈ›ia poate crea la cerere o intrare în jurnal. Dacă o persoană care a făcut check-in la acest eveniment sau în acest loc este diagnosticată ulterior cu coronavirus, toate persoanele care au făcut check-in în acelaÈ™i timp sunt notificate automat."</item> + <item>"Acum, aplicaÈ›ia Corona-Warn vă permite să faceÈ›i check-in la evenimente È™i în locuri. Organizatorii de evenimente È™i proprietarii de magazine pot utiliza aplicaÈ›ia pentru a crea un cod QR. Apoi, invitaÈ›ii pot scana acest cod QR atunci când sosesc, pentru a-È™i înregistra prezenÈ›a. De asemenea, aplicaÈ›ia poate crea la cerere o intrare în jurnal. Dacă o persoană care a făcut check-in la acest eveniment sau în acest loc este diagnosticată ulterior cu coronavirus, alte persoane care au făcut check-in în acelaÈ™i timp pot fi avertizate automat."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> diff --git a/Corona-Warn-App/src/main/res/values-ro/strings.xml b/Corona-Warn-App/src/main/res/values-ro/strings.xml index 42f54f41a08888f4e4bde2234ae3e0747b7b3710..50d09ff8ae9d5fb6ce85e1264004498cf2e98d83 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -30,6 +30,10 @@ <!-- #################################### Notification ###################################### --> + <!-- XHED: Title of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_title">"Generale"</string> + <!-- XTXT: Description of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_description">"Mementouri, note È™i notificări generale – de exemplu, despre starea riscului dvs."</string> <!-- XHED: Notification title --> <string name="notification_headline">"Corona-Warn-App"</string> <!-- XTXT: Notification body --> @@ -301,7 +305,7 @@ <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Riscul dvs. de infectare poate fi calculat doar pentru perioadele în care a fost activă înregistrarea în jurnal a expunerilor. Prin urmare, caracteristica de înregistrare în jurnal trebuie să rămână permanent activă. ÃŽnregistrarea în jurnal a expunerilor acoperă ultimele 14 zile."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> - <string name="risk_details_information_body_period_logged_assessment_under_14_days">"AplicaÈ›ia Corona-Warn a fost instalată acum %s. Riscul dvs. de infectare este calculat pentru perioadele în care înregistrarea în jurnal a expunerilor a fost activă. Dacă v-aÈ›i întâlnit cu alte persoane È™i înregistrarea în jurnal a expunerilor a fost activă, este calculat riscul dvs. de infectare."</string> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"AplicaÈ›ia Corona-Warn a fost instalată acum %s zi(le). Riscul dvs. de infectare este calculat pentru perioadele în care înregistrarea în jurnal a expunerilor a fost activă. Dacă v-aÈ›i întâlnit cu alte persoane È™i înregistrarea în jurnal a expunerilor a fost activă, este calculat riscul dvs. de infectare."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Dacă înregistrarea în jurnal a expunerilor a fost activă pe durata în care v-aÈ›i întâlnit cu alte persoane, riscul dvs. de infectare poate fi calculat pentru această perioadă."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> @@ -1793,13 +1797,13 @@ <string name="statistics_reproduction_title">"Curentă"</string> <!-- XTXT: Caption for statistics reproduction card --> <string name="statistics_reproduction_average">"infecÈ›ii medii de la fiecare persoană infectată"</string> - <!-- XTXT: Statistics trend increasing (Accessibilty) --> <string name="statistics_trend_increasing">"Tendință: ascendentă"</string> <!-- XTXT: Statistics trend decreasing (Accessibilty) --> <string name="statistics_trend_decreasing">"Tendință: descendentă"</string> <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">"Tendință: constantă"</string> + <!-- XHED: Title for BottomNav main screen title --> <string name="bottom_nav_home_title">"Ecran iniÈ›ial"</string> <!-- XHED: Title for BottomNav diary screen title --> @@ -1955,4 +1959,18 @@ <string name="trace_location_onboarding_body_stay">"SpecificaÈ›i durata È™ederii cât mai precis posibil pentru a permite avertizările exacte."</string> <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">"Accept"</string> + <!-- XHED: Trace location check-ins camera permission card title--> + <string name="trace_location_attendee_camera_card_title">"Permite accesul la cameră"</string> + <!-- XTXT: Trace location check-ins camera permission card susbtitle--> + <string name="trace_location_attendee_camera_card_subtitle">"Pentru a utiliza camera smartphone-ului să scanaÈ›i codurile QR, trebuie să permiteÈ›i accesul la cameră în setările dispozitivului pentru aplicaÈ›ie."</string> + <!-- XBUT: Trace location check-ins camera permission card button--> + <string name="trace_location_attendee_camera_card_button">"DeschideÈ›i Configurări"</string> + <!-- YMSG: Trace location onboarding image description--> + <string name="trace_location_onboarding_content_description">"Trei persoane stau în picioare la o masă înaltă, iar două dintre ele se uită pe smartphone."</string> + <!-- XMIT: Trace location poster print --> + <string name="trace_location_organiser_poster_print">"Tipărire"</string> + <!-- XMIT: Trace location poster share --> + <string name="trace_location_organiser_poster_share">"Partajare"</string> + <!-- XHED: Trace location poster title --> + <string name="trace_location_organiser_poster_title">"Versiune de tipărire"</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml index dd2de4ef2aa4a159bb9dc50ce58385acfb2facba..ba14375c44e5d0603fb96eed435c86613589e86c 100644 --- a/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/contact_diary_strings.xml @@ -76,6 +76,13 @@ <string name="contact_diary_risk_body_high_risk_due_to_low_risk_encounters">"(düşük riskli birden fazla maruz kalmaya göre)."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body_extended">"KaydettiÄŸiniz kiÅŸilerle ve yerlerle ilgili olmaları ÅŸart deÄŸildir."</string> + <!-- XTXT: Body for contact diary overview screen trace location risk information prelude --> + <string name="contact_diary_trace_location_risk_body">"temel alınan katılım:"</string> + <!-- XTXT: Indicates this trace location caused a high risk --> + <string name="contact_diary_trace_location_risk_high">"daha yüksek risk"</string> + <!-- XTXT: Indicates this trace location caused a lows risk --> + <string name="contact_diary_trace_location_risk_low">"düşük risk"</string> + <!-- XTXT: content description of contact journal image on home screen --> diff --git a/Corona-Warn-App/src/main/res/values-tr/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values-tr/event_registration_strings.xml index a021d8daa3264b99c1afb31431d4ab95715d2ca8..4e87da23cd26e40c79ca1eb46498f39ebf8cac32 100644 --- a/Corona-Warn-App/src/main/res/values-tr/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/event_registration_strings.xml @@ -135,6 +135,33 @@ <string name="create_trace_location_card_subtitle">"Bir etkinlik planlıyor ya da iÅŸletme mi çalıştırıyorsunuz? Konuklarınızın geldiklerinde check in yapabilmeleri için bir QR kod oluÅŸturun."</string> <!-- XTXT: Text for trace location creation card --> <string name="create_trace_location_card_button">"QR Kod OluÅŸtur"</string> + <!-- XTXT: Text for content description of tile image --> + <string name="create_trace_location_card_content_description">"Bir kiÅŸi bir kağıt tahtayı iÅŸaret ediyor."</string> + + <!-- Trace Location QR Code Info Screen--> + <!-- XTXT: Text for content description of info screen image --> + <string name="trace_location_qr_info_content_description">"Bir kiÅŸi bir kağıt tahtayı iÅŸaret ederken iki kiÅŸi yanında oturuyor ve tahtaya bakıyor."</string> + <!-- XHED: Headline for trace location creation card --> + <string name="trace_location_qr_info_headline_text">"Siz ve Konuklarınız için Daha Yüksek Düzeyde Güvenlik"</string> + <!-- XTXT: Text for subtitle of qr info screen --> + <string name="trace_location_qr_info_subtitle_text">"Geldiklerinde konuklarınızın tarayabilmeleri için bir QR kod oluÅŸturun. Bu sayede gerekirse diÄŸer konukları uyarabilir ya da diÄŸer konuklardan uyarı alabileceklerdir."</string> + <!-- XTXT: Text for tracing icon of qr info screen --> + <string name="trace_location_qr_info_tracing_text">"Risk durumu hesaplanırken her bir check-in de dikkate alınır. Bu etkinliÄŸe veya yere check in yapan biri daha sonra koronavirüs tanısı alırsa tanı alan kiÅŸi ile aynı anda ya da bu kiÅŸi odadan çıktıktan sonra en fazla 30 dakika içinde aynı odada bulunmuÅŸlarsa konuklara uyarı gönderilebilir."</string> + <!-- XTXT: Text for qr icon of qr info screen --> + <string name="trace_location_qr_info_qr_code_text">"QR kodu konuklarınıza akıllı telefonunuzda ya da yazılı bir form halinde sunabilirsiniz."</string> + <!-- XTXT: Text for time sheet icon of qr info screen --> + <string name="trace_location_qr_info_time_text">"QR kodu uzun süreli olarak kullanmak istiyorsanız mesai saatlerinizin dışında her gün yeniden QR kod oluÅŸturmanız gerekir."</string> + <!-- XTXT: Text for I understand button --> + <string name="acknowledge_button">"Sonraki"</string> + + <!-- XHED: Title of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_title">"Check-In’ler"</string> + <!-- XTXT: Description of the notification channel for event registration related notifications. --> + <string name="tracelocation_notification_channel_description">"Check-in’lerinizle ilgili bildirimler; örneÄŸin, otomatik check-out."</string> + <!-- XHED: Title of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_title">"Otomatik olarak check out yaptınız."</string> + <!-- XTXT: Description of the automatic check-out notification. --> + <string name="tracelocation_notification_autocheckout_description">"Gerekiyorsa lütfen kalış sürenizi düzenleyin."</string> <!-- #################################### Event Organiser Trace Locations List @@ -173,6 +200,11 @@ <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">"Check In"</string> + <!-- XTXT: Event organizer detail qr-code: duration with date --> + <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s"</string> + <!-- XTXT: Event organizer detail qr-code: duration with multiple date --> + <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s"</string> + <!-- XBUT: Event organiser list item: menu: information button --> <string name="trace_location_organizer_list_item_menu_duplicate_btn">"ÇoÄŸalt"</string> <!-- XBUT: Event organiser list item: menu: show print button --> @@ -180,12 +212,42 @@ <!-- XBUT: Event organiser list item: menu: clear button --> <string name="trace_location_organizer_list_item_menu_clear_btn">"Sil"</string> + <!-- Trace Location Checkin Confirmation --> + <!-- XHED: Checkin Confirm: title for screen --> + <string name="confirm_checkin_title_text">"Check in niteliÄŸi:"</string> + <!-- XTXT: Checkin Confirm: text for event is in future info card --> + <string name="confirm_checkin_event_in_future_card_text">"Bu etkinliÄŸin baÅŸlangıç saati, %1$s %2$s olarak belirlenmiÅŸtir. Åžimdiden check in yapmak istiyor musunuz?"</string> + <!-- XTXT: Checkin Confirm: text for event is in past info card --> + <string name="confirm_checkin_event_in_past_card_text">"Bu etkinlik zaten bitmiÅŸ. Geriye dönük olarak check in yapmak istiyor musunuz?"</string> + <!-- XTXT: Checkin Confirm: label for save to contact diary toggle --> + <string name="confirm_checkin_settings_card_checkout_toggle_label">"Check-out yapıldıktan sonra temas günceme kaydedilsin mi?"</string> + <!-- XTXT: Checkin Confirm: label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_label">"Daha sonra otomatik olarak check out yap"</string> + <!-- XTXT: Checkin Confirm: time length label for checkout delay selection --> + <string name="confirm_checkin_settings_card_checkout_time_tag">"sa"</string> + <!-- XBUT: Checkin Confirm: Checkin Button --> + <string name="confirm_checkin_confirm_button_text">"Check In"</string> + + <!-- Trace Location Checkin Editing --> + <!-- XHED: Checkin Edit: title for screen --> + <string name="edit_checkin_title_text">"Kalış süresini deÄŸiÅŸtir:"</string> + <!-- XTXT: Checkin Edit: label for checkout time --> + <string name="edit_checkin_edit_card_checkout_time_label">"Check Out yapıldı"</string> + <!-- XTXT: Checkin Edit: label for checkin time --> + <string name="edit_checkin_edit_card_checkin_time_label">"Check In yapıldı"</string> + <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> + <string name="edit_checkin_duration_edit_hint_card_text">"Kalış süresi temas güncenizde otomatik olarak ayarlanmayacak."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"Check in’inizi 24 saate kadar sürdürebilirsiniz. Check-out zamanı check-in zamanından sonra olmalıdır."</string> + <!-- XBUT: Checkin Edit: Save Button --> + <string name="edit_checkin_confirm_button_text">"Kaydet"</string> + <!-- Organizer Flow: Event Detail Screen--> <!-- XTXT: Organizer Flow : Accessibility description for qr-code illustration --> <string name="trace_location_event_detail_qr_code_accessibility">"QR kod"</string> <!-- XHED: Organizer Flow : Accessibility title for event-overview --> <string name="trace_location_event_detail_title_accessibility">"Etkinlik Görünümü"</string> - !-- XBUT: Organizer Flow : Title for show print version button --> + <!-- XBUT: Organizer Flow : Title for show print version button --> <string name="trace_location_event_detail_show_print_qr_code_button">"Basılı Versiyonu Görüntüle"</string> <!-- XBUT: Organizer Flow : Title for save as template button --> <string name="trace_location_event_detail_save_as_template_button">"Åžablon olarak kullan"</string> diff --git a/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml index 4306536cd319d23a33380117c5505acc33bcb338..647de500bcd9025e787228efd55ee1c74563e106 100644 --- a/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml @@ -21,7 +21,7 @@ <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"CWA artık etkinliklere ve yerlere check in yapabilmenizi saÄŸlar. Etkinlik organizatörleri ve maÄŸaza sahipleri uygulamayı kullanarak bir QR kod oluÅŸturabilir. Ardından konuklar geldiklerinde bu QR kodu tarayarak katılımlarını kayıt altına alabilir. Uygulama, talep üzerine günce giriÅŸi oluÅŸturma seçeneÄŸi de sunmaktadır. Bu etkinliÄŸe ya da yere check in yapan bir kiÅŸi daha sonra koronavirüs tanısı alırsa aynı anda check in yapmış herkese otomatik olarak uyarı gönderilir."</item> + <item>"CWA artık etkinliklere ve yerlere check in yapabilmenizi saÄŸlar. Etkinlik organizatörleri ve maÄŸaza sahipleri uygulamayı kullanarak bir QR kod oluÅŸturabilir. Ardından konuklar geldiklerinde bu QR kodu tarayarak katılımlarını kayıt altına alabilir. Uygulama, talep üzerine günce giriÅŸi oluÅŸturma seçeneÄŸi de sunmaktadır. Bu etkinliÄŸe ya da yere check in yapan bir kiÅŸi daha sonra koronavirüs tanısı alırsa aynı anda check in yapmış diÄŸer kiÅŸilere otomatik olarak uyarı gönderilebilir."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> diff --git a/Corona-Warn-App/src/main/res/values-tr/strings.xml b/Corona-Warn-App/src/main/res/values-tr/strings.xml index 899b582582ec3dbb5dbaaf1baab5099ccd680900..d080eb6d94cba83a4cab05fbffa1ce71adb7ee8b 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -30,6 +30,10 @@ <!-- #################################### Notification ###################################### --> + <!-- XHED: Title of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_title">"Genel"</string> + <!-- XTXT: Description of the notification channel for general CWA notifications (e.g. risk change) --> + <string name="general_notification_channel_description">"Hatırlatmalar, notlar ve genel bildirimler (örneÄŸin, risk durumunuz hakkında)."</string> <!-- XHED: Notification title --> <string name="notification_headline">"Corona-Warn-App"</string> <!-- XTXT: Notification body --> @@ -303,7 +307,7 @@ <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment_under_14_days">"Corona-Warn-App %s gün önce yüklendi. Enfeksiyon riskiniz, maruz kalma günlüğünün etkin olduÄŸu dönemler için hesaplanır. BaÅŸka insanlarla karşılaÅŸmışsanız ve bu sırada maruz kalma günlüğü etkindiyse enfeksiyon riskiniz hesaplanır."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> - <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Maruz kalma günlüğü baÅŸka insanlarla karşılaÅŸtığınız sırada etkindiyse bu dönem için enfeksiyon riskiniz hesaplanabilir."</string> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"Maruz kalma günlüğü baÅŸka insanlarla karşılaÅŸtığınız anlarda etkindiyse bu dönem için enfeksiyon riskiniz hesaplanabilir."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment">"Uygulama, enfeksiyondan korunma için artık ilgili olmadığından daha eski kayıtları otomatik olarak siler."</string> <!-- XTXT: risk details - infection period days logged/14 --> @@ -1793,13 +1797,13 @@ <string name="statistics_reproduction_title">"Åžu Anda"</string> <!-- XTXT: Caption for statistics reproduction card --> <string name="statistics_reproduction_average">"enfekte olan kiÅŸi başına ortalama enfeksiyon sayısı"</string> - <!-- XTXT: Statistics trend increasing (Accessibilty) --> <string name="statistics_trend_increasing">"Trend: Yukarı"</string> <!-- XTXT: Statistics trend decreasing (Accessibilty) --> <string name="statistics_trend_decreasing">"Trend: AÅŸağı"</string> <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">"Trend: Sabit"</string> + <!-- XHED: Title for BottomNav main screen title --> <string name="bottom_nav_home_title">"BaÅŸlangıç Ekranı"</string> <!-- XHED: Title for BottomNav diary screen title --> @@ -1955,4 +1959,18 @@ <string name="trace_location_onboarding_body_stay">"Hedeflenen uyarıları etkinleÅŸtirmek için lütfen kalış sürenizi mümkün olduÄŸunca net bir biçimde belirtin."</string> <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">"Kabul Et"</string> + <!-- XHED: Trace location check-ins camera permission card title--> + <string name="trace_location_attendee_camera_card_title">"Kamera EriÅŸimine Ä°zin Ver"</string> + <!-- XTXT: Trace location check-ins camera permission card susbtitle--> + <string name="trace_location_attendee_camera_card_subtitle">"Akıllı telefonunuzun kamerasını kullanarak QR kodları taramak için uygulamanın cihaz ayarlarında kamera eriÅŸimine izin vermeniz gerekir."</string> + <!-- XBUT: Trace location check-ins camera permission card button--> + <string name="trace_location_attendee_camera_card_button">"Ayarları Aç"</string> + <!-- YMSG: Trace location onboarding image description--> + <string name="trace_location_onboarding_content_description">"Üç kiÅŸi yüksek bir masanın etrafında ayakta duruyor, ikisi akıllı telefonuna bakıyor."</string> + <!-- XMIT: Trace location poster print --> + <string name="trace_location_organiser_poster_print">"Yazdır"</string> + <!-- XMIT: Trace location poster share --> + <string name="trace_location_organiser_poster_share">"PaylaÅŸ"</string> + <!-- XHED: Trace location poster title --> + <string name="trace_location_organiser_poster_title">"Yazdırma Sürümü"</string> </resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml index 80718cbbbd309538f704035c4d9548a2174a934a..2a15f03cf339fb1410ad35260a076f9885391d1b 100644 --- a/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values/contact_diary_strings.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> <!-- #################################### - Contact Diary - ###################################### --> + Contact Diary + ###################################### --> <string name="contact_diary_day_person_tab_title">"People"</string> <string name="contact_diary_day_person_fab_title">"Person"</string> <string name="contact_diary_day_location_tab_title">"Places"</string> @@ -64,7 +64,7 @@ <!-- XTXT: Subtitle for contact diary overview screen --> <string name="contact_diary_overview_subtitle">"Write down the people you have met and places you have visited. If a risk is displayed for a day, you can warn people you have been with on that day if they do not use the Corona-Warn-App themselves."</string> <!-- XTXT: Header for contact diary overview screen --> - <string name="contact_diary_overview_header">Start Page</string> + <string name="contact_diary_overview_header">"Start Page"</string> <!-- XTXT: Title for contact diary overview screen high risk information --> @@ -78,11 +78,12 @@ <!-- XTXT: Extended Body for contact diary overview screen risk information --> <string name="contact_diary_risk_body_extended">"They are not necessarily related to the people and places you have recorded."</string> <!-- XTXT: Body for contact diary overview screen trace location risk information prelude --> - <string name="contact_diary_trace_location_risk_body">""</string> + <string name="contact_diary_trace_location_risk_body">"based on their presence at:"</string> <!-- XTXT: Indicates this trace location caused a high risk --> - <string name="contact_diary_trace_location_risk_high">""</string> + <string name="contact_diary_trace_location_risk_high">"increased risk"</string> <!-- XTXT: Indicates this trace location caused a lows risk --> - <string name="contact_diary_trace_location_risk_low">""</string> + <string name="contact_diary_trace_location_risk_low">"low risk"</string> + <!-- XTXT: content description of contact journal image on home screen --> <string name="contact_diary_homescreen_card_image_content_description">"A closed book with a bookmark"</string> @@ -125,6 +126,7 @@ <!-- XTXT: Message for the contact diary dialog to delete a single person --> <string name="contact_diary_delete_person_message">"If you remove a person, all the entries for that person will be removed from your journal."</string> + <!-- EXPORT --> <!-- XHED: Title for the contact journal export email subject --> <string name="contact_diary_export_subject" translatable="false">"Mein Kontakt-Tagebuch"</string> <!-- XTXT: Intro text for the contact journal email export part one--> @@ -208,4 +210,4 @@ <string name="contact_diary_location_visit_duration_hour">"hrs"</string> <!-- XTXT: Option - person encounter - circumstances hint--> <string name="contact_diary_location_visit_circumstances_hint">"Note (e.g. very full)"</string> -</resources> +</resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/event_registration_strings.xml b/Corona-Warn-App/src/main/res/values/event_registration_strings.xml index b748e76ead86b6fc283427c1629e237575db530e..a326943cfb63867f792db7ac14a3789e7bac4326 100644 --- a/Corona-Warn-App/src/main/res/values/event_registration_strings.xml +++ b/Corona-Warn-App/src/main/res/values/event_registration_strings.xml @@ -42,7 +42,8 @@ <string name="trace_location_checkins_card_highlight_duration_label">"Duration"</string> <!-- XTXT: My check-ins card: Active event, checkin information, automatic checkout info --> <string name="trace_location_checkins_card_automatic_checkout_info">"%1$s - check out automatically after %2$s."</string> - + <!-- XTXT: My check-ins card: Active event, checkin information, automatic checkout info --> + <string name="trace_location_checkins_card_automatic_checkout_info_format">"%1$s 2$s - check out automatically after %3$s."</string> <!-- XHED: Title of the category list screen of the event creation --> <string name="tracelocation_organizer_category_title">"Create QR Code"</string> @@ -136,33 +137,32 @@ <!-- XTXT: Text for trace location creation card --> <string name="create_trace_location_card_button">"Create QR Code"</string> <!-- XTXT: Text for content description of tile image --> - <string name="create_trace_location_card_content_description">"Eine Person zeigt auf ein Flipchart."</string> + <string name="create_trace_location_card_content_description">"A person points to a flip chart."</string> <!-- Trace Location QR Code Info Screen--> <!-- XTXT: Text for content description of info screen image --> - <string name="trace_location_qr_info_content_description">"Eine Person zeigt auf ein Flipchart, zwei Personen sitzen daneben und schauen auf das Flipchart."</string> + <string name="trace_location_qr_info_content_description">"One person points to a flip chart, while two people are sitting adjacent and looking at the flip chart."</string> <!-- XHED: Headline for trace location creation card --> - <string name="trace_location_qr_info_headline_text">"Mehr Sicherheit für Sie und Ihre Gäste"</string> + <string name="trace_location_qr_info_headline_text">"Increased Safety for You and Your Guests"</string> <!-- XTXT: Text for subtitle of qr info screen --> - <string name="trace_location_qr_info_subtitle_text">"Erstellen Sie einen QR-Code, den Ihre Gäste bei ihrer Ankunft scannen können. So können sie bei Bedarf andere Gäste warnen oder von ihnen gewarnt werden."</string> + <string name="trace_location_qr_info_subtitle_text">"Create a QR code that your guests can scan when they arrive. This will enable them to warn other guests or be warned by them if necessary."</string> <!-- XTXT: Text for tracing icon of qr info screen --> - <string name="trace_location_qr_info_tracing_text">"Jeder Check-in wird bei der Ermittlung des Risikostatus zusätzlich berücksichtigt. Wird eine eingecheckte Person später positiv getestet, können die Gäste gewarnt werden, wenn sie sich zur gleichen Zeit oder bis zu 30 Minuten nach der positiv getesteten Person im gleichen Raum aufgehalten haben."</string> + <string name="trace_location_qr_info_tracing_text">"Every check-in is also taken into account when calculating the risk status. If someone who checked in to this event or place is diagnosed with coronavirus later, the guests can be notified if they were in the same room at the same time or up to 30 minutes after the diagnosed person."</string> <!-- XTXT: Text for qr icon of qr info screen --> - <string name="trace_location_qr_info_qr_code_text">"Stellen Sie den QR-Code Ihren Gästen entweder über Ihr Smartphone oder in ausgedruckter Form zur Verfügung."</string> + <string name="trace_location_qr_info_qr_code_text">"You can provide the QR code to your guests on your smartphone or as a printed form."</string> <!-- XTXT: Text for time sheet icon of qr info screen --> - <string name="trace_location_qr_info_time_text">"Wenn Sie dauerhaft einen QR-Code verwenden, sollten Sie diesen einmal täglich neu erstellen außerhalb der Öffnungszeiten."</string> + <string name="trace_location_qr_info_time_text">"If you want to use a QR code for the long term, you should generate it again each day, outside of your operating hours."</string> <!-- XTXT: Text for I understand button --> - <string name="acknowledge_button">"Enverstanden"</string> - + <string name="acknowledge_button">"Next"</string> <!-- XHED: Title of the notification channel for event registration related notifications. --> - <string name="tracelocation_notification_channel_title">"Check-ins"</string> + <string name="tracelocation_notification_channel_title">"Check-Ins"</string> <!-- XTXT: Description of the notification channel for event registration related notifications. --> - <string name="tracelocation_notification_channel_description">"Benachrichtungen zu Ihren Check-ins, z.B. automatischer Check-out."</string> + <string name="tracelocation_notification_channel_description">"Notifications about your check-ins, such as automatic check-out."</string> <!-- XHED: Title of the automatic check-out notification. --> - <string name="tracelocation_notification_autocheckout_title">"Sie wurden automatisch ausgecheckt."</string> + <string name="tracelocation_notification_autocheckout_title">"You were checked out automatically."</string> <!-- XTXT: Description of the automatic check-out notification. --> - <string name="tracelocation_notification_autocheckout_description">"Bitte passen Sie Ihre Aufenthaltsdauer gegebenenfalls an."</string> + <string name="tracelocation_notification_autocheckout_description">"Please adjust the length of your stay if needed."</string> <!-- #################################### Event Organiser Trace Locations List @@ -198,13 +198,17 @@ <!-- XTXT: Event organiser list item: duration--> <string name="trace_location_organizer_list_item_duration">"%1$s - %2$s"</string> + + <!-- XTXT: Event organiser list item: duration same day--> + <string name="trace_location_organizer_list_item_duration_same_day">"%1$s 2$s - %3$s"</string> + <!-- XBUT: Event organiser list item: checkin button --> <string name="trace_location_organizer_list_item_action_checkin">"Check In"</string> <!-- XTXT: Event organizer detail qr-code: duration with date --> - <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s Uhr"</string> + <string name="trace_location_organizer_detail_item_duration">"%1$s, %2$s - %3$s"</string> <!-- XTXT: Event organizer detail qr-code: duration with multiple date --> - <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s Uhr"</string> + <string name="trace_location_organizer_detail_item_duration_multiple_days">"%1$s, %2$s - %3$s, %4$s"</string> <!-- XBUT: Event organiser list item: menu: information button --> <string name="trace_location_organizer_list_item_menu_duplicate_btn">"Duplicate"</string> @@ -213,34 +217,35 @@ <!-- XBUT: Event organiser list item: menu: clear button --> <string name="trace_location_organizer_list_item_menu_clear_btn">"Delete"</string> - <!-- Trace Location Checkin Confirmation --> <!-- XHED: Checkin Confirm: title for screen --> - <string name="confirm_checkin_title_text">"Einchecken für:"</string> + <string name="confirm_checkin_title_text">"Check in for:"</string> <!-- XTXT: Checkin Confirm: text for event is in future info card --> - <string name="confirm_checkin_event_in_future_card_text">"Das Event beginnt erst am %1$s um %2$s Uhr. Wollen Sie jetzt bereits einchecken?"</string> + <string name="confirm_checkin_event_in_future_card_text">"This event doesn’t start until %2$s on %1$s. Do you want to check in already?"</string> <!-- XTXT: Checkin Confirm: text for event is in past info card --> - <string name="confirm_checkin_event_in_past_card_text">"Das Event ist beendet. Wollen Sie nachträglich einchecken?"</string> + <string name="confirm_checkin_event_in_past_card_text">"This event is already over. Do you want to check in retroactively?"</string> <!-- XTXT: Checkin Confirm: label for save to contact diary toggle --> - <string name="confirm_checkin_settings_card_checkout_toggle_label">"Nach dem Auschecken in mein Kontakt-Tagebuch eintragen?"</string> + <string name="confirm_checkin_settings_card_checkout_toggle_label">"Record in my contact journal after check-out?"</string> <!-- XTXT: Checkin Confirm: label for checkout delay selection --> - <string name="confirm_checkin_settings_card_checkout_time_label">"Automatisch auschecken nach"</string> + <string name="confirm_checkin_settings_card_checkout_time_label">"Check out automatically after"</string> <!-- XTXT: Checkin Confirm: time length label for checkout delay selection --> - <string name="confirm_checkin_settings_card_checkout_time_tag">"Std"</string> + <string name="confirm_checkin_settings_card_checkout_time_tag">"hrs"</string> <!-- XBUT: Checkin Confirm: Checkin Button --> - <string name="confirm_checkin_confirm_button_text">"Einchecken"</string> + <string name="confirm_checkin_confirm_button_text">"Check In"</string> <!-- Trace Location Checkin Editing --> <!-- XHED: Checkin Edit: title for screen --> - <string name="edit_checkin_title_text">"Aufenthaltsdauer anpassen für:"</string> + <string name="edit_checkin_title_text">"Change length of stay for:"</string> <!-- XTXT: Checkin Edit: label for checkout time --> - <string name="edit_checkin_edit_card_checkout_time_label">"Ausgecheckt"</string> + <string name="edit_checkin_edit_card_checkout_time_label">"Checked Out"</string> <!-- XTXT: Checkin Edit: label for checkin time --> - <string name="edit_checkin_edit_card_checkin_time_label">"Eingecheckt"</string> + <string name="edit_checkin_edit_card_checkin_time_label">"Checked In"</string> <!-- XTXT: Checkin Edit: hint for unchanged contact diary checkin duration --> - <string name="edit_checkin_duration_edit_hint_card_text">"Die Aufenthaltsdauer wird nicht automatisch in Ihrem Kontakt-Tagebuch angepasst."</string> + <string name="edit_checkin_duration_edit_hint_card_text">"The length of stay will not be adjusted automatically in your contact journal."</string> + <!-- XTXT: Checkin Edit: warning if user input is incorrect --> + <string name="edit_checkin_wrong_input_warning_text">"You can remain checked in for up to 24 hours. The check-out time must be later than the check-in time."</string> <!-- XBUT: Checkin Edit: Save Button --> - <string name="edit_checkin_confirm_button_text">"Speichern"</string> + <string name="edit_checkin_confirm_button_text">"Save"</string> <!-- Organizer Flow: Event Detail Screen--> <!-- XTXT: Organizer Flow : Accessibility description for qr-code illustration --> diff --git a/Corona-Warn-App/src/main/res/values/release_info_strings.xml b/Corona-Warn-App/src/main/res/values/release_info_strings.xml index d0c5629983daf84179471605bf87da8c83298fa5..f480f402b927499db999d597b89dd70293fdc893 100644 --- a/Corona-Warn-App/src/main/res/values/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values/release_info_strings.xml @@ -7,7 +7,7 @@ <!-- XHED: Title for the release info screen --> <string name="release_info_header">"New Features"</string> <!-- XHED: Version title for the release info screen --> - <string name="release_info_version_title">Release %1$s</string> + <string name="release_info_version_title">"Release %1$s"</string> <!-- XTXT: Description for the release info screen --> <string name="release_info_version_body">"Along with bug fixes, this update also includes new and enhanced features."</string> <!-- XBUT: Continue button for the release info screen --> @@ -22,7 +22,7 @@ <!-- XTXT: Text bodies for the release info screen bullet points --> <string-array name="new_release_body"> - <item>"The CWA now enables you to check in to events and places. Event organizers and store owners can use the app to create a QR code. Guests can then scan this QR code when they arrive to register their presence. The app can also create a journal entry upon request. If someone who checked in to this event or place is diagnosed with coronavirus later, everyone who was checked in at the same time is notified automatically."</item> + <item>"The CWA now enables you to check in to events and places. Event organizers and store owners can use the app to create a QR code. Guests can then scan this QR code when they arrive to register their presence. The app can also create a journal entry upon request. If someone who checked in to this event or place is diagnosed with coronavirus later, other people who were checked in at the same time can be warned automatically."</item> </string-array> <!-- XTXT: Text labels that will be converted to Links --> @@ -35,4 +35,4 @@ <item/> </string-array> -</resources> +</resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 0d6a785821099345fbd57d5192707d9d26b2a4f6..530da0471d5301ce113b1209ffc9c87584c6f478 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1,5 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2" tools:ignore="MissingTranslation"> + + <!-- #################################### + Non translatable + ###################################### --> + + <!-- XTXT: Path to the full blown legal html, currently not translated --> + <string name="information_technical_html_path" translatable="false">"technical.html"</string> + <!-- #################################### Generics ###################################### --> @@ -31,14 +39,10 @@ <!-- #################################### Notification ###################################### --> - <!-- NOTR --> - <string name="notification_channel_id"><xliff:g id="notification_channel_id">"de.rki.coronawarnapp.notification.exposureNotificationChannelId"</xliff:g></string> - <!-- NOTR --> - <string name="notification_id"><xliff:g id="notification_id">"1"</xliff:g></string> <!-- XHED: Title of the notification channel for general CWA notifications (e.g. risk change) --> - <string name="general_notification_channel_title">"Allgemein"</string> + <string name="general_notification_channel_title">"General"</string> <!-- XTXT: Description of the notification channel for general CWA notifications (e.g. risk change) --> - <string name="general_notification_channel_description">"Erinnerungen, Hinweise und allgemeine Benachrichtigungen, z.B. zu Ihrem Risikostatus."</string> + <string name="general_notification_channel_description">"Reminders, notes, and general notifications – about your risk status, for example."</string> <!-- XHED: Notification title --> <string name="notification_headline">"Corona-Warn-App"</string> <!-- XTXT: Notification body --> @@ -307,12 +311,12 @@ <!-- XHED: risk details - infection period logged headling, below behaviors --> <string name="risk_details_subtitle_period_logged">"This period is included in the calculation."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> -<!-- Dialog part 1--> + <!-- Dialog part 1--> <string name="risk_details_information_body_period_logged">"Your risk of infection can be calculated only for periods during which exposure logging was active. The logging feature should therefore remain active permanently. Exposure logging covers the last 14 days."</string> <!-- XTXT: risk details - infection period logged information body, under 14 days --> - <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> + <string name="risk_details_information_body_period_logged_assessment_under_14_days">"The Corona-Warn-App was installed %s days ago. Your risk of infection is calculated for the periods during which exposure logging was active. If you have encountered other people and exposure logging was active, your risk of infection is calculated."</string> <!-- XTXT: risk details - infection period logged information body, over 14 days --> - <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active in times during which you encountered other people, your risk of infection can be calculated for this period."</string> + <string name="risk_details_information_body_period_logged_assessment_over_14_days">"If exposure logging was active at times during which you encountered other people, your risk of infection can be calculated for this period."</string> <!-- XHED: risk details - infection period logged information body, below behaviors --> <!-- XTXT: risk details - infection period logged information body, under 14 days --> <string name="risk_details_information_body_period_logged_assessment">"The app automatically deletes older logs, as these are no longer relevant for infection prevention."</string> <!-- XTXT: risk details - infection period days logged/14 --> @@ -327,6 +331,8 @@ <string name="risk_details_information_body_low_risk">"You have a low risk of infection because no exposure to people later diagnosed with coronavirus was logged, or because your encounters were only for a short time and at a greater distance."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"The risk of infection is calculated locally on your smartphone, using exposure logging data. The calculation also takes into account distance and duration of any exposure to persons diagnosed with coronavirus, as well as their potential infectiousness. Your risk of infection cannot be seen by or passed on to anyone else."</string> + <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> + <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> <string name="risk_details_information_body_increased_risk_date">"You have an increased risk of infection because you were exposed over a longer period of time and at close proximity to at least one person diagnosed with coronavirus."</string> <!-- YTXT: risk details - increased risk explanation text with variable for day(s) since last contact --> @@ -353,7 +359,6 @@ <string name="risk_details_explanation_dialog_title">"Information about exposure logging functionality"</string> <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> <string name="risk_details_explanation_dialog_faq_body">"For further information, please see our FAQ page."</string> - <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Your Risk Status"</string> <!-- YTXT: risk details - deadman notification text --> @@ -582,8 +587,8 @@ <!-- #################################### - Onboarding sixteen include - ###################################### --> + Onboarding sixteen include + ###################################### --> <!-- XACT: onboarding(sixteen) title --> <string name="sixteen_title_text">"Age 16 Warning"</string> @@ -644,8 +649,6 @@ <string name="settings_tracing_status_connection_headline">"Open Internet connection"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if connection is disabled --> <string name="settings_tracing_status_connection_body">"Exposure logging requires an Internet connection to calculate your risk of infection. Please turn on Wi-Fi or mobile data in your device settings."</string> - <!-- XLNK: FAQ URL pointing to the faq page in german. Need to use the URL for english for all other languages--> - <string name="risk_details_explanation_faq_link">"https://www.coronawarn.app/en/faq/#encounter_but_green"</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card --> <string name="settings_tracing_status_connection_button">"Open Device Settings"</string> <!-- XTXT: settings(tracing) - explains the circle progress indicator to the right with the current value --> @@ -807,8 +810,6 @@ <string name="information_technical_title">"Legal Notices"</string> <!-- XACT: describes illustration --> <string name="information_technical_illustration_description">"A hand holds a smartphone displaying a large body of text on the screen. Next to the text is a balance scale that symbolizes legal notices."</string> - <!-- XTXT: Path to the full blown legal html, currently not translated --> - <string name="information_technical_html_path" translatable="false">"technical.html"</string> <!-- XHED: Page title for legal information page, also menu item / button text --> <string name="information_legal_title">"Imprint"</string> <!-- XHED: Headline for legal information page, publisher section --> @@ -834,9 +835,8 @@ <!-- XACT: describes illustration --> <string name="information_legal_illustration_description">"A hand holds a smartphone displaying a large body of text on the screen. Next to the text is a section symbol representing the imprint."</string> - <!-- #################################### - App Information - Bug Reporting + App Information - Bug Reporting ###################################### --> <!-- XHED: Headline for debug log screen --> @@ -1131,6 +1131,7 @@ <!-- YTXT: Description for illustration in submission onboarding--> <string name="submission_intro_illustration_description">"An encrypted positive test diagnosis is transmitted to the system, which will now warn other users."</string> + <!-- Dispatcher --> <!-- XHED: Page headline for dispatcher menu --> <string name="submission_dispatcher_headline">"Retrieve Test Result"</string> @@ -1149,7 +1150,6 @@ <!-- YTXT: Body text for TAN code dispatcher option --> <string name="submission_dispatcher_tan_code_card_text">"Do you have a TAN? Tap here to enter your TAN so you can warn others. "</string> <!-- YTXT: Dispatcher text for TELE-TAN option --> - <!-- YTXT: Dispatcher text for TELE-TAN option --> <string name="submission_dispatcher_card_tan_tele">"No TAN yet?"</string> <!-- YTXT: Body text for TELE_TAN dispatcher option --> <string name="submission_dispatcher_tan_tele_card_text">"Call us and get a TAN."</string> @@ -1352,12 +1352,12 @@ <string name="submission_status_card_body_positive">"You have been diagnosed positive for SARS-CoV-2."</string> <!-- YTXT: Body text for submission status: negative --> <string name="submission_status_card_body_negative">"You have been diagnosed negative for SARS-CoV-2."</string> + <!-- YTXT: Body text for submission status fetch failed --> + <string name="submission_status_card_body_failed">"Your test is more than 21 days old and is therefore no longer relevant. Please delete the test. You can then add another."</string> <!-- YTXT: Body text for submission status fetch ready --> <string name="submission_status_card_body_ready">"If you have tested positive for coronavirus, you can warn others."</string> <!-- YTXT: Button text for submission status fetch ready --> <string name="submission_status_card_retrieve_test_result">"Retrieve Test Result"</string> - <!-- YTXT: Body text for submission status fetch failed --> - <string name="submission_status_card_body_failed">"Your test is more than 21 days old and is therefore no longer relevant. Please delete the test. You can then add another."</string> <!-- XBUT: submission status card unregistered button --> <string name="submission_status_card_button_unregistered">"Next Steps"</string> <!-- XBUT: submission status card show results button --> @@ -1482,8 +1482,8 @@ <string name="submission_your_consent_agreement_details">"Detailed Information on Data Processing and Your Consent"</string> <!-- #################################### - Statistics - ###################################### --> + Statistics + ###################################### --> <!-- Incidence statistics card --> <!-- XHED: Title for incident statistics card --> @@ -1574,7 +1574,7 @@ <string name="statistics_explanation_trend_stable_title">"Trend: Steady"</string> <!-- XHED: Explanation screen trend description --> <string name="statistics_explanation_trend_description">"The assessment of the trend for warnings by app users changes depending on current infection levels, which is why this trend is always displayed as neutral."</string> - <!-- XTXT: Explains user about statistics: URL, has to be "translated" into english (relevant for all languages except german)0 - https://www.coronawarn.app/en/faq/#further_details --> + <!-- XTXT: Explains user about statistics: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> <string name="statistics_explanation_faq_url">"https://www.coronawarn.app/en/faq/#further_details"</string> <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"Abstract picture of a smartphone with four placeholders for information"</string> @@ -1584,7 +1584,6 @@ <!-- XTXT: Statistics Card Navigation Announcement --> <string name="accessibility_statistics_card_navigation_information">"Swipe horizontally between the tiles to display more statistics"</string> - <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1824,13 +1823,13 @@ <string name="statistics_reproduction_title">"Currently"</string> <!-- XTXT: Caption for statistics reproduction card --> <string name="statistics_reproduction_average">"average infections by each infected person"</string> - <!-- XTXT: Statistics trend increasing (Accessibilty) --> <string name="statistics_trend_increasing">"Trend: Upwards"</string> <!-- XTXT: Statistics trend decreasing (Accessibilty) --> <string name="statistics_trend_decreasing">"Trend: Downwards"</string> <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">"Trend: Steady"</string> + <!-- XHED: Title for BottomNav main screen title --> <string name="bottom_nav_home_title">"Start Screen"</string> <!-- XHED: Title for BottomNav diary screen title --> @@ -1987,17 +1986,17 @@ <!-- XACT: Button/Dialog label to consent--> <string name="trace_location_onboarding_body_confirm">"Accept"</string> <!-- XHED: Trace location check-ins camera permission card title--> - <string name="trace_location_attendee_camera_card_title">Zugriff auf Kamera erlauben</string> + <string name="trace_location_attendee_camera_card_title">"Allow Access to Camera"</string> <!-- XTXT: Trace location check-ins camera permission card susbtitle--> - <string name="trace_location_attendee_camera_card_subtitle">Um die Kamera für das Scannen des QR-Codes zu nutzen, müssen Sie die Verwendung der Kamera in den Geräte-Einstellungen zur App zulassen.</string> + <string name="trace_location_attendee_camera_card_subtitle">"To use your smartphone camera to scan QR codes, you have to allow access to the camera in the device settings for the app."</string> <!-- XBUT: Trace location check-ins camera permission card button--> - <string name="trace_location_attendee_camera_card_button">Einstellungen öffnen</string> + <string name="trace_location_attendee_camera_card_button">"Open Settings"</string> <!-- YMSG: Trace location onboarding image description--> - <string name="trace_location_onboarding_content_description">Drei Personen an einem Stehtisch, zwei von ihnen schauen auf ihr Smartphone.</string> + <string name="trace_location_onboarding_content_description">"Three persons are standing at a high table, two of them are looking at their smartphones."</string> <!-- XMIT: Trace location poster print --> <string name="trace_location_organiser_poster_print">"Print"</string> <!-- XMIT: Trace location poster share --> <string name="trace_location_organiser_poster_share">"Share"</string> <!-- XHED: Trace location poster title --> - <string name="trace_location_organiser_poster_title">"Print version"</string> -</resources> + <string name="trace_location_organiser_poster_title">"Print Version"</string> +</resources> \ No newline at end of file diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt index e8ee5c3275b31cd946d5dfae4556b7d023127e3a..6706173c4975ad8508aa49a7fe7f77c3c0aadc6d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.contactdiary.ui.edit import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationEntity import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocationId import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.Runs @@ -30,7 +31,7 @@ class ContactDiaryEditLocationsViewModelTest { override val phoneNumber: String? = null override val emailAddress: String? = null override val stableId = 1L - override val traceLocationID: String? = null + override val traceLocationID: TraceLocationId? = null } private val locationList = listOf(location) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt index 7c0754dd1a75fa657fe35e47ee9156b1cc95ef31..b50da8d26d837814fd363f8957e9e362ff9cb0e9 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewViewModelTest.kt @@ -35,6 +35,7 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import kotlinx.coroutines.flow.flowOf +import okio.ByteString.Companion.decodeBase64 import org.joda.time.DateTimeZone import org.joda.time.Instant import org.joda.time.LocalDate @@ -69,7 +70,7 @@ open class ContactDiaryOverviewViewModelTest { every { contactDiaryRepository.personEncounters } returns flowOf(emptyList()) every { riskLevelStorage.ewDayRiskStates } returns flowOf(emptyList()) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(emptyList()) - every { checkInRepository.allCheckIns } returns flowOf(emptyList()) + every { checkInRepository.checkInsWithinRetention } returns flowOf(emptyList()) mockStringsForContactDiaryExporterTests(context) every { timeStamper.nowUTC } returns Instant.ofEpochMilli(dateMillis) @@ -83,13 +84,13 @@ open class ContactDiaryOverviewViewModelTest { private val locationEventLowRisk = DefaultContactDiaryLocation( locationId = 456, locationName = "Jahrestreffen der deutschen SAP Anwendergruppe", - traceLocationID = "12ab-34cd-56ef-78gh-456" + traceLocationID = "12ab-34cd-56ef-78gh-456".decodeBase64() ) private val locationEventHighRisk = DefaultContactDiaryLocation( locationId = 457, locationName = "Kiosk", - traceLocationID = "12ab-34cd-56ef-78gh-457" + traceLocationID = "12ab-34cd-56ef-78gh-457".decodeBase64() ) private val locationEventLowRiskVisit = DefaultContactDiaryLocationVisit( @@ -361,7 +362,7 @@ open class ContactDiaryOverviewViewModelTest { every { riskLevelStorage.ewDayRiskStates } returns flowOf(listOf(aggregatedRiskPerDateResultLowRisk)) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(listOf(traceLocationCheckInRiskHigh)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationEventHighRiskVisit)) - every { checkInRepository.allCheckIns } returns flowOf(listOf(checkInHigh)) + every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(checkInHigh)) var item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date @@ -384,7 +385,7 @@ open class ContactDiaryOverviewViewModelTest { ) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(listOf(traceLocationCheckInRiskLow)) every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationEventLowRiskVisit)) - every { checkInRepository.allCheckIns } returns flowOf(listOf(checkInLow)) + every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(checkInLow)) item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date @@ -414,7 +415,7 @@ open class ContactDiaryOverviewViewModelTest { fun `low risk event by attending event with low risk`() { every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationEventLowRiskVisit)) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(listOf(traceLocationCheckInRiskLow)) - every { checkInRepository.allCheckIns } returns flowOf(listOf(checkInLow)) + every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(checkInLow)) val item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date @@ -436,7 +437,7 @@ open class ContactDiaryOverviewViewModelTest { fun `high risk event by attending event with high risk`() { every { contactDiaryRepository.locationVisits } returns flowOf(listOf(locationEventHighRiskVisit)) every { riskLevelStorage.traceLocationCheckInRiskStates } returns flowOf(listOf(traceLocationCheckInRiskHigh)) - every { checkInRepository.allCheckIns } returns flowOf(listOf(checkInHigh)) + every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(checkInHigh)) val item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date @@ -469,7 +470,7 @@ open class ContactDiaryOverviewViewModelTest { ) ) - every { checkInRepository.allCheckIns } returns flowOf(listOf(checkInHigh, checkInLow)) + every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(checkInHigh, checkInLow)) val item = createInstance().listItems.getOrAwaitValue().first { it is DayOverviewItem && it.date == date diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt index aee24dd7643b2551114c25038649cc427269aad3..f5b58f654c33d15067a76e6d8740474edce34cd7 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/attendee/confirm/ConfirmCheckInViewModelTest.kt @@ -14,6 +14,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import okio.ByteString.Companion.decodeBase64 +import org.joda.time.Duration import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -29,8 +30,6 @@ class ConfirmCheckInViewModelTest : BaseTest() { @MockK lateinit var checkInRepository: CheckInRepository @MockK lateinit var timeStamper: TimeStamper - private lateinit var viewModel: ConfirmCheckInViewModel - private val traceLocation = TraceLocation( id = 1, type = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_OTHER, @@ -51,23 +50,35 @@ class ConfirmCheckInViewModelTest : BaseTest() { coEvery { checkInRepository.addCheckIn(any()) } returns 1L every { verifiedTraceLocation.traceLocation } returns traceLocation every { timeStamper.nowUTC } returns Instant.parse("2021-03-04T10:30:00Z") + } + + private fun createInstance() = ConfirmCheckInViewModel( + verifiedTraceLocation = verifiedTraceLocation, + checkInRepository = checkInRepository, + timeStamper = timeStamper + ) - viewModel = ConfirmCheckInViewModel( - verifiedTraceLocation = verifiedTraceLocation, - checkInRepository = checkInRepository, - timeStamper = timeStamper - ) + @Test + fun onClose() = with(createInstance()) { + onClose() + events.getOrAwaitValue() shouldBe ConfirmCheckInNavigation.BackNavigation + } + + @Test + fun onConfirmEvent() = with(createInstance()) { + onConfirmTraceLocation() + events.getOrAwaitValue() shouldBe ConfirmCheckInNavigation.ConfirmNavigation } @Test - fun onClose() { - viewModel.onClose() - viewModel.events.getOrAwaitValue() shouldBe ConfirmCheckInNavigation.BackNavigation + fun `confirm button should be disabled when autoCheckOutLength is 0`() = with(createInstance()) { + durationUpdated(Duration.standardMinutes(0)) + uiState.getOrAwaitValue().confirmButtonEnabled shouldBe false } @Test - fun onConfirmEvent() { - viewModel.onConfirmTraceLocation() - viewModel.events.getOrAwaitValue() shouldBe ConfirmCheckInNavigation.ConfirmNavigation + fun `confirm button should be enabled when autoCheckOutLength is greater than 0`() = with(createInstance()) { + durationUpdated(Duration.standardMinutes(15)) + uiState.getOrAwaitValue().confirmButtonEnabled shouldBe true } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt index 0298f03aec66f49a628fb48937b3f1106e802387..23ea1802a9cfc3827af343301079606c0e160f46 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRepositoryTest.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.eventregistration.checkins import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity +import de.rki.coronawarnapp.util.TimeStamper import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations @@ -10,11 +11,13 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.impl.annotations.RelaxedMockK import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest import okio.ByteString.Companion.encode import org.joda.time.Instant @@ -27,6 +30,7 @@ class CheckInRepositoryTest : BaseTest() { @MockK lateinit var factory: TraceLocationDatabase.Factory @MockK lateinit var database: TraceLocationDatabase @MockK lateinit var checkInDao: CheckInDao + @RelaxedMockK lateinit var timeStamper: TimeStamper private val allEntriesFlow = MutableStateFlow(emptyList<TraceLocationCheckInEntity>()) @BeforeEach @@ -40,7 +44,7 @@ class CheckInRepositoryTest : BaseTest() { } } - private fun createInstance(scope: CoroutineScope) = CheckInRepository(factory) + private fun createInstance(scope: CoroutineScope) = CheckInRepository(factory, timeStamper) @Test fun `new entities should have ID 0`() = runBlockingTest { @@ -88,7 +92,8 @@ class CheckInRepositoryTest : BaseTest() { checkInStart = time, checkInEnd = end, completed = false, - createJournalEntry = false + createJournalEntry = false, + isSubmitted = true ) ) coVerify { @@ -108,7 +113,8 @@ class CheckInRepositoryTest : BaseTest() { checkInStart = time, checkInEnd = end, completed = false, - createJournalEntry = false + createJournalEntry = false, + isSubmitted = true, ) ) } @@ -152,7 +158,8 @@ class CheckInRepositoryTest : BaseTest() { checkInStart = start, checkInEnd = end, completed = false, - createJournalEntry = false + createJournalEntry = false, + isSubmitted = true, ) ) runBlockingTest { @@ -172,9 +179,51 @@ class CheckInRepositoryTest : BaseTest() { checkInStart = start, checkInEnd = end, completed = false, - createJournalEntry = false + createJournalEntry = false, + isSubmitted = true, ) ) } } + + @Test + fun `checkInsWithinRetention() should filter out stale check-ins`() = runBlockingTest { + + // Now = Jan 16th 2020, 00:00 + // CheckIns should be kept for 15 days, so every check-in with an end date before + // Jan 1st 2020, 00:00 should get deleted + every { timeStamper.nowUTC } returns Instant.parse("2020-01-16T00:00:00.000Z") + + val checkInWithinRetention = createCheckIn(Instant.parse("2020-01-01T00:00:00.000Z")) + + // should be filtered out + val staleCheckIn = createCheckIn(Instant.parse("2019-12-31T23:59:59.000Z")) + + every { checkInDao.allEntries() } returns flowOf( + listOf( + staleCheckIn.toEntity(), + checkInWithinRetention.toEntity() + ) + ) + + createInstance(scope = this).checkInsWithinRetention.first() shouldBe + listOf(checkInWithinRetention) + } + + private fun createCheckIn(checkOutDate: Instant) = CheckIn( + traceLocationId = "traceLocationId1".encode(), + version = 1, + type = 1, + description = "", + address = "", + traceLocationStart = null, + traceLocationEnd = null, + defaultCheckInLengthInMinutes = 30, + cryptographicSeed = "cryptographicSeed".encode(), + cnPublicKey = "cnPublicKey", + checkInStart = Instant.parse("1970-01-01T00:00:00.000Z"), + checkInEnd = checkOutDate, + completed = true, + createJournalEntry = true + ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRetentionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRetentionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c95a3d181f9774bf3f772bd239d6122dce82635b --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/CheckInRetentionTest.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.eventregistration.checkins + +import io.kotest.matchers.shouldBe +import okio.ByteString.Companion.encode +import org.joda.time.Instant +import org.junit.jupiter.api.Test + +internal class CheckInRetentionTest { + + @Test + fun `isWithinRetention() and isOutOfRetention() should return correct result`() { + + // Now = Jan 16th 2020, 00:00 + // CheckIns should be kept for 15 days, so every check-in with an end date before + // Jan 1st 2020 is out of retention + val now = Instant.parse("2020-01-16T00:00:00.000Z") + + val checkInWithinRetention = createCheckIn(Instant.parse("2020-01-01T00:00:00.000Z")) + val checkInOutOfRetention = createCheckIn(Instant.parse("2019-12-31T23:59:59.000Z")) + + checkInWithinRetention.isWithinRetention(now) shouldBe true + checkInWithinRetention.isOutOfRetention(now) shouldBe false + + checkInOutOfRetention.isWithinRetention(now) shouldBe false + checkInOutOfRetention.isOutOfRetention(now) shouldBe true + } + + private fun createCheckIn(checkOutDate: Instant) = CheckIn( + traceLocationId = "traceLocationId1".encode(), + version = 1, + type = 1, + description = "", + address = "", + traceLocationStart = null, + traceLocationEnd = null, + defaultCheckInLengthInMinutes = 30, + cryptographicSeed = "cryptographicSeed".encode(), + cnPublicKey = "cnPublicKey", + checkInStart = Instant.parse("1970-01-01T00:00:00.000Z"), + checkInEnd = checkOutDate, + completed = true, + createJournalEntry = true + ) +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/AutoCheckoutHelperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/AutoCheckoutHelperTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c0690025cf2d7d8acc53403175cfef1b61f830c3 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/checkins/qrcode/AutoCheckoutHelperTest.kt @@ -0,0 +1,46 @@ +package de.rki.coronawarnapp.eventregistration.checkins.qrcode + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource + +internal class AutoCheckoutHelperTest { + + @ParameterizedTest + @MethodSource("provideArguments") + fun `roundToNearest15Minutes() should round correctly`(testCase: TestCase) = with(testCase) { + roundToNearest15Minutes(minutesToRound) shouldBe expectedRoundingResult + } + + companion object { + @Suppress("unused") + @JvmStatic + fun provideArguments() = listOf( + TestCase( + minutesToRound = 0, + expectedRoundingResult = 0 + ), + TestCase( + minutesToRound = 7, + expectedRoundingResult = 0 + ), + TestCase( + minutesToRound = 8, + expectedRoundingResult = 15 + ), + TestCase( + minutesToRound = 22, + expectedRoundingResult = 15 + ), + TestCase( + minutesToRound = 23, + expectedRoundingResult = 30 + ) + ) + } + + data class TestCase( + val minutesToRound: Int, + val expectedRoundingResult: Int + ) +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultAutoCheckoutLengthTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultAutoCheckoutLengthTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..f5d72d1ae03d53902b8a71df0964a07857b97aae --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultAutoCheckoutLengthTest.kt @@ -0,0 +1,188 @@ +package de.rki.coronawarnapp.eventregistration.events + +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.getDefaultAutoCheckoutLengthInMinutes +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import io.kotest.matchers.shouldBe +import okio.ByteString.Companion.encode +import org.joda.time.Instant +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import testhelpers.BaseTest +import java.util.concurrent.TimeUnit + +class DefaultAutoCheckoutLengthTest : BaseTest() { + + @ParameterizedTest + @MethodSource("provideArguments") + fun `getDefaultAuthCheckoutLengthInMinutes(now) should return correct value`( + testCase: DefaultAutoCheckoutLengthTestCase + ) = with(testCase) { + + createTraceLocation(this) + .getDefaultAutoCheckoutLengthInMinutes(now) shouldBe expectedDefaultAutoCheckoutLength + } + + private fun createTraceLocation(testCase: DefaultAutoCheckoutLengthTestCase) = TraceLocation( + id = 1, + type = TraceLocationOuterClass.TraceLocationType.UNRECOGNIZED, + description = "", + address = "", + startDate = testCase.startDate, + endDate = testCase.endDate, + defaultCheckInLengthInMinutes = testCase.defaultCheckInLengthInMinutes, + cryptographicSeed = "seed byte array".encode(), + cnPublicKey = "cnPublicKey" + ) + + companion object { + + // min valid length = 00:15h + private const val MIN_VALID_LENGTH = 15 + + // max valid length = 23:45h + private val MAX_VALID_LENGTH = (TimeUnit.HOURS.toMinutes(23) + 45).toInt() + + @Suppress("unused") + @JvmStatic + fun provideArguments() = listOf( + DefaultAutoCheckoutLengthTestCase( + // now doesn't matter, as defaultCheckInLengthInMinutes is not null + now = Instant.parse("1970-01-01T00:00:00.000Z"), + defaultCheckInLengthInMinutes = 30, + startDate = null, + endDate = null, + expectedDefaultAutoCheckoutLength = 30, + ), + DefaultAutoCheckoutLengthTestCase( + // now doesn't matter here, as defaultCheckInLengthInMinutes is not null + now = Instant.parse("1970-01-01T00:00:00.000Z"), + // min valid length = 00:15h + defaultCheckInLengthInMinutes = 0, + startDate = null, + endDate = null, + expectedDefaultAutoCheckoutLength = MIN_VALID_LENGTH + ), + DefaultAutoCheckoutLengthTestCase( + // now doesn't matter here, as defaultCheckInLengthInMinutes is not null + now = Instant.parse("1970-01-01T00:00:00.000Z"), + // TraceLocations with CWA can actually only have 15 minute interval lengths. However, a trace location + // created by a third party could create arbitrary lengths. + // 22 min should be rounded to 15 + defaultCheckInLengthInMinutes = 22, + startDate = null, + endDate = null, + expectedDefaultAutoCheckoutLength = 15 + ), + DefaultAutoCheckoutLengthTestCase( + // now doesn't matter here, as defaultCheckInLengthInMinutes is not null + now = Instant.parse("1970-01-01T00:00:00.000Z"), + // TraceLocations with CWA can actually only have 15 minute interval lengths. However, a trace location + // created by a third party could create arbitrary lengths. + // 23 min should be rounded to 30 + defaultCheckInLengthInMinutes = 23, + startDate = null, + endDate = null, + expectedDefaultAutoCheckoutLength = 30 + ), + DefaultAutoCheckoutLengthTestCase( + // now doesn't matter here, as defaultCheckInLengthInMinutes is not null + now = Instant.parse("1970-01-01T00:00:00.000Z"), + // max valid length = 23:45h + defaultCheckInLengthInMinutes = MAX_VALID_LENGTH, + startDate = null, + endDate = null, + expectedDefaultAutoCheckoutLength = MAX_VALID_LENGTH + ), + DefaultAutoCheckoutLengthTestCase( + // now doesn't matter here, as defaultCheckInLengthInMinutes is not null + now = Instant.parse("1970-01-01T00:00:00.000Z"), + // max valid length = 23:45h + // TraceLocations with CWA can actually only have a max length of 23:45h. However, a trace location + // created by a third party could have a bigger length. + defaultCheckInLengthInMinutes = MAX_VALID_LENGTH + 1, + startDate = null, + endDate = null, + expectedDefaultAutoCheckoutLength = MAX_VALID_LENGTH + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-23T17:00:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // use 23:45h if user checks in earlier than 23:45 before the event ends + expectedDefaultAutoCheckoutLength = MAX_VALID_LENGTH + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-23T17:30:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // 23:30h in minutes + expectedDefaultAutoCheckoutLength = (TimeUnit.HOURS.toMinutes(23) + 30).toInt() + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T16:00:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + expectedDefaultAutoCheckoutLength = 60 + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T17:01:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + expectedDefaultAutoCheckoutLength = 15 + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T17:30:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // We take the min value when the user checks in after the event has already ended + expectedDefaultAutoCheckoutLength = 15 + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T16:31:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // Event ends in 29min -> we round to the nearest 15 minutes + expectedDefaultAutoCheckoutLength = 30 + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T16:38:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // Event ends in 22min -> we round to the nearest 15 minutes + expectedDefaultAutoCheckoutLength = 15 + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T16:59:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // Event ends in 1min -> we are not rounding to 0 but to the min value (15min) + expectedDefaultAutoCheckoutLength = 15 + ), + DefaultAutoCheckoutLengthTestCase( + now = Instant.parse("2021-12-24T17:00:00.000Z"), + defaultCheckInLengthInMinutes = null, + startDate = Instant.parse("2021-12-24T15:00:00.000Z"), + endDate = Instant.parse("2021-12-24T17:00:00.000Z"), + // We check in at event end, return min value (15min) + expectedDefaultAutoCheckoutLength = 15 + ) + ) + } +} + +data class DefaultAutoCheckoutLengthTestCase( + val now: Instant, + val defaultCheckInLengthInMinutes: Int?, + val startDate: Instant?, + val endDate: Instant?, + val expectedDefaultAutoCheckoutLength: Int, +) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt index fe715bcdc2a27941e2f4c7206a274a39039a0637..0aea9cdf1e398308995bde72f087fa3b094386ba 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateApiV1Test.kt @@ -114,4 +114,49 @@ class QrCodePosterTemplateApiV1Test : BaseIOTest() { headers["If-None-Match"] shouldBe "ETAG_OF_MOCKED_RESPONSE" } } + + @Test + fun `should return cached response when backend returns unsuccessful response`() { + + // Backend response contains Cache-Control header "public,max-age=300", therefore, okhttp should + // serve the cached response in case the backend returns an unsuccessful response for a subsequent request + + webServer.enqueue( + MockResponse() + .setBody("Poster Template") + .setResponseCode(200) + .setHeader("ETag", "ETAG_OF_MOCKED_RESPONSE") + .setHeader("Cache-Control", "public,max-age=300") + ) + + runBlocking { + createAPI().getQrCodePosterTemplate().apply { + // we should receive the body and ETag + code() shouldBe 200 + body()!!.string() shouldBe "Poster Template" + headers()["ETag"] shouldBe "ETAG_OF_MOCKED_RESPONSE" + headers()["Cache-Control"] shouldBe "public,max-age=300" + } + } + + webServer.takeRequest(5, TimeUnit.SECONDS) + + // Second response is unsuccessful ... + webServer.enqueue( + MockResponse() + .setResponseCode(500) + ) + + // ... and in this case, okhttp should serve the cached response + runBlocking { + createAPI().getQrCodePosterTemplate().apply { + code() shouldBe 200 + raw().cacheResponse shouldNotBe null + // cached poster template should be returned + body()!!.string() shouldBe "Poster Template" + headers()["ETag"] shouldBe "ETAG_OF_MOCKED_RESPONSE" + headers()["Cache-Control"] shouldBe "public,max-age=300" + } + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt index 15083e52e544f6bc9586f5963ca5f7ba1071eb78..68c3ee124e630adce7004a40f3f0454ce65a128d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/server/qrcodepostertemplate/QrCodePosterTemplateServerTest.kt @@ -23,7 +23,7 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { @MockK lateinit var defaultTemplateSource: DefaultQrCodePosterTemplateSource /** - * Info: [QrCodePosterTemplateApiV1Test] is testing if the ETag is set correctly + * Info: [QrCodePosterTemplateApiV1Test] is testing if okhttp caching is working correctly */ @BeforeEach @@ -31,7 +31,7 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { MockKAnnotations.init(this) every { signatureValidation.hasValidSignature(any(), any()) } returns true - every { defaultTemplateSource.getDefaultQrCodePosterTemplate() } returns "CACHE".toByteArray() + every { defaultTemplateSource.getDefaultQrCodePosterTemplate() } returns "DEFAULT TEMPLATE".toByteArray() } private fun createInstance() = QrCodePosterTemplateServer( @@ -47,13 +47,15 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { } returns Response.success(POSTER_BUNDLE.toResponseBody()) createInstance().downloadQrCodePosterTemplate().apply { - template.toStringUtf8().substring(0, 22) shouldBe "<vector xmlns:android=" - offsetX shouldBe 10.0f - offsetY shouldBe 10.0f - qrCodeSideLength shouldBe 100 + + // check if template contains the pdf by checking the first characters + template.toStringUtf8().substring(0, 8) shouldBe "%PDF-1.1" + offsetX shouldBe 0.16f + offsetY shouldBe 0.095f + qrCodeSideLength shouldBe 1000 with(descriptionTextBox) { - offsetX shouldBe 0.0f - offsetY shouldBe 0.0f + offsetX shouldBe 0.132f + offsetY shouldBe 0.61f width shouldBe 100 height shouldBe 20 fontSize shouldBe 10 @@ -65,14 +67,14 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { } @Test - fun `should fallback to cached or default template if signature is invalid`() = runBlockingTest { + fun `should fallback to default template if signature is invalid`() = runBlockingTest { every { signatureValidation.hasValidSignature(any(), any()) } returns false coEvery { api.getQrCodePosterTemplate() } returns Response.success(POSTER_BUNDLE.toResponseBody()) - createInstance().getTemplateFromApiOrCache() shouldBe "CACHE".toByteArray() + createInstance().getTemplateFromApiOrCache() shouldBe "DEFAULT TEMPLATE".toByteArray() } @Test @@ -87,12 +89,12 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { } @Test - fun `should fallback to cached or default template when response is not successful`() = runBlockingTest { + fun `should fallback to default template when response is not successful`() = runBlockingTest { coEvery { api.getQrCodePosterTemplate() } returns Response.error(404, "ERROR".toResponseBody()) - createInstance().getTemplateFromApiOrCache() shouldBe "CACHE".toByteArray() + createInstance().getTemplateFromApiOrCache() shouldBe "DEFAULT TEMPLATE".toByteArray() } companion object { @@ -101,8 +103,8 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { private val descriptionTextBox = QrCodePosterTemplate.QRCodePosterTemplateAndroid.QRCodeTextBoxAndroid.newBuilder() - .setOffsetX(10) - .setOffsetY(50) + .setOffsetX(0.132f) + .setOffsetY(0.61f) .setWidth(100) .setHeight(20) .setFontSize(10) @@ -110,38 +112,29 @@ internal class QrCodePosterTemplateServerTest : BaseTest() { .build() private val qrCodePosterTemplate = QrCodePosterTemplate.QRCodePosterTemplateAndroid.newBuilder() - .setOffsetX(10.0f) - .setOffsetY(10.0f) - .setQrCodeSideLength(100) + .setOffsetX(0.16f) + .setOffsetY(0.095f) + .setQrCodeSideLength(1000) .setDescriptionTextBox(descriptionTextBox) - .setTemplate( - ByteString.copyFromUtf8("""<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + - " xmlns:aapt=\"http://schemas.android.com/aapt\"\n" + - " android:width=\"595.3dp\"\n" + - " android:height=\"841.9dp\"\n" + - " android:viewportWidth=\"595.3\"\n" + - " android:viewportHeight=\"841.9\">\n" + - " <path\n" + - " android:pathData=\"M78.1,665.6v-12.8h1.2l3.6,8.5v-8.5h1.5v12.8h-1.1l-3.7,-8.7v8.7H78.1z\"\n" + - " android:fillColor=\"#404040\"/>\n" + - "</vector>\n""")) + .setTemplate(BINARY PDF) .build()*/ - // TODO update this bundle to send PDF file not XML private val POSTER_BUNDLE = ( - "504b03040a000000080014867d52008c85fefb000000ab0100000a0000006578706f72742e62" + - "696e7d90cf4bc33014c7071e949c0415bc0825bbc8685f9676edbad216440fbb78f61c9a6a8ae912da9089ff80ffb66957700e" + - "f185bcc3e7fbe3f0d0d7596eebcaa8cefb68e5aecfd88e77aae10516c6e88c90be1275cb7a983854aa254cbf93aeeec9c430f2" + - "dc4c71a6cdff59673804269aed1b6e4481e34d0c11d7bf3551376fc215a62b0a9b53d136f55eabcebc1c15fcedd81ed7e0d279" + - "72cd8c18bd3fee013d31c30afcbc4e81fa49124362031a422a28843282c44f21b6815b0ec47654020a540611ac7dc7d7d6fded" + - "90fec427edaf8d948f4aaaaec0f3d572789894282787eb97e86636f31eee86e5f1c5d505ba0c6fb9777d8fc2f3f9729c6f504b" + - "03040a000000080014867d528a1d0eac8f0000008a0000000a0000006578706f72742e736967018a0075ff0a87010a380a1864" + - "652e726b692e636f726f6e617761726e6170702d6465761a02763122033236322a13312e322e3834302e31303034352e342e33" + - "2e321001180122473045022100c251eb5e62282e5573fdb915edf61115d61d020354a510bed66b7b8ce482a38d02202a793775" + - "0958155c82a17acb6dd4b666afc1566285ef532e6e8c11e1d52e5a75504b01020a000a000000080014867d52008c85fefb0000" + - "00ab0100000a0000000000000000000000a401000000006578706f72742e62696e504b01020a000a000000080014867d528a1d" + - "0eac8f0000008a0000000a0000000000000000000000a401230100006578706f72742e736967504b0506000000000200020070" + - "000000da0100000000" + "504b03040a0000000800685588525c24ae70900100000e0300000a0000006578706f72742e62696e6d52cd4ac3401006410a0bde2c" + + "28280c9442556a9226db7aa8155a5b04154b1af0507bd89a6d8da45949b6507d099fc293275fa13e840fe0c527f0ec6e7e6ada" + + "744f33df7edf7c33b38bbe378bddf34e593bd65071fe36fff87c47480315d8f01101d4eba058cf4f149416e1c465638189a374" + + "c9980650113c53208d06a29e2d15a8b2461a9263e1a56307d0d7a57010432d36f5386871764d6d8734d90cfaaa20e9aa0a9a61" + + "0c964df4b449da25aa21639f8a9a497f2166d2804dfdfba493a8c10ef378922f302d8d246864b2ca8f8fd29b0e794890b48c5e" + + "329a24a0522d28ce840665934d88b74a1433aecd16915896c7c5680118d9dd1b4bbbbfa2de983f00c69212709f9289b8695a61" + + "2531246827608dc24c6edab2c3b074415d97c12df35dfb002c59ac6d4987b842e235f3e908a98091ba3850c558c7308205261c" + + "a21bef1fabd5329856cbf20c9ce2719f382ef593f7361913ff25fdb63de7858a5e201a95f83c6c0f57312a16db371d94475f85" + + "c6feddebfc147e7287bb5b1b47b946feb7b477b663c37609557285c8f50f504b03040a00000008006855885218d81ad88f0000" + + "008a0000000a0000006578706f72742e736967018a0075ff0a87010a380a1864652e726b692e636f726f6e617761726e617070" + + "2d6465761a02763122033236322a13312e322e3834302e31303034352e342e332e3210011801224730450220148d01176d9be9" + + "8fa78ca1b0cad0b8b12033f35a43cb7d10369536c233701fff022100ed7c748e7f8e82c6a98ead3a19f2041b1ec090268e3ae1" + + "5aa146bd0d567617c7504b01020a000a0000000800685588525c24ae70900100000e0300000a0000000000000000000000a401" + + "000000006578706f72742e62696e504b01020a000a00000008006855885218d81ad88f0000008a0000000a0000000000000000" + + "000000a401b80100006578706f72742e736967504b05060000000002000200700000006f0200000000" ).decodeHex() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..89c9e4da754a01c06001e2e7e9fe2a4976054553 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepositoryTest.kt @@ -0,0 +1,74 @@ +package de.rki.coronawarnapp.eventregistration.storage.repo + +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase +import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao +import de.rki.coronawarnapp.eventregistration.storage.entity.toTraceLocationEntity +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import de.rki.coronawarnapp.util.TimeStamper +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import okio.ByteString.Companion.encode +import org.joda.time.Instant +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +internal class DefaultTraceLocationRepositoryTest : BaseTest() { + + @MockK lateinit var factory: TraceLocationDatabase.Factory + @MockK lateinit var database: TraceLocationDatabase + @MockK lateinit var appScope: CoroutineScope + @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var traceLocationDao: TraceLocationDao + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + every { factory.create() } returns database + every { database.traceLocationDao() } returns traceLocationDao + } + + private fun createInstance() = DefaultTraceLocationRepository(factory, appScope, timeStamper) + + @Test + fun `getTraceLocationsWithinRetention() should filter out stale trace locations`() = runBlockingTest { + + // Now = Jan 16th 2020, 00:00 + // TraceLocations should be kept for 15 days, so every TraceLocation with an end date before + // Jan 1st 2020, 00:00 should get deleted + every { timeStamper.nowUTC } returns Instant.parse("2020-01-16T00:00:00.000Z") + + val traceLocationWithinRetention = createTraceLocationWithEndDate(Instant.parse("2020-01-01T00:00:00.000Z")) + + // should be filtered out + val staleTraceLocation = createTraceLocationWithEndDate(Instant.parse("2019-12-31T23:59:59.000Z")) + + every { traceLocationDao.allEntries() } returns flowOf( + listOf( + staleTraceLocation.toTraceLocationEntity(), + traceLocationWithinRetention.toTraceLocationEntity() + ) + ) + + createInstance().traceLocationsWithinRetention.first() shouldBe listOf(traceLocationWithinRetention) + } + + private fun createTraceLocationWithEndDate(endDate: Instant?) = TraceLocation( + id = 1, + type = TraceLocationOuterClass.TraceLocationType.UNRECOGNIZED, + description = "", + address = "", + startDate = null, + endDate = endDate, + defaultCheckInLengthInMinutes = 30, + cryptographicSeed = "seed byte array".encode(), + cnPublicKey = "cnPublicKey" + ) +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationRetentionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationRetentionTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..d0f58ebbc8de7827753a9d2f70cffd5423ec0b68 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/retention/TraceLocationRetentionTest.kt @@ -0,0 +1,46 @@ +package de.rki.coronawarnapp.eventregistration.storage.retention + +import de.rki.coronawarnapp.eventregistration.checkins.qrcode.TraceLocation +import de.rki.coronawarnapp.server.protocols.internal.pt.TraceLocationOuterClass +import io.kotest.matchers.shouldBe +import okio.ByteString.Companion.encode +import org.joda.time.Instant +import org.junit.jupiter.api.Test + +internal class TraceLocationRetentionTest { + + @Test + fun `isWithinRetention() and isOutOfRetention() should return correct result`() { + + // Now = Jan 16th 2020, 00:00 + // TraceLocations should be kept for 15 days, so every trace location with and end date before + // Jan 1st 2020, 00:00 should be out of retention + val now = Instant.parse("2020-01-16T00:00:00.000Z") + + val traceLocationWithinRetention = createTraceLocation(Instant.parse("2020-01-01T00:00:00.000Z")) + val traceLocationOutOfRetention = createTraceLocation(Instant.parse("2019-12-31T23:59:59.000Z")) + val traceLocationNoEndDate = createTraceLocation(null) + + traceLocationWithinRetention.isWithinRetention(now) shouldBe true + traceLocationWithinRetention.isOutOfRetention(now) shouldBe false + + traceLocationOutOfRetention.isWithinRetention(now) shouldBe false + traceLocationOutOfRetention.isOutOfRetention(now) shouldBe true + + // trace locations without end date are never out of retention + traceLocationNoEndDate.isWithinRetention(now) shouldBe true + traceLocationNoEndDate.isOutOfRetention(now) shouldBe false + } + + private fun createTraceLocation(endDate: Instant?) = TraceLocation( + id = 1, + type = TraceLocationOuterClass.TraceLocationType.UNRECOGNIZED, + description = "", + address = "", + startDate = null, + endDate = endDate, + defaultCheckInLengthInMinutes = 30, + cryptographicSeed = "seed byte array".encode(), + cnPublicKey = "cnPublicKey" + ) +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt index 349174209a5e60b842f29743c31c5be66cbee914..e456fa43ae2757a5bfa043e2ff923cc1cbb8b57f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/MainActivityViewModelTest.kt @@ -44,7 +44,7 @@ class MainActivityViewModelTest : BaseTest() { every { environmentSetup.currentEnvironment } returns EnvironmentSetup.Type.WRU every { traceLocationSettings.onboardingStatus } returns TraceLocationSettings.OnboardingStatus.NOT_ONBOARDED every { onboardingSettings.isBackgroundCheckDone } returns true - every { checkInRepository.allCheckIns } returns MutableStateFlow(listOf()) + every { checkInRepository.checkInsWithinRetention } returns MutableStateFlow(listOf()) } private fun createInstance(): MainActivityViewModel = MainActivityViewModel( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt index 04fbb3a38e611423435fd717cc6e31d76f76e096..07007ff99eeed66a36d4c651f91d3a51c8904be0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/CheckOutHandlerTest.kt @@ -1,14 +1,16 @@ package de.rki.coronawarnapp.presencetracing.checkins.checkout -import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.util.TimeStamper import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.runs import kotlinx.coroutines.test.runBlockingTest import okio.ByteString.Companion.encode import org.joda.time.Instant @@ -20,7 +22,7 @@ class CheckOutHandlerTest : BaseTest() { @MockK lateinit var repository: CheckInRepository @MockK lateinit var timeStamper: TimeStamper - @MockK lateinit var diaryRepository: ContactDiaryRepository + @MockK lateinit var contactJournalCheckInEntryCreator: ContactJournalCheckInEntryCreator private val testCheckIn = CheckIn( id = 42L, @@ -39,6 +41,12 @@ class CheckOutHandlerTest : BaseTest() { completed = false, createJournalEntry = true ) + + private val testCheckInDontCreate = testCheckIn.copy( + id = 43L, + createJournalEntry = false + ) + private var updatedCheckIn: CheckIn? = null private val nowUTC = Instant.ofEpochMilli(50) @@ -52,12 +60,19 @@ class CheckOutHandlerTest : BaseTest() { val callback: (CheckIn) -> CheckIn = arg(1) updatedCheckIn = callback(testCheckIn) } + + coEvery { repository.updateCheckIn(43, any()) } coAnswers { + val callback: (CheckIn) -> CheckIn = arg(1) + updatedCheckIn = callback(testCheckInDontCreate) + } + + coEvery { contactJournalCheckInEntryCreator.createEntry(any()) } just runs } private fun createInstance() = CheckOutHandler( repository = repository, timeStamper = timeStamper, - diaryRepository = diaryRepository, + contactJournalCheckInEntryCreator = contactJournalCheckInEntryCreator ) @Test @@ -68,7 +83,37 @@ class CheckOutHandlerTest : BaseTest() { checkInEnd = nowUTC, completed = true ) - // TODO journal creation + + coVerify(exactly = 1) { + contactJournalCheckInEntryCreator.createEntry(any()) + } + // TODO cancel auto checkouts } + + @Test + fun `Creates entry if create journal entry is true`() = runBlockingTest { + createInstance().apply { + checkOut(42) + } + + updatedCheckIn?.createJournalEntry shouldBe true + + coVerify(exactly = 1) { + contactJournalCheckInEntryCreator.createEntry(any()) + } + } + + @Test + fun `Does not create entry if create journal entry is false`() = runBlockingTest { + createInstance().apply { + checkOut(43) + } + + updatedCheckIn?.createJournalEntry shouldBe false + + coVerify(exactly = 0) { + contactJournalCheckInEntryCreator.createEntry(any()) + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..58834e6c7b70de6ac2f0e5a7452baabbd0c29e86 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/checkins/checkout/ContactJournalCheckInEntryCreatorTest.kt @@ -0,0 +1,87 @@ +package de.rki.coronawarnapp.presencetracing.checkins.checkout + +import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn +import io.mockk.MockKAnnotations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.runs +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import okio.ByteString.Companion.encode +import org.joda.time.Instant +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class ContactJournalCheckInEntryCreatorTest : BaseTest() { + + @MockK lateinit var contactDiaryRepo: ContactDiaryRepository + + private val testCheckIn = CheckIn( + id = 42L, + traceLocationId = "traceLocationId1".encode(), + version = 1, + type = 1, + description = "Restaurant", + address = "Around the corner", + traceLocationStart = Instant.parse("2021-03-04T22:00+01:00"), + traceLocationEnd = Instant.parse("2021-03-04T23:00+01:00"), + defaultCheckInLengthInMinutes = null, + cryptographicSeed = "cryptographicSeed".encode(), + cnPublicKey = "cnPublicKey", + checkInStart = Instant.parse("2021-03-04T22:00+01:00"), + checkInEnd = Instant.parse("2021-03-04T23:00+01:00"), + completed = false, + createJournalEntry = true + ) + + private val testLocation = DefaultContactDiaryLocation( + locationId = 123L, + locationName = "${testCheckIn.description}, ${testCheckIn.address}, ${testCheckIn.traceLocationStart} - ${testCheckIn.traceLocationEnd}", + traceLocationID = testCheckIn.traceLocationId + ) + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + every { contactDiaryRepo.locations } returns flowOf(emptyList()) + + every { contactDiaryRepo.locationVisits } returns flowOf(emptyList()) + + coEvery { contactDiaryRepo.addLocationVisit(any()) } just runs + + coEvery { contactDiaryRepo.addLocation(any()) } returns testLocation + } + + private fun createInstance() = ContactJournalCheckInEntryCreator( + diaryRepository = contactDiaryRepo + ) + + @Test + fun `Creates location if missing`() = runBlockingTest { + every { contactDiaryRepo.locations } returns flowOf(emptyList()) andThen flowOf(listOf(testLocation)) + + // Repo returns an empty list for the first call, so location is missing and a new location should be created and added + val instance = createInstance() + instance.createEntry(testCheckIn) + + coVerify(exactly = 1) { + contactDiaryRepo.addLocation(any()) + } + + // Location with trace location id already exists, so that location will be used + instance.createEntry(testCheckIn) + instance.createEntry(testCheckIn) + instance.createEntry(testCheckIn) + + coVerify(exactly = 1) { + contactDiaryRepo.addLocation(any()) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcherTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcherTest.kt index b8beb8b1106ba1b21dc47c0596ea523728adc571..52e215794f50a5b27cedbb9853e447bce06d214a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcherTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/CheckInWarningMatcherTest.kt @@ -252,4 +252,59 @@ class CheckInWarningMatcherTest : BaseTest() { } } } + + @Test + fun `we do not match our own CheckIns`() { + val checkIn1 = createCheckIn( + id = 2L, + traceLocationId = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871", + startDateStr = "2021-03-04T10:15+01:00", + endDateStr = "2021-03-04T10:17+01:00" + ) + val checkIn2 = createCheckIn( + id = 3L, + traceLocationId = "69eb427e1a48133970486244487e31b3f1c5bde47415db9b52cc5a2ece1e0060", + startDateStr = "2021-03-04T09:15+01:00", + endDateStr = "2021-03-04T10:12+01:00", + isSubmitted = true + ) + + val warning1 = createWarning( + traceLocationId = "fe84394e73838590cc7707aba0350c130f6d0fb6f0f2535f9735f481dee61871", + startIntervalDateStr = "2021-03-04T10:00+01:00", + period = 6, + transmissionRiskLevel = 8 + ) + + val warning2 = createWarning( + traceLocationId = "69eb427e1a48133970486244487e31b3f1c5bde47415db9b52cc5a2ece1e0060", + startIntervalDateStr = "2021-03-04T10:00+01:00", + period = 6, + transmissionRiskLevel = 8 + ) + + val warningPackage = object : TraceWarningPackage { + override suspend fun extractWarnings(): List<TraceWarning.TraceTimeIntervalWarning> { + return listOf(warning1, warning2) + } + + override val packageId: WarningPackageId + get() = "id" + } + + runBlockingTest { + val result = createInstance().process( + checkIns = listOf(checkIn1, checkIn2), + warningPackages = listOf(warningPackage) + ) + result.apply { + successful shouldBe true + processedPackages.single().warningPackage shouldBe warningPackage + processedPackages.single().apply { + overlaps.size shouldBe 1 + overlaps.any { it.checkInId == 2L } shouldBe true + } + } + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/OverlapTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/OverlapTest.kt index 67cdb5a057bde725542c8cd8712ade2d4aeb3ba0..bff78e2cc5b30c5fc13a732d15456d48d6f4f3f6 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/OverlapTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/calculation/OverlapTest.kt @@ -262,13 +262,32 @@ class OverlapTest : BaseTest() { traceWarningPackageId = id )!!.roundedMinutes shouldBe 5 } + + @Test + fun `returns null if it matches our own ssubmitted CheckIn`() { + createCheckIn( + traceLocationId = locationId, + startDateStr = "2021-03-04T10:15+01:00", + endDateStr = "2021-03-04T10:17+01:00", + isSubmitted = true, + ).calculateOverlap( + createWarning( + traceLocationId = locationId, + startIntervalDateStr = "2021-03-04T10:00+01:00", + period = 6, + transmissionRiskLevel = 8 + ), + traceWarningPackageId = id + ) shouldBe null + } } fun createCheckIn( id: Long = 1L, traceLocationId: String, startDateStr: String, - endDateStr: String + endDateStr: String, + isSubmitted: Boolean = false, ) = CheckIn( id = id, traceLocationId = traceLocationId.decodeHex(), @@ -284,7 +303,8 @@ fun createCheckIn( checkInStart = Instant.parse(startDateStr), checkInEnd = Instant.parse(endDateStr), completed = false, - createJournalEntry = false + createJournalEntry = false, + isSubmitted = isSubmitted ) fun createWarning( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt index e44656741f40654af7de030c9812598a591047f4..918257658101455d756ffaa64ddcf11ac0885cb4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/presencetracing/risk/execution/PresenceTracingWarningTaskTest.kt @@ -46,7 +46,7 @@ class PresenceTracingWarningTaskTest : BaseTest() { MockKAnnotations.init(this) every { timeStamper.nowUTC } returns Instant.ofEpochMilli(9000) - coEvery { syncTool.syncPackages() } returns mockk() + coEvery { syncTool.syncPackages() } returns TraceWarningPackageSyncTool.SyncResult(successful = true) coEvery { checkInWarningMatcher.process(any(), any()) } answers { CheckInWarningMatcher.Result( successful = true, @@ -64,7 +64,7 @@ class PresenceTracingWarningTaskTest : BaseTest() { coEvery { markPackagesProcessed(any()) } just Runs } - coEvery { checkInsRepository.allCheckIns } returns flowOf(listOf(CHECKIN_1, CHECKIN_2)) + coEvery { checkInsRepository.checkInsWithinRetention } returns flowOf(listOf(CHECKIN_1, CHECKIN_2)) presenceTracingRiskRepository.apply { coEvery { deleteAllMatches() } just Runs @@ -89,7 +89,7 @@ class PresenceTracingWarningTaskTest : BaseTest() { coVerifySequence { syncTool.syncPackages() presenceTracingRiskRepository.deleteStaleData() - checkInsRepository.allCheckIns + checkInsRepository.checkInsWithinRetention traceWarningRepository.unprocessedWarningPackages checkInWarningMatcher.process(any(), any()) @@ -120,14 +120,14 @@ class PresenceTracingWarningTaskTest : BaseTest() { @Test fun `there are no check-ins to match against`() = runBlockingTest { - coEvery { checkInsRepository.allCheckIns } returns flowOf(emptyList()) + coEvery { checkInsRepository.checkInsWithinRetention } returns flowOf(emptyList()) createInstance().run(mockk()) shouldNotBe null coVerifySequence { syncTool.syncPackages() presenceTracingRiskRepository.deleteStaleData() - checkInsRepository.allCheckIns + checkInsRepository.checkInsWithinRetention presenceTracingRiskRepository.deleteAllMatches() presenceTracingRiskRepository.reportCalculation(successful = true) @@ -143,13 +143,33 @@ class PresenceTracingWarningTaskTest : BaseTest() { coVerifySequence { syncTool.syncPackages() presenceTracingRiskRepository.deleteStaleData() - checkInsRepository.allCheckIns + checkInsRepository.checkInsWithinRetention traceWarningRepository.unprocessedWarningPackages presenceTracingRiskRepository.reportCalculation(successful = true) } } + @Test + fun `report failure if downloads fail`() = runBlockingTest { + coEvery { syncTool.syncPackages() } returns TraceWarningPackageSyncTool.SyncResult(successful = false) + + createInstance().run(mockk()) shouldNotBe null + + coVerifySequence { + syncTool.syncPackages() + + presenceTracingRiskRepository.reportCalculation( + successful = false, + overlaps = emptyList() + ) + } + + coVerify(exactly = 0) { + traceWarningRepository.markPackagesProcessed(any()) + } + } + @Test fun `report failure if matching throws exception`() = runBlockingTest { coEvery { checkInWarningMatcher.process(any(), any()) } throws IllegalArgumentException() @@ -160,7 +180,7 @@ class PresenceTracingWarningTaskTest : BaseTest() { coVerifySequence { syncTool.syncPackages() presenceTracingRiskRepository.deleteStaleData() - checkInsRepository.allCheckIns + checkInsRepository.checkInsWithinRetention traceWarningRepository.unprocessedWarningPackages checkInWarningMatcher.process(any(), any()) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt index 03c6758900c2520d87a5dbf7fa83f0224ad034a7..fdff4062dbc0aefef65a35dc69770f9fb3462280 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/BaseRiskLevelStorageTest.kt @@ -220,7 +220,7 @@ class BaseRiskLevelStorageTest : BaseTest() { riskLevelResults.size shouldBe 2 riskLevelResults[0].calculatedAt shouldBe calculatedAt - riskLevelResults[0].riskState shouldBe RiskState.INCREASED_RISK + riskLevelResults[0].riskState shouldBe RiskState.CALCULATION_FAILED riskLevelResults[1].calculatedAt shouldBe ewCalculatedAt riskLevelResults[1].riskState shouldBe RiskState.INCREASED_RISK @@ -241,7 +241,7 @@ class BaseRiskLevelStorageTest : BaseTest() { PtRiskLevelResult( calculatedAt = calculatedAt, presenceTracingDayRisk = null, - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.LOW_RISK ) ) ) @@ -303,12 +303,12 @@ class BaseRiskLevelStorageTest : BaseTest() { PtRiskLevelResult( calculatedAt = calculatedAt, presenceTracingDayRisk = null, - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.INCREASED_RISK ), PtRiskLevelResult( calculatedAt = calculatedAt.minus(400L), presenceTracingDayRisk = null, - riskState = RiskState.LOW_RISK + riskState = RiskState.CALCULATION_FAILED ) ) ) @@ -319,7 +319,7 @@ class BaseRiskLevelStorageTest : BaseTest() { riskLevelResults.size shouldBe 2 riskLevelResults[0].calculatedAt shouldBe ewCalculatedAt - riskLevelResults[0].riskState shouldBe RiskState.LOW_RISK + riskLevelResults[0].riskState shouldBe RiskState.INCREASED_RISK riskLevelResults[1].calculatedAt shouldBe ewCalculatedAt.minus(200L) riskLevelResults[1].riskState shouldBe RiskState.INCREASED_RISK diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt index a1099568aa19e32b4143ce8da45613664c804346..9e1122826a32e6f43db61015975e11d0421c3a17 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/storage/CombineRiskTest.kt @@ -17,7 +17,11 @@ class CombineRiskTest { @Test fun `combineRisk works`() { - val ptRisk = PresenceTracingDayRisk( + val ptRisk0 = PresenceTracingDayRisk( + localDateUtc = LocalDate(2021, 3, 19), + riskState = RiskState.LOW_RISK + ) + val ptRisk1 = PresenceTracingDayRisk( localDateUtc = LocalDate(2021, 3, 20), riskState = RiskState.INCREASED_RISK ) @@ -29,50 +33,81 @@ class CombineRiskTest { localDateUtc = LocalDate(2021, 3, 22), riskState = RiskState.CALCULATION_FAILED ) - val ewRisk = ExposureWindowDayRisk( - dateMillisSinceEpoch = Instant.parse("2021-03-22T14:00:00.000Z").millis, + val ptRisk4 = PresenceTracingDayRisk( + localDateUtc = LocalDate(2021, 3, 23), + riskState = RiskState.LOW_RISK + ) + val ptRisk5 = PresenceTracingDayRisk( + localDateUtc = LocalDate(2021, 3, 24), + riskState = RiskState.INCREASED_RISK + ) + + val ewRisk0 = ExposureWindowDayRisk( + dateMillisSinceEpoch = Instant.parse("2021-03-24T14:00:00.000Z").millis, + riskLevel = RiskLevel.HIGH, + 0, + 0 + ) + val ewRisk1 = ExposureWindowDayRisk( + dateMillisSinceEpoch = Instant.parse("2021-03-23T14:00:00.000Z").millis, riskLevel = RiskLevel.HIGH, 0, 0 ) val ewRisk2 = ExposureWindowDayRisk( + dateMillisSinceEpoch = Instant.parse("2021-03-22T14:00:00.000Z").millis, + riskLevel = RiskLevel.HIGH, + 0, + 0 + ) + val ewRisk3 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-19T14:00:00.000Z").millis, riskLevel = RiskLevel.LOW, 0, 0 ) - val ewRisk3 = ExposureWindowDayRisk( + val ewRisk4 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-20T14:00:00.000Z").millis, riskLevel = RiskLevel.UNSPECIFIED, 0, 0 ) - val ewRisk4 = ExposureWindowDayRisk( + val ewRisk5 = ExposureWindowDayRisk( dateMillisSinceEpoch = Instant.parse("2021-03-15T14:00:00.000Z").millis, riskLevel = RiskLevel.UNSPECIFIED, 0, 0 ) - val ptDayRiskList: List<PresenceTracingDayRisk> = listOf(ptRisk, ptRisk2, ptRisk3) - val ewDayRiskList: List<ExposureWindowDayRisk> = listOf(ewRisk, ewRisk2, ewRisk3, ewRisk4) + val ptDayRiskList: List<PresenceTracingDayRisk> = listOf(ptRisk0, ptRisk1, ptRisk2, ptRisk3, ptRisk4, ptRisk5) + val ewDayRiskList: List<ExposureWindowDayRisk> = listOf(ewRisk0, ewRisk1, ewRisk2, ewRisk3, ewRisk4, ewRisk5) val result = combineRisk(ptDayRiskList, ewDayRiskList) - result.size shouldBe 5 - result.find { + result.size shouldBe 7 + + result.single { it.localDate == LocalDate(2021, 3, 15) - }!!.riskState shouldBe RiskState.CALCULATION_FAILED - result.find { + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { it.localDate == LocalDate(2021, 3, 19) - }!!.riskState shouldBe RiskState.LOW_RISK - result.find { + }.riskState shouldBe RiskState.LOW_RISK + result.single { it.localDate == LocalDate(2021, 3, 20) - }!!.riskState shouldBe RiskState.INCREASED_RISK - result.find { + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { it.localDate == LocalDate(2021, 3, 21) - }!!.riskState shouldBe RiskState.LOW_RISK - result.find { + }.riskState shouldBe RiskState.LOW_RISK + result.single { + it.localDate == LocalDate(2021, 3, 22) + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { it.localDate == LocalDate(2021, 3, 22) - }!!.riskState shouldBe RiskState.INCREASED_RISK + }.riskState shouldBe RiskState.CALCULATION_FAILED + result.single { + it.localDate == LocalDate(2021, 3, 23) + }.riskState shouldBe RiskState.INCREASED_RISK + result.single { + it.localDate == LocalDate(2021, 3, 24) + }.riskState shouldBe RiskState.INCREASED_RISK } @Test @@ -85,7 +120,7 @@ class CombineRiskTest { ) val ptResult2 = PtRiskLevelResult( calculatedAt = startInstant.plus(3000L), - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.LOW_RISK ) val ptResult3 = PtRiskLevelResult( calculatedAt = startInstant.plus(6000L), @@ -99,7 +134,7 @@ class CombineRiskTest { val ptResults = listOf(ptResult, ptResult2, ptResult4, ptResult3) val ewResult = createEwRiskLevelResult( calculatedAt = startInstant.plus(2000L), - riskState = RiskState.CALCULATION_FAILED + riskState = RiskState.LOW_RISK ) val ewResult2 = createEwRiskLevelResult( calculatedAt = startInstant.plus(4000L), @@ -126,11 +161,11 @@ class CombineRiskTest { result[3].calculatedAt shouldBe startInstant.plus(5000L) result[4].riskState shouldBe RiskState.INCREASED_RISK result[4].calculatedAt shouldBe startInstant.plus(4000L) - result[5].riskState shouldBe RiskState.CALCULATION_FAILED + result[5].riskState shouldBe RiskState.LOW_RISK result[5].calculatedAt shouldBe startInstant.plus(3000L) result[6].riskState shouldBe RiskState.LOW_RISK result[6].calculatedAt shouldBe startInstant.plus(2000L) - result[7].riskState shouldBe RiskState.LOW_RISK + result[7].riskState shouldBe RiskState.CALCULATION_FAILED result[7].calculatedAt shouldBe startInstant.plus(1000L) } @@ -138,11 +173,11 @@ class CombineRiskTest { fun `max RiskState works`() { combine(RiskState.INCREASED_RISK, RiskState.INCREASED_RISK) shouldBe RiskState.INCREASED_RISK combine(RiskState.INCREASED_RISK, RiskState.LOW_RISK) shouldBe RiskState.INCREASED_RISK - combine(RiskState.INCREASED_RISK, RiskState.CALCULATION_FAILED) shouldBe RiskState.INCREASED_RISK + combine(RiskState.INCREASED_RISK, RiskState.CALCULATION_FAILED) shouldBe RiskState.CALCULATION_FAILED combine(RiskState.LOW_RISK, RiskState.INCREASED_RISK) shouldBe RiskState.INCREASED_RISK - combine(RiskState.CALCULATION_FAILED, RiskState.INCREASED_RISK) shouldBe RiskState.INCREASED_RISK + combine(RiskState.CALCULATION_FAILED, RiskState.INCREASED_RISK) shouldBe RiskState.CALCULATION_FAILED combine(RiskState.LOW_RISK, RiskState.LOW_RISK) shouldBe RiskState.LOW_RISK - combine(RiskState.CALCULATION_FAILED, RiskState.LOW_RISK) shouldBe RiskState.LOW_RISK + combine(RiskState.CALCULATION_FAILED, RiskState.LOW_RISK) shouldBe RiskState.CALCULATION_FAILED combine(RiskState.CALCULATION_FAILED, RiskState.CALCULATION_FAILED) shouldBe RiskState.CALCULATION_FAILED } } 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 2802029978aae963e691b4abaa25e40ec5cac9cd..778d26c72e6670b9339992f8fcdeb119a7fc115b 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,6 +4,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector +import de.rki.coronawarnapp.eventregistration.checkins.CheckIn import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.checkins.CheckInsTransformer import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException @@ -73,6 +74,25 @@ class SubmissionTaskTest : BaseTest() { private val settingLastUserActivityUTC: FlowPreference<Instant> = mockFlowPreference(Instant.EPOCH.plus(1)) + private val testCheckIn1 = CheckIn( + id = 1L, + traceLocationId = mockk(), + version = 1, + type = 2, + description = "brothers birthday", + address = "Malibu", + traceLocationStart = Instant.EPOCH, + traceLocationEnd = null, + defaultCheckInLengthInMinutes = null, + cryptographicSeed = mockk(), + cnPublicKey = "cnPublicKey", + checkInStart = Instant.EPOCH, + checkInEnd = Instant.EPOCH.plus(9000), + completed = false, + createJournalEntry = false, + isSubmitted = true + ) + @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -113,8 +133,7 @@ class SubmissionTaskTest : BaseTest() { every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardHours(1)) - every { checkInRepository.allCheckIns } returns flowOf(emptyList()) - coEvery { checkInRepository.clear() } just Runs + every { checkInRepository.checkInsWithinRetention } returns flowOf(listOf(testCheckIn1)) coEvery { checkInsTransformer.transform(any(), any()) } returns emptyList() } @@ -159,7 +178,7 @@ class SubmissionTaskTest : BaseTest() { settingSymptomsPreference.value tekHistoryCalculations.transformToKeyHistoryInExternalFormat(listOf(tek), userSymptoms) - checkInRepository.allCheckIns + checkInRepository.checkInsWithinRetention checkInsTransformer.transform(any(), any()) appConfigProvider.getAppConfig() @@ -177,10 +196,11 @@ class SubmissionTaskTest : BaseTest() { analyticsKeySubmissionCollector.reportSubmittedInBackground() tekHistoryStorage.clear() - checkInRepository.clear() submissionSettings.symptoms settingSymptomsPreference.update(match { it.invoke(mockk()) == null }) + checkInRepository.markCheckInAsSubmitted(testCheckIn1.id) + autoSubmission.updateMode(AutoSubmission.Mode.DISABLED) backgroundWorkScheduler.stopWorkScheduler() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt index daf12a7211a3c53bb238e1cb84de4f73bda21e4c..1be311fd3e44adab39c9b2480871a5471191892c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt @@ -87,7 +87,7 @@ class TracingDetailsItemProviderTest : BaseTest() { exposureWindows = listOf(exposureWindow) ) - every { combinedResult.ewRiskLevelResult } returns ewRiskLevelTaskResult + every { combinedResult.matchedRiskCount } returns matchedKeyCount val lastCombined = LastCombinedRiskResults( lastCalculated = combinedResult, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt index bfbd074700697d48cbfc6eebf01d6c887164530c..c1adf45da1f8b75e2e530e7cd7bb86f1d120bf61 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/eventregistration/attendee/checkins/CheckInsViewModelTest.kt @@ -46,7 +46,7 @@ class CheckInsViewModelTest : BaseTest() { fun setup() { MockKAnnotations.init(this) every { savedState.set(any(), any<String>()) } just Runs - every { checkInsRepository.allCheckIns } returns flowOf() + every { checkInsRepository.checkInsWithinRetention } returns flowOf() every { cameraPermissionProvider.deniedPermanently } returns flowOf(false) } @@ -120,7 +120,7 @@ class CheckInsViewModelTest : BaseTest() { } val checkIns = listOf(checkIn1, checkIn2, checkIn3, checkIn4) - every { checkInsRepository.allCheckIns } returns flowOf(checkIns) + every { checkInsRepository.checkInsWithinRetention } returns flowOf(checkIns) createInstance(deepLink = null, scope = this).apply { checkins.getOrAwaitValue().apply { @@ -155,7 +155,7 @@ class CheckInsViewModelTest : BaseTest() { every { completed } returns false } - every { checkInsRepository.allCheckIns } returns flowOf(listOf(checkIn)) + every { checkInsRepository.checkInsWithinRetention } returns flowOf(listOf(checkIn)) every { cameraPermissionProvider.deniedPermanently } returns flowOf(true) createInstance(deepLink = null, scope = this).apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt index 71a57686eb2e2999f67071e2bdb18e99af8e9b60..742faf11606a3d64d9aaf813fb81ce1a47744d93 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/DataResetTest.kt @@ -15,6 +15,7 @@ import de.rki.coronawarnapp.eventregistration.checkins.CheckInRepository import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker +import de.rki.coronawarnapp.presencetracing.warning.storage.TraceWarningRepository import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.storage.OnboardingSettings @@ -52,6 +53,7 @@ internal class DataResetTest : BaseTest() { @MockK lateinit var onboardingSettings: OnboardingSettings @MockK lateinit var submissionSettings: SubmissionSettings @MockK lateinit var traceLocationRepository: TraceLocationRepository + @MockK lateinit var traceWarningRepository: TraceWarningRepository @MockK lateinit var checkInRepository: CheckInRepository @MockK lateinit var traceLocationSettings: TraceLocationSettings @@ -82,7 +84,8 @@ internal class DataResetTest : BaseTest() { submissionSettings = submissionSettings, traceLocationRepository = traceLocationRepository, checkInRepository = checkInRepository, - traceLocationSettings = traceLocationSettings + traceLocationSettings = traceLocationSettings, + traceWarningRepository = traceWarningRepository ) @Test @@ -112,7 +115,7 @@ internal class DataResetTest : BaseTest() { coVerify(exactly = 1) { statisticsProvider.clear() } coVerify(exactly = 1) { bugReportingSettings.clear() } - + coVerify(exactly = 1) { traceWarningRepository.clear() } coVerify(exactly = 1) { traceLocationRepository.deleteAllTraceLocations() } coVerify(exactly = 1) { checkInRepository.clear() } } diff --git a/Server-Protocol-Buffer/src/main/proto/internal/pt/qr_code_poster_template.proto b/Server-Protocol-Buffer/src/main/proto/internal/pt/qr_code_poster_template.proto index 6fdbbcdd9957131b034b0d8c5c6cecda1b1d447d..65363477ac14139f8c9a6548c21120f7ed7be883 100644 --- a/Server-Protocol-Buffer/src/main/proto/internal/pt/qr_code_poster_template.proto +++ b/Server-Protocol-Buffer/src/main/proto/internal/pt/qr_code_poster_template.proto @@ -36,6 +36,7 @@ message QRCodePosterTemplateIOS { uint32 qrCodeSideLength = 4; QRCodeTextBoxIOS descriptionTextBox = 5; + QRCodeTextBoxIOS addressTextBox = 6; message QRCodeTextBoxIOS { uint32 offsetX = 1;