diff --git a/.circleci/config.yml b/.circleci/config.yml index 377608e5540ba8cd4b78840af7213ef0061fbb83..939ec23a96d88b817af35289015454d878139d38 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -493,11 +493,13 @@ jobs: - run: name: Create directory to store test results command: mkdir firebase-results + when: always - run: name: Install gsutil dependency and copy test results data command: | sudo pip install -U crcmod sudo gsutil -m cp -R -U gs://${GOOGLE_PROJECT_ID}-circleci-android/${BUCKETDIR}/flame* firebase-results + when: always - store_test_results: path: ./firebase-results/flame-29-de_DE-portrait - compress-path: diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index e874d14cd4f4398c236fa51413e1256675bcf8d2..9a830e0d3e7ecf1d54bf8b54c7bed6125f39aaa2 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -379,6 +379,7 @@ dependencies { androidTestImplementation 'androidx.work:work-testing:2.5.0' androidTestImplementation "io.mockk:mockk-android:1.10.4" debugImplementation 'androidx.fragment:fragment-testing:1.2.5' + debugImplementation 'androidx.test:core-ktx:1.3.0' androidTestImplementation 'tools.fastlane:screengrab:2.0.0' androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryDayFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryDayFragmentTest.kt index 584cdf3f7a3a9e361d6895d5092a843c69bdbf00..2202440649b5b02ff4377e738bb7e04853500402 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryDayFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryDayFragmentTest.kt @@ -33,14 +33,13 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 import testhelpers.selectTabAtPosition -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule import tools.fastlane.screengrab.locale.LocaleUtil import java.util.Locale @@ -87,7 +86,7 @@ class ContactDiaryDayFragmentTest : BaseUITest() { fun launch_fragment() { launchFragment2<ContactDiaryDayFragment>( fragmentArgs = fragmentArgs, - themeResId = R.style.AppTheme_ContactDiary + themeResId = R.style.AppTheme_Main ) } @@ -113,14 +112,12 @@ class ContactDiaryDayFragmentTest : BaseUITest() { launchFragmentInContainer2<ContactDiaryDayFragment>( fragmentArgs = fragmentArgs, - themeResId = R.style.AppTheme_ContactDiary + themeResId = R.style.AppTheme_Main ) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(ContactDiaryDayFragment::class.simpleName.plus("_$suffix")) - - onView(withId(R.id.contact_diary_day_tab_layout)).perform(selectTabAtPosition(1)) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(ContactDiaryDayFragment::class.simpleName.plus("_${suffix}_2")) + takeScreenshot<ContactDiaryDayFragment>(suffix) + onView(withId(R.id.contact_diary_day_tab_layout)) + .perform(selectTabAtPosition(1)) + takeScreenshot<ContactDiaryDayFragment>(suffix + "_2") } private fun setupViewModels() { diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditLocationsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditLocationsFragmentTest.kt index e073a7b837a511a71c0c66f1e39c944ba5090487..ad260c4e9e0884618f38e8da947ecfcf2c456dab 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditLocationsFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditLocationsFragmentTest.kt @@ -19,13 +19,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -73,9 +72,7 @@ class ContactDiaryEditLocationsFragmentTest : BaseUITest() { fun capture_screenshot() { every { viewModel.locationsLiveData } returns MutableLiveData(LOCATIONS_EDIT_LIST) launchFragmentInContainer2<ContactDiaryEditLocationsFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - - Screengrab.screenshot(ContactDiaryEditLocationsFragment::class.simpleName) + takeScreenshot<ContactDiaryEditLocationsFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditPersonsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditPersonsFragmentTest.kt index ad37d0d4ca767e599d186c318b096798f1e6267d..c05ad05c97322007d33746cae50efcd64f96eeb9 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditPersonsFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryEditPersonsFragmentTest.kt @@ -19,13 +19,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -73,8 +72,7 @@ class ContactDiaryEditPersonsFragmentTest : BaseUITest() { fun capture_screenshot() { every { viewModel.personsLiveData } returns MutableLiveData(PERSONS_EDIT_LIST) launchFragmentInContainer2<ContactDiaryEditPersonsFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(ContactDiaryEditPersonsFragment::class.simpleName) + takeScreenshot<ContactDiaryEditPersonsFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryInformationPrivacyFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryInformationPrivacyFragmentTest.kt index bcf8c04e2bb7e36e8b95817dd94fc37b1b3ae1a5..450d89231d4e06fe68a48b23781332f9d8531fcf 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryInformationPrivacyFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryInformationPrivacyFragmentTest.kt @@ -5,12 +5,11 @@ import de.rki.coronawarnapp.ui.information.InformationPrivacyFragment import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -31,7 +30,6 @@ class ContactDiaryInformationPrivacyFragmentTest { @Test fun capture_screenshot() { launchFragmentInContainer2<InformationPrivacyFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(InformationPrivacyFragment::class.simpleName) + takeScreenshot<InformationPrivacyFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOnboardingFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOnboardingFragmentTest.kt index 62bd3621a9a391bc67732ae2599d5f94cd5bdae2..5614d8687dcd51cfe7c62588b2961c88b092dd1a 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOnboardingFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOnboardingFragmentTest.kt @@ -4,6 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.contactdiary.ui.onboarding.ContactDiaryOnboardingFragment +import de.rki.coronawarnapp.contactdiary.ui.onboarding.ContactDiaryOnboardingFragmentArgs import de.rki.coronawarnapp.contactdiary.ui.onboarding.ContactDiaryOnboardingFragmentViewModel import io.mockk.MockKAnnotations import io.mockk.unmockkAll @@ -13,12 +14,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -30,6 +30,10 @@ class ContactDiaryOnboardingFragmentTest : BaseUITest() { @get:Rule val systemUIDemoModeRule = SystemUIDemoModeRule() + private val fragmentArgs = ContactDiaryOnboardingFragmentArgs( + showBottomNav = false + ).toBundle() + @Before fun setup() { MockKAnnotations.init(this, relaxed = true) @@ -50,15 +54,14 @@ class ContactDiaryOnboardingFragmentTest : BaseUITest() { @Test fun launch_fragment() { - launchFragment2<ContactDiaryOnboardingFragment>() + launchFragment2<ContactDiaryOnboardingFragment>(fragmentArgs) } @Screenshot @Test fun capture_screenshot() { - launchFragmentInContainer2<ContactDiaryOnboardingFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(ContactDiaryOnboardingFragment::class.simpleName) + launchFragmentInContainer2<ContactDiaryOnboardingFragment>(fragmentArgs) + takeScreenshot<ContactDiaryOnboardingFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOverviewFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOverviewFragmentTest.kt index 4c9895d6a8f26ae1de185e4d5feb36ca4be2cac8..cc87948f952b3e29fe077ad352413a49995280a5 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOverviewFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/contactdiary/ContactDiaryOverviewFragmentTest.kt @@ -1,48 +1,27 @@ package de.rki.coronawarnapp.ui.contactdiary -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragment import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewViewModel -import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.ListItem import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.task.TaskController -import de.rki.coronawarnapp.ui.contactdiary.DiaryData.DATA_ITEMS -import de.rki.coronawarnapp.ui.contactdiary.DiaryData.HIGH_RISK -import de.rki.coronawarnapp.ui.contactdiary.DiaryData.LOW_RISK import io.mockk.MockKAnnotations -import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.spyk import io.mockk.unmockkAll -import org.joda.time.LocalDate import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME -import testhelpers.Screenshot -import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 -import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab -import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) class ContactDiaryOverviewFragmentTest : BaseUITest() { - @Rule - @JvmField - val localeTestRule = LocaleTestRule() - - @get:Rule - val systemUIDemoModeRule = SystemUIDemoModeRule() @MockK lateinit var taskController: TaskController @MockK lateinit var contactDiaryRepository: ContactDiaryRepository @@ -79,27 +58,6 @@ class ContactDiaryOverviewFragmentTest : BaseUITest() { fun launch_fragment() { launchFragment2<ContactDiaryOverviewFragment>() } - - @Screenshot - @Test - fun capture_screenshot() { - every { viewModel.listItems } returns itemLiveData() - launchFragmentInContainer2<ContactDiaryOverviewFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(ContactDiaryOverviewFragment::class.simpleName) - } - - private fun itemLiveData(): LiveData<List<ListItem>> = - MutableLiveData( - (0 until ContactDiaryOverviewViewModel.DAY_COUNT) - .map { LocalDate.now().minusDays(it) } - .map { - ListItem(it).apply { - data.addAll(DATA_ITEMS) - risk = if (it.dayOfYear % 2 == 0) HIGH_RISK else LOW_RISK - } - } - ) } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..01e34360046775c23bd35ad9876bf7777b1632a0 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/MainActivityTest.kt @@ -0,0 +1,440 @@ +package de.rki.coronawarnapp.ui.main + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.test.core.app.launchActivity +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.Provides +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryWorkScheduler +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings +import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragment +import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewViewModel +import de.rki.coronawarnapp.contactdiary.ui.overview.adapter.ListItem +import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler +import de.rki.coronawarnapp.environment.EnvironmentSetup +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.notification.ShareTestResultNotificationService +import de.rki.coronawarnapp.risk.storage.RiskLevelStorage +import de.rki.coronawarnapp.statistics.source.StatisticsProvider +import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard +import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider +import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard +import de.rki.coronawarnapp.submission.ui.homecards.TestResultItem +import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard +import de.rki.coronawarnapp.task.TaskController +import de.rki.coronawarnapp.tracing.GeneralTracingStatus +import de.rki.coronawarnapp.tracing.states.TracingStateProvider +import de.rki.coronawarnapp.tracing.ui.homecards.TracingStateItem +import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState +import de.rki.coronawarnapp.ui.contactdiary.DiaryData +import de.rki.coronawarnapp.ui.main.home.HomeData +import de.rki.coronawarnapp.ui.main.home.HomeFragment +import de.rki.coronawarnapp.ui.main.home.HomeFragmentViewModel +import de.rki.coronawarnapp.ui.main.home.items.FAQCard +import de.rki.coronawarnapp.ui.main.home.items.HomeItem +import de.rki.coronawarnapp.ui.statistics.Statistics +import de.rki.coronawarnapp.util.CWADebug +import de.rki.coronawarnapp.util.device.BackgroundModeStatus +import de.rki.coronawarnapp.util.device.PowerManagement +import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.worker.BackgroundWorkScheduler +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.spyk +import kotlinx.coroutines.flow.flowOf +import org.joda.time.LocalDate +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.recyclerScrollTo +import testhelpers.selectBottomNavTab +import testhelpers.takeScreenshot +import timber.log.Timber +import tools.fastlane.screengrab.locale.LocaleTestRule + +@RunWith(AndroidJUnit4::class) +class MainActivityTest : BaseUITest() { + // HomeFragment mocks + @MockK lateinit var errorResetTool: EncryptionErrorResetTool + @MockK lateinit var tracingStatus: GeneralTracingStatus + @MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory + @MockK lateinit var submissionStateProvider: SubmissionStateProvider + @MockK lateinit var tracingRepository: TracingRepository + @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var cwaSettings: CWASettings + @MockK lateinit var appConfigProvider: AppConfigProvider + @MockK lateinit var statisticsProvider: StatisticsProvider + + // MainActivity mocks + @MockK lateinit var environmentSetup: EnvironmentSetup + @MockK lateinit var backgroundModeStatus: BackgroundModeStatus + @MockK lateinit var tracingStateProvider: TracingStateProvider + @MockK lateinit var diarySettings: ContactDiarySettings + + // ContactDiaryOverviewFragment mocks + @MockK lateinit var taskController: TaskController + @MockK lateinit var contactDiaryRepository: ContactDiaryRepository + @MockK lateinit var riskLevelStorage: RiskLevelStorage + + // ViewModels + private lateinit var mainActivityViewModel: MainActivityViewModel + private lateinit var homeFragmentViewModel: HomeFragmentViewModel + private lateinit var contactDiaryOverviewViewModel: ContactDiaryOverviewViewModel + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + @Before + fun setup() { + MockKAnnotations.init(this, true) + mockkObject(LocalData) + mockkObject(CWADebug) + mockkObject(BackgroundWorkScheduler) + // Common mocks + every { CWADebug.isDeviceForTestersBuild } returns false + every { environmentSetup.currentEnvironment } returns EnvironmentSetup.Type.PRODUCTION + every { LocalData.isBackgroundCheckDone() } returns true + every { LocalData.submissionWasSuccessful() } returns false + every { BackgroundWorkScheduler.startWorkScheduler() } just Runs + // Setup ViewModels + setupActivityViewModel() + setupHomeFragmentViewModel() + setupContactDiaryOverviewViewModel() + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + fun launchMainActivity() { + launchActivity<MainActivity>() + } + + @Screenshot + @Test + fun captureHomeFragmentLowRiskNoEncounters() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + HomeData.Tracing.LOW_RISK_ITEM_NO_ENCOUNTERS + ) + captureHomeFragment("low_risk_no_encounters") + + // also scroll down and capture a screenshot of the faq card + onView(withId(R.id.recycler_view)).perform(recyclerScrollTo()) + takeScreenshot<HomeFragment>("faq_card") + } + + @Screenshot + @Test + fun captureHomeFragmentLowRiskWithEncounters() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + HomeData.Tracing.LOW_RISK_ITEM_WITH_ENCOUNTERS + ) + captureHomeFragment("low_risk_with_encounters") + } + + @Screenshot + @Test + fun captureHomeFragmentIncreasedRisk() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + HomeData.Tracing.INCREASED_RISK_ITEM + ) + captureHomeFragment("increased_risk") + } + + @Screenshot + @Test + fun captureHomeFragmentTracingDisabled() { + every { homeFragmentViewModel.tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingInActive) + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + HomeData.Tracing.TRACING_DISABLED_ITEM + ) + captureHomeFragment("tracing_disabled") + } + + @Screenshot + @Test + fun captureHomeFragmentTracingProgressDownloading() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + HomeData.Tracing.TRACING_PROGRESS_ITEM + ) + captureHomeFragment("progress_downloading") + } + + @Screenshot + @Test + fun captureHomeFragmentTracingFailed() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + HomeData.Tracing.TRACING_FAILED_ITEM + ) + captureHomeFragment("tracing_failed") + } + + @Screenshot + @Test + fun captureHomeFragmentTestSubmissionDone() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_SUBMISSION_DONE_ITEM + ) + captureHomeFragment("submission_done") + } + + @Screenshot + @Test + fun captureHomeFragmentTestError() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_ERROR_ITEM + ) + captureHomeFragment("test_error") + } + + @Screenshot + @Test + fun captureHomeFragmentTestFetching() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_FETCHING_ITEM + ) + captureHomeFragment("test_fetching") + } + + @Screenshot + @Test + fun captureHomeFragmentTestInvalid() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_INVALID_ITEM + ) + captureHomeFragment("test_invalid") + } + + @Screenshot + @Test + fun captureHomeFragmentTestNegative() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_NEGATIVE_ITEM + ) + captureHomeFragment("test_negative") + } + + @Screenshot + @Test + fun captureHomeFragmentTestPositive() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_POSITIVE_ITEM + ) + captureHomeFragment("test_positive") + } + + @Screenshot + @Test + fun captureHomeFragmentTestPending() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData( + submissionTestResultItem = HomeData.Submission.TEST_PENDING_ITEM + ) + captureHomeFragment("test_pending") + } + + @Screenshot + @Test + fun captureHomeFragmentStatistics() { + every { homeFragmentViewModel.homeItems } returns homeFragmentItemsLiveData(HomeData.Tracing.LOW_RISK_ITEM_WITH_ENCOUNTERS) + launchActivity<MainActivity>() + onView(withId(R.id.recycler_view)).perform(recyclerScrollTo(3)) + Statistics.statisticsData?.items?.forEachIndexed { index, _ -> + onView(withId(R.id.statistics_recyclerview)).perform(recyclerScrollTo(index)) + takeScreenshot<HomeFragment>("statistics_card_$index") + } + } + + @Screenshot + @Test + fun captureContactDiaryOverviewFragment() { + every { contactDiaryOverviewViewModel.listItems } returns contactDiaryOverviewItemLiveData() + launchActivity<MainActivity>() + onView(withId(R.id.main_bottom_navigation)) + .perform(selectBottomNavTab(R.id.contact_diary_nav_graph)) + takeScreenshot<ContactDiaryOverviewFragment>() + + onView(withId(R.id.contact_diary_overview_recyclerview)) + .perform(recyclerScrollTo(1)) + takeScreenshot<ContactDiaryOverviewFragment>("2") + } + + private fun captureHomeFragment(nameSuffix: String) { + launchActivity<MainActivity>() + takeScreenshot<HomeFragment>(nameSuffix) + } + + // LiveData item for fragments + private fun homeFragmentItemsLiveData( + tracingStateItem: TracingStateItem = HomeData.Tracing.LOW_RISK_ITEM_WITH_ENCOUNTERS, + submissionTestResultItem: TestResultItem = HomeData.Submission.TEST_UNREGISTERED_ITEM + ): LiveData<List<HomeItem>> = + MutableLiveData( + mutableListOf<HomeItem>().apply { + when (submissionTestResultItem) { + is TestSubmissionDoneCard.Item, + is TestPositiveCard.Item -> { + Timber.d("Tracing item is not added, submission:$submissionTestResultItem") + } + else -> add(tracingStateItem) + } + add(submissionTestResultItem) + Statistics.statisticsData?.let { + add(StatisticsHomeCard.Item(data = it, onHelpAction = { })) + } + add(FAQCard.Item {}) + } + ) + + private fun contactDiaryOverviewItemLiveData(): LiveData<List<ListItem>> = + MutableLiveData( + (0 until ContactDiaryOverviewViewModel.DAY_COUNT) + .map { LocalDate.now().minusDays(it) } + .map { + ListItem(it).apply { + data.addAll(DiaryData.DATA_ITEMS) + risk = if (it.dayOfYear % 2 == 0) DiaryData.HIGH_RISK else DiaryData.LOW_RISK + } + } + ) + + // ViewModels creators + private fun mainActivityViewModelSpy() = spyk( + MainActivityViewModel( + dispatcherProvider = TestDispatcherProvider(), + environmentSetup = environmentSetup, + backgroundModeStatus = backgroundModeStatus, + contactDiarySettings = diarySettings + ) + ) + + private fun homeFragmentViewModelSpy() = spyk( + HomeFragmentViewModel( + dispatcherProvider = TestDispatcherProvider(), + errorResetTool = errorResetTool, + tracingRepository = tracingRepository, + tracingStateProviderFactory = tracingStateProviderFactory, + shareTestResultNotificationService = shareTestResultNotificationService, + appConfigProvider = appConfigProvider, + tracingStatus = tracingStatus, + submissionRepository = submissionRepository, + submissionStateProvider = submissionStateProvider, + cwaSettings = cwaSettings, + statisticsProvider = statisticsProvider + ) + ) + + private fun contactDiaryOverviewViewModelSpy() = spyk( + ContactDiaryOverviewViewModel( + taskController = taskController, + dispatcherProvider = TestDispatcherProvider(), + contactDiaryRepository = contactDiaryRepository, + riskLevelStorage = riskLevelStorage + ) + ) + + // Setup ViewModels + private fun setupContactDiaryOverviewViewModel() { + every { contactDiaryRepository.locationVisits } returns flowOf() + every { contactDiaryRepository.personEncounters } returns flowOf() + every { riskLevelStorage.aggregatedRiskPerDateResults } returns flowOf() + every { taskController.submit(any()) } just Runs + + contactDiaryOverviewViewModel = contactDiaryOverviewViewModelSpy() + setupMockViewModel( + object : ContactDiaryOverviewViewModel.Factory { + override fun create(): ContactDiaryOverviewViewModel = contactDiaryOverviewViewModel + } + ) + } + + private fun setupHomeFragmentViewModel() { + every { tracingStatus.generalStatus } returns flowOf() + every { tracingStateProviderFactory.create(any()) } returns tracingStateProvider.apply { + every { state } returns flowOf() + } + every { submissionStateProvider.state } returns flowOf() + every { statisticsProvider.current } returns flowOf() + every { appConfigProvider.currentConfig } returns flowOf() + homeFragmentViewModel = homeFragmentViewModelSpy() + with(homeFragmentViewModel) { + every { observeTestResultToSchedulePositiveTestResultReminder() } just Runs + every { refreshRequiredData() } just Runs + every { tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingActive) + every { showLoweredRiskLevelDialog } returns MutableLiveData() + every { homeItems } returns MutableLiveData(emptyList()) + every { popupEvents } returns SingleLiveEvent() + every { showPopUpsOrNavigate() } just Runs + } + + setupMockViewModel( + object : HomeFragmentViewModel.Factory { + override fun create(): HomeFragmentViewModel = homeFragmentViewModel + } + ) + } + + private fun setupActivityViewModel() { + every { diarySettings.onboardingStatus } returns ContactDiarySettings.OnboardingStatus.RISK_STATUS_1_12 + mainActivityViewModel = mainActivityViewModelSpy() + every { mainActivityViewModel.doBackgroundNoiseCheck() } just Runs + setupMockViewModel( + object : MainActivityViewModel.Factory { + override fun create(): MainActivityViewModel = mainActivityViewModel + } + ) + } +} + +// MainActivity DI Modules +@Module +abstract class MainActivityTestModule { + @ContributesAndroidInjector(modules = [MainProviderModule::class]) + abstract fun mainActivity(): MainActivity +} + +@Module +class MainProviderModule { + @Provides + fun powerManagement(): PowerManagement = mockk(relaxed = true) + + @Provides + fun deadmanScheduler(): DeadmanNotificationScheduler = + mockk<DeadmanNotificationScheduler>(relaxed = true).apply { + every { schedulePeriodic() } just Runs + } + + @Provides + fun contactDiaryWorkScheduler(): ContactDiaryWorkScheduler = + mockk<ContactDiaryWorkScheduler>(relaxed = true).apply { + every { schedulePeriodic() } just Runs + } +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt index 97a76ddf8297d79305f7382d0a3576c5fd0e30e2..c57339176a8bc9198d47ba315bf449f112551ff7 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeData.kt @@ -33,11 +33,27 @@ import org.joda.time.Instant object HomeData { object Tracing { - val LOW_RISK_ITEM = LowRiskCard.Item( + + val LOW_RISK_ITEM_NO_ENCOUNTERS = LowRiskCard.Item( + state = LowRisk( + riskState = RiskState.LOW_RISK, + isInDetailsMode = false, + lastExposureDetectionTime = Instant.now(), + lastEncounterAt = null, + allowManualUpdate = false, + daysWithEncounters = 0, + activeTracingDays = 1 + ), + onCardClick = {}, + onUpdateClick = {} + ) + + val LOW_RISK_ITEM_WITH_ENCOUNTERS = LowRiskCard.Item( state = LowRisk( riskState = RiskState.LOW_RISK, isInDetailsMode = false, lastExposureDetectionTime = Instant.now(), + lastEncounterAt = Instant.now(), allowManualUpdate = false, daysWithEncounters = 1, activeTracingDays = 1 diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt index a16a8dbe7470081881f075d046ef1267fa597e7d..21ce72fab6d5dab9df8069cdd2b8194fb9e6b855 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentTest.kt @@ -1,13 +1,9 @@ package de.rki.coronawarnapp.ui.main.home -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector -import de.rki.coronawarnapp.R import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.ShareTestResultNotificationService @@ -15,16 +11,9 @@ import de.rki.coronawarnapp.statistics.source.StatisticsProvider import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider -import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard -import de.rki.coronawarnapp.submission.ui.homecards.TestResultItem -import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.states.TracingStateProvider -import de.rki.coronawarnapp.tracing.ui.homecards.TracingStateItem import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState -import de.rki.coronawarnapp.ui.main.home.items.DiaryCard -import de.rki.coronawarnapp.ui.main.home.items.FAQCard -import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool import de.rki.coronawarnapp.util.ui.SingleLiveEvent import io.mockk.MockKAnnotations @@ -37,20 +26,11 @@ import io.mockk.unmockkAll import io.mockk.verify import org.junit.After import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME -import testhelpers.Screenshot -import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 -import testhelpers.launchFragmentInContainer2 -import testhelpers.recyclerScrollTo -import timber.log.Timber -import tools.fastlane.screengrab.Screengrab -import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) class HomeFragmentTest : BaseUITest() { @@ -68,13 +48,6 @@ class HomeFragmentTest : BaseUITest() { private lateinit var viewModel: HomeFragmentViewModel - @Rule - @JvmField - val localeTestRule = LocaleTestRule() - - @get:Rule - val systemUIDemoModeRule = SystemUIDemoModeRule() - @Before fun setup() { MockKAnnotations.init(this, relaxed = true) @@ -86,6 +59,7 @@ class HomeFragmentTest : BaseUITest() { every { showLoweredRiskLevelDialog } returns MutableLiveData() every { homeItems } returns MutableLiveData(emptyList()) every { popupEvents } returns SingleLiveEvent() + every { showPopUpsOrNavigate() } just Runs } setupMockViewModel( @@ -107,146 +81,6 @@ class HomeFragmentTest : BaseUITest() { verify(exactly = 1) { viewModel.refreshRequiredData() } } - @Screenshot - @Test - fun capture_screenshot_low_risk() { - every { viewModel.homeItems } returns itemsLiveData( - HomeData.Tracing.LOW_RISK_ITEM - ) - - captureScreenshot("low_risk") - onView(withId(R.id.recycler_view)).perform(recyclerScrollTo()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(HomeFragment::class.simpleName.plus("low_risk_2")) - } - - @Screenshot - @Test - fun capture_screenshot_increased_risk() { - every { viewModel.homeItems } returns itemsLiveData( - HomeData.Tracing.INCREASED_RISK_ITEM - ) - captureScreenshot("increased_risk") - } - - @Screenshot - @Test - fun capture_screenshot_tracing_disabled() { - every { viewModel.tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingInActive) - every { viewModel.homeItems } returns itemsLiveData( - HomeData.Tracing.TRACING_DISABLED_ITEM - ) - captureScreenshot("tracing_disabled") - } - - @Screenshot - @Test - fun capture_screenshot_tracing_progress_downloading() { - every { viewModel.homeItems } returns itemsLiveData( - HomeData.Tracing.TRACING_PROGRESS_ITEM - ) - captureScreenshot("progress_downloading") - } - - @Screenshot - @Test - fun capture_screenshot_tracing_failed() { - every { viewModel.homeItems } returns itemsLiveData( - HomeData.Tracing.TRACING_FAILED_ITEM - ) - captureScreenshot("tracing_failed") - } - - @Screenshot - @Test - fun capture_screenshot_test_submission_done() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_SUBMISSION_DONE_ITEM - ) - captureScreenshot("submission_done") - } - - @Screenshot - @Test - fun capture_screenshot_test_error() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_ERROR_ITEM - ) - captureScreenshot("test_error") - } - - @Screenshot - @Test - fun capture_screenshot_test_fetching() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_FETCHING_ITEM - ) - captureScreenshot("test_fetching") - } - - @Screenshot - @Test - fun capture_screenshot_test_invalid() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_INVALID_ITEM - ) - captureScreenshot("test_invalid") - } - - @Screenshot - @Test - fun capture_screenshot_test_negative() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_NEGATIVE_ITEM - ) - captureScreenshot("test_negative") - } - - @Screenshot - @Test - fun capture_screenshot_test_positive() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_POSITIVE_ITEM - ) - captureScreenshot("test_positive") - } - - @Screenshot - @Test - fun capture_screenshot_test_pending() { - every { viewModel.homeItems } returns itemsLiveData( - submissionTestResultItem = HomeData.Submission.TEST_PENDING_ITEM - ) - captureScreenshot("test_pending") - } - - private fun captureScreenshot(nameSuffix: String) { - val name = HomeFragment::class.simpleName + "_" + nameSuffix - launchFragmentInContainer2<HomeFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(name) - } - - private fun itemsLiveData( - tracingStateItem: TracingStateItem = HomeData.Tracing.LOW_RISK_ITEM, - submissionTestResultItem: TestResultItem = HomeData.Submission.TEST_UNREGISTERED_ITEM - ): LiveData<List<HomeItem>> = - MutableLiveData( - mutableListOf<HomeItem>().apply { - when (submissionTestResultItem) { - is TestSubmissionDoneCard.Item, - is TestPositiveCard.Item -> { - Timber.d("Tracing item is not added, submission:$submissionTestResultItem") - } - else -> add(tracingStateItem) - } - - add(submissionTestResultItem) - add(DiaryCard.Item {}) - add(FAQCard.Item {}) - } - ) - private fun homeFragmentViewModelSpy() = spyk( HomeFragmentViewModel( dispatcherProvider = TestDispatcherProvider(), diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt index 7fe75bf2a5a60131c5d25ac3a2612c1134962161..28dd9ce30754eda7622431dbcf6cd5debd7ce03c 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragmentTest.kt @@ -13,13 +13,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -62,8 +61,7 @@ class OnboardingDeltaInteroperabilityFragmentTest : BaseUITest() { @Test fun capture_screenshot() { launchFragmentInContainer2<OnboardingDeltaInteroperabilityFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(OnboardingDeltaInteroperabilityFragment::class.simpleName) + takeScreenshot<OnboardingDeltaInteroperabilityFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt index 69329685eef46ebbab0994b665afe6dc93b1f350..96e77f77f4a73cdb3e41944ece2b0314a70232b3 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingFragmentTest.kt @@ -14,12 +14,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -54,11 +53,10 @@ class OnboardingFragmentTest : BaseUITest() { @Test fun capture_screenshot() { launchFragmentInContainer2<OnboardingFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(OnboardingFragment::class.simpleName) + takeScreenshot<OnboardingFragment>() onView(withId(R.id.onboarding_easy_language)).perform(scrollTo()) - Screengrab.screenshot(OnboardingFragment::class.simpleName.plus("2")) + takeScreenshot<OnboardingFragment>("2") } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragmentTest.kt index 59a196e51c7a8f95fab4742a840f4ffba0b7e82a..460f251e6cb7b4f4930854b40eca7db4766d8a19 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingNotificationsFragmentTest.kt @@ -10,12 +10,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -50,8 +49,7 @@ class OnboardingNotificationsFragmentTest : BaseUITest() { @Test fun capture_screenshot() { launchFragmentInContainer2<OnboardingNotificationsFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(OnboardingNotificationsFragment::class.simpleName) + takeScreenshot<OnboardingNotificationsFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragmentTest.kt index ba58d9aa22ba00addd50a0300e32d74149ac50ae..e29b08e3a12c762997cdba0938a05aa5ab17c082 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingPrivacyFragmentTest.kt @@ -10,12 +10,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -50,8 +49,7 @@ class OnboardingPrivacyFragmentTest : BaseUITest() { @Test fun capture_screenshot() { launchFragmentInContainer2<OnboardingPrivacyFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(OnboardingPrivacyFragment::class.simpleName) + takeScreenshot<OnboardingPrivacyFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt index b15252ac7d20f9c570701081fa82b5ea2415ecb4..3d5031b76fea25225cc9b6a3efb490b091d51d03 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTestFragmentTest.kt @@ -13,9 +13,8 @@ import testhelpers.BaseUITest import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.launchFragment2 @RunWith(AndroidJUnit4::class) @@ -50,8 +49,7 @@ class OnboardingTestFragmentTest : BaseUITest() { @Test fun capture_screenshot() { launchFragmentInContainer2<OnboardingTestFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(OnboardingTestFragment::class.simpleName) + takeScreenshot<OnboardingTestFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt index e7621a3c18e74d4c700f7d7277e8aaae2a48ce2f..35bd31261e39b8cd9dea384212c7779a959b7a16 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/onboarding/OnboardingTracingFragmentTest.kt @@ -19,13 +19,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -75,10 +74,8 @@ class OnboardingTracingFragmentTest : BaseUITest() { @Screenshot @Test fun capture_screenshot() { - val simpleName = OnboardingTracingFragment::class.simpleName launchFragmentInContainer2<OnboardingTracingFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(simpleName) + takeScreenshot<OnboardingTracingFragment>() } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/Statistics.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/Statistics.kt new file mode 100644 index 0000000000000000000000000000000000000000..f4a6f85e1b5589ecd767d35cbd31d187a09860ec --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/Statistics.kt @@ -0,0 +1,65 @@ +package de.rki.coronawarnapp.ui.statistics + +import android.content.Context +import android.content.SharedPreferences +import de.rki.coronawarnapp.environment.EnvironmentSetup +import de.rki.coronawarnapp.environment.download.DownloadCDNModule +import de.rki.coronawarnapp.http.HttpModule +import de.rki.coronawarnapp.statistics.StatisticsData +import de.rki.coronawarnapp.statistics.StatisticsModule +import de.rki.coronawarnapp.statistics.source.StatisticsParser +import de.rki.coronawarnapp.statistics.source.StatisticsServer +import de.rki.coronawarnapp.util.security.VerificationKeys +import de.rki.coronawarnapp.util.serialization.SerializationModule +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import okhttp3.Cache +import retrofit2.converter.gson.GsonConverterFactory +import timber.log.Timber + +object Statistics { + + private fun loadRealStatisticsData(): StatisticsData? { + val context = mockk<Context>(relaxed = true) + val preferences = mockk<SharedPreferences>(relaxed = true) + val cache = mockk<Cache>(relaxed = true) + + every { preferences.getString(any(), any()) } returns null + every { context.getSharedPreferences(any(), any()) } returns preferences + + val cdnModule = DownloadCDNModule() + val baseGson = SerializationModule().baseGson() + val environmentSetup = EnvironmentSetup(context = context, gson = baseGson) + val httpClient = HttpModule().defaultHttpClient() + val cdnClient = cdnModule.cdnHttpClient(httpClient) + val url = cdnModule.provideDownloadServerUrl(environmentSetup) + val verificationKeys = VerificationKeys(environmentSetup) + val gsonFactory = GsonConverterFactory.create() + + val statisticsServer = StatisticsServer( + api = { + StatisticsModule().api( + client = cdnClient, + url = url, + gsonConverterFactory = gsonFactory, + cache = cache + ) + }, + cache = cache, + verificationKeys = verificationKeys + ) + + return runBlocking { + try { + val rawData = statisticsServer.getRawStatistics() + StatisticsParser().parse(rawData) + } catch (e: Exception) { + Timber.e(e, "Can't download statistics data. Check your internet connection.") + null + } + } + } + + val statisticsData: StatisticsData? = loadRealStatisticsData() +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/StatisticsCardsTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/StatisticsCardsTest.kt index 5d9afd0ba8f5e50bc8b2f51f8453182737b2beba..8b137891791fe96927ad78e64b0aad7bded08bdc 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/StatisticsCardsTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/statistics/StatisticsCardsTest.kt @@ -1,220 +1 @@ -package de.rki.coronawarnapp.ui.statistics -import android.content.Context -import android.content.SharedPreferences -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.matcher.ViewMatchers.withId -import androidx.test.ext.junit.runners.AndroidJUnit4 -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.environment.EnvironmentSetup -import de.rki.coronawarnapp.environment.download.DownloadCDNModule -import de.rki.coronawarnapp.http.HttpModule -import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.notification.ShareTestResultNotificationService -import de.rki.coronawarnapp.statistics.StatisticsData -import de.rki.coronawarnapp.statistics.StatisticsModule -import de.rki.coronawarnapp.statistics.source.StatisticsParser -import de.rki.coronawarnapp.statistics.source.StatisticsProvider -import de.rki.coronawarnapp.statistics.source.StatisticsServer -import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsHomeCard -import de.rki.coronawarnapp.storage.TracingRepository -import de.rki.coronawarnapp.submission.SubmissionRepository -import de.rki.coronawarnapp.submission.ui.homecards.SubmissionStateProvider -import de.rki.coronawarnapp.submission.ui.homecards.TestPositiveCard -import de.rki.coronawarnapp.submission.ui.homecards.TestResultItem -import de.rki.coronawarnapp.submission.ui.homecards.TestSubmissionDoneCard -import de.rki.coronawarnapp.tracing.GeneralTracingStatus -import de.rki.coronawarnapp.tracing.states.TracingStateProvider -import de.rki.coronawarnapp.tracing.ui.homecards.TracingStateItem -import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState -import de.rki.coronawarnapp.ui.main.home.HomeData -import de.rki.coronawarnapp.ui.main.home.HomeFragment -import de.rki.coronawarnapp.ui.main.home.HomeFragmentViewModel -import de.rki.coronawarnapp.ui.main.home.items.DiaryCard -import de.rki.coronawarnapp.ui.main.home.items.FAQCard -import de.rki.coronawarnapp.ui.main.home.items.HomeItem -import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool -import de.rki.coronawarnapp.util.security.VerificationKeys -import de.rki.coronawarnapp.util.serialization.SerializationModule -import de.rki.coronawarnapp.util.ui.SingleLiveEvent -import io.mockk.MockKAnnotations -import io.mockk.Runs -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.spyk -import kotlinx.coroutines.runBlocking -import okhttp3.Cache -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import retrofit2.converter.gson.GsonConverterFactory -import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME -import testhelpers.Screenshot -import testhelpers.SystemUIDemoModeRule -import testhelpers.TestDispatcherProvider -import testhelpers.launchFragmentInContainer2 -import testhelpers.recyclerScrollTo -import timber.log.Timber -import tools.fastlane.screengrab.Screengrab -import tools.fastlane.screengrab.locale.LocaleTestRule - -@RunWith(AndroidJUnit4::class) -class StatisticsCardsTest : BaseUITest() { - - @MockK lateinit var errorResetTool: EncryptionErrorResetTool - @MockK lateinit var tracingStatus: GeneralTracingStatus - @MockK lateinit var tracingStateProviderFactory: TracingStateProvider.Factory - @MockK lateinit var submissionStateProvider: SubmissionStateProvider - @MockK lateinit var tracingRepository: TracingRepository - @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService - @MockK lateinit var submissionRepository: SubmissionRepository - @MockK lateinit var cwaSettings: CWASettings - @MockK lateinit var appConfigProvider: AppConfigProvider - @MockK lateinit var cache: Cache - @MockK lateinit var context: Context - @MockK lateinit var preferences: SharedPreferences - @MockK lateinit var statisticsProvider: StatisticsProvider - - private lateinit var viewModel: HomeFragmentViewModel - - @Rule - @JvmField - val localeTestRule = LocaleTestRule() - - @get:Rule - val systemUIDemoModeRule = SystemUIDemoModeRule() - - @Before - fun setup() { - MockKAnnotations.init(this, relaxed = true) - - if (statisticsData == null) { - statisticsData = loadRealStatisticsData() - } - - viewModel = homeFragmentViewModelSpy() - with(viewModel) { - every { observeTestResultToSchedulePositiveTestResultReminder() } just Runs - every { refreshRequiredData() } just Runs - every { tracingHeaderState } returns MutableLiveData(TracingHeaderState.TracingActive) - every { showLoweredRiskLevelDialog } returns MutableLiveData() - every { popupEvents } returns SingleLiveEvent() - every { homeItems } returns itemsLiveData(HomeData.Tracing.LOW_RISK_ITEM, statsData = statisticsData) - } - - setupMockViewModel( - object : HomeFragmentViewModel.Factory { - override fun create(): HomeFragmentViewModel = viewModel - } - ) - } - - @After - fun teardown() { - clearAllViewModels() - } - - @Screenshot - @Test - fun capture_screenshot() { - launchFragmentInContainer2<HomeFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - onView(withId(R.id.recycler_view)).perform(recyclerScrollTo(3)) - - for (i in 0 until (statisticsData?.items?.size ?: 0)) { - onView(withId(R.id.statistics_recyclerview)).perform(recyclerScrollTo(i)) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(HomeFragment::class.simpleName.plus("_statistics_card_$i")) - } - } - - private fun itemsLiveData( - tracingStateItem: TracingStateItem = HomeData.Tracing.LOW_RISK_ITEM, - submissionTestResultItem: TestResultItem = HomeData.Submission.TEST_UNREGISTERED_ITEM, - statsData: StatisticsData? = null - ): LiveData<List<HomeItem>> = - MutableLiveData( - mutableListOf<HomeItem>().apply { - when (submissionTestResultItem) { - is TestSubmissionDoneCard.Item, - is TestPositiveCard.Item -> { - Timber.d("Tracing item is not added, submission:$submissionTestResultItem") - } - else -> add(tracingStateItem) - } - - add(submissionTestResultItem) - - if (statsData != null) { - add(StatisticsHomeCard.Item(data = statsData, onHelpAction = { })) - } - - add(DiaryCard.Item {}) - add(FAQCard.Item {}) - } - ) - - private fun homeFragmentViewModelSpy() = spyk( - HomeFragmentViewModel( - dispatcherProvider = TestDispatcherProvider(), - errorResetTool = errorResetTool, - tracingRepository = tracingRepository, - tracingStateProviderFactory = tracingStateProviderFactory, - shareTestResultNotificationService = shareTestResultNotificationService, - appConfigProvider = appConfigProvider, - tracingStatus = tracingStatus, - submissionRepository = submissionRepository, - submissionStateProvider = submissionStateProvider, - cwaSettings = cwaSettings, - statisticsProvider = statisticsProvider - ) - ) - - private fun loadRealStatisticsData(): StatisticsData? { - every { preferences.getString(any(), any()) } returns null - every { context.getSharedPreferences(any(), any()) } returns preferences - - val cdnModule = DownloadCDNModule() - val baseGson = SerializationModule().baseGson() - val environmentSetup = EnvironmentSetup(context = context, gson = baseGson) - val httpClient = HttpModule().defaultHttpClient() - val cdnClient = cdnModule.cdnHttpClient(httpClient) - val url = cdnModule.provideDownloadServerUrl(environmentSetup) - val verificationKeys = VerificationKeys(environmentSetup) - val gsonFactory = GsonConverterFactory.create() - - val statisticsServer = StatisticsServer( - api = { - StatisticsModule().api( - client = cdnClient, - url = url, - gsonConverterFactory = gsonFactory, - cache = cache - ) - }, - cache = cache, - verificationKeys = verificationKeys - ) - - return runBlocking { - try { - val rawData = statisticsServer.getRawStatistics() - StatisticsParser().parse(rawData) - } catch (e: Exception) { - Timber.e(e, "Can't download statistics data. Check your internet connection.") - null - } - } - } - - companion object { - private var statisticsData: StatisticsData? = null - } -} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt index ebfb27e04eaf52f0af131afe5b8402d54fbf47a5..bce66aec861cd6c7e85d2aa21ef3052ae0f8152a 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt @@ -22,13 +22,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule +import testhelpers.takeScreenshot import testhelpers.captureScreenshot import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -106,8 +105,7 @@ class SubmissionDispatcherFragmentTest : BaseUITest() { captureScreenshot<SubmissionDispatcherFragment>() onView(withId(R.id.submission_dispatcher_tan_tele)) .perform(scrollTo()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(SubmissionDispatcherFragment::class.simpleName.plus("2")) + takeScreenshot<SubmissionDispatcherFragment>("2") } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt index b845d8bd072562fce000a67aa82814ad332a4dec..9f39d1bc63ff9c4613b625de8d36228aa18eb02f 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt @@ -2,8 +2,8 @@ package de.rki.coronawarnapp.ui.submission import androidx.lifecycle.MutableLiveData import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector @@ -25,12 +25,11 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.captureScreenshot -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -82,11 +81,10 @@ class SubmissionSymptomCalendarFragmentTest : BaseUITest() { ).toBundle() ) - onView(ViewMatchers.withId(R.id.target_button_verify)) - .perform(ViewActions.scrollTo()) + onView(withId(R.id.target_button_verify)) + .perform(scrollTo()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(SubmissionSymptomCalendarFragment::class.simpleName.plus("2")) + takeScreenshot<SubmissionSymptomCalendarFragment>("2") } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt index 0011a0d46cea3363ae3dfc7f58a74b2b7a11b39d..1480a9211ef090e4a12ca0fbebd2a65ff75230bd 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt @@ -25,14 +25,13 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.captureScreenshot import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -53,14 +52,21 @@ class SubmissionSymptomIntroFragmentTest : BaseUITest() { @Before fun setup() { MockKAnnotations.init(this, relaxed = true) - viewModel = - spyk(SubmissionSymptomIntroductionViewModel(TestDispatcherProvider(), submissionRepository, autoSubmission)) + viewModel = spyk( + SubmissionSymptomIntroductionViewModel( + TestDispatcherProvider(), + submissionRepository, + autoSubmission + ) + ) with(viewModel) { every { symptomIndication } returns MutableLiveData(Symptoms.Indication.POSITIVE) } - setupMockViewModel(object : SubmissionSymptomIntroductionViewModel.Factory { - override fun create(): SubmissionSymptomIntroductionViewModel = viewModel - }) + setupMockViewModel( + object : SubmissionSymptomIntroductionViewModel.Factory { + override fun create(): SubmissionSymptomIntroductionViewModel = viewModel + } + ) } @After @@ -87,8 +93,7 @@ class SubmissionSymptomIntroFragmentTest : BaseUITest() { captureScreenshot<SubmissionSymptomIntroductionFragment>() onView(withId(R.id.target_button_verify)) .perform(scrollTo()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(SubmissionSymptomIntroductionFragment::class.simpleName.plus("2")) + takeScreenshot<SubmissionSymptomIntroductionFragment>("2") } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt index 65605273bf1de62344ec15571c3cc0005468196d..15acf26045ab042a24a85f8e9d41389f1df1bf03 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt @@ -21,13 +21,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -82,8 +81,7 @@ class SubmissionTanFragmentTest : BaseUITest() { onView(withId(R.id.tan_input_edittext)) .perform(click()) .perform(closeSoftKeyboard()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(SubmissionTanFragment::class.simpleName) + takeScreenshot<SubmissionTanFragment>() } @Test @@ -93,8 +91,7 @@ class SubmissionTanFragmentTest : BaseUITest() { onView(withId(R.id.tan_input_edittext)) .perform(click()) .perform(typeText("AC9UHD65AF"), closeSoftKeyboard()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(SubmissionTanFragment::class.simpleName.plus("_done")) + takeScreenshot<SubmissionTanFragment>("done") } @Test @@ -104,8 +101,7 @@ class SubmissionTanFragmentTest : BaseUITest() { onView(withId(R.id.tan_input_edittext)) .perform(click()) .perform(typeText("AC9U0"), closeSoftKeyboard()) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(SubmissionTanFragment::class.simpleName.plus("_invalid")) + takeScreenshot<SubmissionTanFragment>("invalid") } } 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 e02ca5fad8d6d499fc9101c734776fda06b77f2b..49ee8fe72c5280e320ab5b6cb0b49ef5250ab3c5 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 @@ -59,7 +59,8 @@ object TracingData { lastExposureDetectionTime = Instant.now(), allowManualUpdate = false, daysWithEncounters = 0, - activeTracingDays = 0 + activeTracingDays = 0, + lastEncounterAt = Instant.now() ) ), BehaviorNormalRiskBox.Item( diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt index d22dbfa33f7460cd2dc96ba63e4f98a86d75b106..cd1ae371d21040b73673b24272918215fea7f634 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/tracing/TracingDetailsFragmentTest.kt @@ -24,13 +24,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest -import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.takeScreenshot import testhelpers.Screenshot import testhelpers.SystemUIDemoModeRule import testhelpers.TestDispatcherProvider import testhelpers.launchFragment2 import testhelpers.launchFragmentInContainer2 -import tools.fastlane.screengrab.Screengrab import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) @@ -118,11 +117,9 @@ class TracingDetailsFragmentTest : BaseUITest() { every { viewModel.detailsItems } returns MutableLiveData(pair.second) } - private fun captureScreenshot(nameSuffix: String) { - val name = TracingDetailsFragment::class.simpleName + "_" + nameSuffix + private fun captureScreenshot(suffix: String) { launchFragmentInContainer2<TracingDetailsFragment>() - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(name) + takeScreenshot<TracingDetailsFragment>(suffix) } } diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt new file mode 100644 index 0000000000000000000000000000000000000000..e7076a69636b134b9e8fb5a545b19836c801fea5 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/ScreenShotter.kt @@ -0,0 +1,38 @@ +package testhelpers + +import android.app.Activity +import android.os.Bundle +import androidx.annotation.StyleRes +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentFactory +import androidx.test.espresso.ViewAction +import de.rki.coronawarnapp.R +import tools.fastlane.screengrab.Screengrab + +/** + * Waits for 2 sec and captures a screenshot + * @param suffix [String] Screenshots file name suffix, default name:[T::class.simpleName] + * @param delay [Long] delay time before capturing, default 2 sec + */ +inline fun <reified T> takeScreenshot(suffix: String = "", delay: Long = SCREENSHOT_DELAY_TIME) { + Thread.sleep(delay) + val simpleName = T::class.simpleName + val name = if (suffix.isEmpty()) simpleName else simpleName.plus("_$suffix") + Screengrab.screenshot(name) +} + +/** + * Launches and captures a screen [Fragment]. + * This function is not convenient if Espresso [ViewAction] is required + * before taking screenshot or the screen is [Activity]. + * Better to use [takeScreenshot] + */ +inline fun <reified F : Fragment> captureScreenshot( + suffix: String = "", + fragmentArgs: Bundle? = null, + @StyleRes themeResId: Int = R.style.AppTheme, + factory: FragmentFactory? = null +) { + launchFragmentInContainer2<F>(fragmentArgs, themeResId, factory) + takeScreenshot<F>(suffix) +} diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt index adc3a10ace099a3f9d7770d8b94605bced233747..67c9eb0316e96ef454662209766ea6efdb6c4d90 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestAppComponent.kt @@ -4,6 +4,7 @@ import dagger.BindsInstance import dagger.Component import dagger.android.AndroidInjector import dagger.android.support.AndroidSupportInjectionModule +import de.rki.coronawarnapp.ui.main.MainActivityTestModule import testhelpers.viewmodels.MockViewModelModule import javax.inject.Singleton @@ -12,7 +13,8 @@ import javax.inject.Singleton AndroidSupportInjectionModule::class, MockViewModelModule::class, FragmentTestModuleRegistrar::class, - TestAndroidModule::class + TestAndroidModule::class, + MainActivityTestModule::class ] ) @Singleton diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt index 1c2e282f7327ea6bf3c58c9dd49803369167b8e0..8d8a6905d35d49024023de5a5be5b9bf8f4508e2 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt @@ -6,7 +6,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory import androidx.fragment.app.testing.FragmentScenario import de.rki.coronawarnapp.R -import tools.fastlane.screengrab.Screengrab /** Delay time before taking screenshot */ @@ -28,18 +27,6 @@ inline fun <reified F : Fragment> launchFragmentInContainer2( factory: FragmentFactory? = null ) = FragmentScenario.launchInContainer(F::class.java, fragmentArgs, themeResId, factory) -inline fun <reified F : Fragment> captureScreenshot( - suffix: String = "", - fragmentArgs: Bundle? = null, - @StyleRes themeResId: Int = R.style.AppTheme, - factory: FragmentFactory? = null -) { - val name = F::class.simpleName.plus(suffix) - launchFragmentInContainer2<F>(fragmentArgs, themeResId, factory) - Thread.sleep(SCREENSHOT_DELAY_TIME) - Screengrab.screenshot(name) -} - /** * Launches Fragment in Activity. * Same as [androidx.fragment.app.testing.launchFragment] except that it defaults diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/ViewActions.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/ViewActions.kt index 0e2f02d6f7fd700682c40c490fc19d97f559a757..ab0e0e65d36e005edde33439106fcfda24c0fb06 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/ViewActions.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/ViewActions.kt @@ -1,12 +1,14 @@ package testhelpers import android.view.View +import androidx.annotation.IdRes import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.PerformException import androidx.test.espresso.UiController import androidx.test.espresso.ViewAction import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.tabs.TabLayout import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Matcher @@ -22,12 +24,12 @@ private class RecyclerViewScrollAction(private val position: Int? = null) : View return allOf(isAssignableFrom(RecyclerView::class.java), isDisplayed()) } - override fun perform(uiController: UiController?, view: View?) { + override fun perform(uiController: UiController, view: View) { val recyclerView = view as RecyclerView val itemCount = recyclerView.adapter?.itemCount val itemPosition = position ?: itemCount?.minus(1) ?: 0 recyclerView.scrollToPosition(itemPosition) - uiController?.loopMainThreadUntilIdle() + uiController.loopMainThreadUntilIdle() } } @@ -48,3 +50,16 @@ fun selectTabAtPosition(tabIndex: Int): ViewAction { } } } + +fun selectBottomNavTab(@IdRes id: Int): ViewAction { + return object : ViewAction { + override fun getDescription() = "with menu id $id" + + override fun getConstraints() = allOf(isDisplayed(), isAssignableFrom(BottomNavigationView::class.java)) + + override fun perform(uiController: UiController, view: View) { + val navigationView = view as BottomNavigationView + navigationView.selectedItemId = id + } + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..7a3f24566343c33c2bb20a8be2da2938dac918df --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragment.kt @@ -0,0 +1,63 @@ +package de.rki.coronawarnapp.test.datadonation.ui + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.core.app.ShareCompat +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentTestDatadonationBinding +import de.rki.coronawarnapp.test.menu.ui.TestMenuItem +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import org.json.JSONObject +import javax.inject.Inject + +@SuppressLint("SetTextI18n") +class DataDonationTestFragment : Fragment(R.layout.fragment_test_datadonation), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: DataDonationTestFragmentViewModel by cwaViewModels { viewModelFactory } + + private val binding: FragmentTestDatadonationBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + vm.currentReport.observe2(this) { + binding.safetynetBody.text = it?.body?.toString()?.let { json -> + JSONObject(json).toString(4) + } + } + + binding.apply { + safetynetCreateReport.setOnClickListener { vm.createSafetyNetReport() } + safetynetCopyJws.setOnClickListener { vm.copyJWS() } + } + + vm.copyJWSEvent.observe2(this) { jws -> + val intent = ShareCompat.IntentBuilder.from(requireActivity()).apply { + setType("text/plain") + setSubject("JWS") + setText(jws) + }.createChooserIntent() + startActivity(intent) + } + + vm.errorEvents.observe2(this) { + Toast.makeText(requireContext(), it.toString(), Toast.LENGTH_LONG).show() + } + } + + companion object { + val MENU_ITEM = TestMenuItem( + title = "Data Donation", + description = "SafetyNet, Analytics, Surveys et al.", + targetId = R.id.test_datadonation_fragment + ) + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..ffcccb443f848a5262596374c2d6310bbe92c7f8 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentModule.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.test.datadonation.ui + +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 DataDonationTestFragmentModule { + @Binds + @IntoMap + @CWAViewModelKey(DataDonationTestFragmentViewModel::class) + abstract fun dataDonation( + factory: DataDonationTestFragmentViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..9c574919c0b71a0dbebec0ac02f95fb7929afe41 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/datadonation/ui/DataDonationTestFragmentViewModel.kt @@ -0,0 +1,49 @@ +package de.rki.coronawarnapp.test.datadonation.ui + +import androidx.lifecycle.asLiveData +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetClientWrapper +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.MutableStateFlow +import timber.log.Timber +import java.security.SecureRandom + +class DataDonationTestFragmentViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, + private val safetyNetClientWrapper: SafetyNetClientWrapper, + private val secureRandom: SecureRandom +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + private val currentReportInternal = MutableStateFlow<SafetyNetClientWrapper.Report?>(null) + val currentReport = currentReportInternal.asLiveData(context = dispatcherProvider.Default) + val errorEvents = SingleLiveEvent<Throwable>() + val copyJWSEvent = SingleLiveEvent<String>() + + fun createSafetyNetReport() { + launch { + val nonce = ByteArray(16) + secureRandom.nextBytes(nonce) + try { + val report = safetyNetClientWrapper.attest(nonce) + currentReportInternal.value = report + } catch (e: Exception) { + Timber.e(e, "attest() failed.") + errorEvents.postValue(e) + } + } + } + + fun copyJWS() { + launch { + val value = currentReport.value?.jwsResult ?: "" + copyJWSEvent.postValue(value) + } + } + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<DataDonationTestFragmentViewModel> +} 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 992dc606712e5f159f247190f16dbdff97536918..45d99a414bdc53b84a241f66ef74ebd777229696 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 @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.miscinfo.MiscInfoFragment import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragment import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment 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.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment @@ -30,7 +31,8 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { SettingsCrashReportFragment.MENU_ITEM, MiscInfoFragment.MENU_ITEM, ContactDiaryTestFragment.MENU_ITEM, - PlaygroundFragment.MENU_ITEM + PlaygroundFragment.MENU_ITEM, + DataDonationTestFragment.MENU_ITEM ).let { MutableLiveData(it) } } val showTestScreenEvent = SingleLiveEvent<TestMenuItem>() 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 889dba9383e0cc4bcfbae6b5e225615bb9bfe6d0..c0511503a46fcdbcb518cc3415e2a36e0b4889c4 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 @@ -8,6 +8,8 @@ import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragment import de.rki.coronawarnapp.test.appconfig.ui.AppConfigTestFragmentModule import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragment import de.rki.coronawarnapp.test.contactdiary.ui.ContactDiaryTestFragmentModule +import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment +import de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragmentModule import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragment import de.rki.coronawarnapp.test.debugoptions.ui.DebugOptionsFragmentModule import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment @@ -55,4 +57,7 @@ abstract class MainActivityTestModule { @ContributesAndroidInjector(modules = [PlaygroundModule::class]) abstract fun playground(): PlaygroundFragment + + @ContributesAndroidInjector(modules = [DataDonationTestFragmentModule::class]) + abstract fun dataDonation(): DataDonationTestFragment } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml new file mode 100644 index 0000000000000000000000000000000000000000..cb3d19b8c37704608bab6a7401e0f07107960ace --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_datadonation.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout 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" + tools:ignore="HardcodedText"> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny" + android:orientation="vertical"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/debug_container" + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/safetynet_title" + style="@style/headline6" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:text="SafetyNet Report" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/safetynet_body" + android:layout_width="match_parent" + android:textIsSelectable="true" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/safetynet_title" + tools:text="Body" /> + + <Button + android:id="@+id/safetynet_copy_jws" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="Copy JWS" + app:layout_constraintEnd_toStartOf="@+id/safetynet_create_report" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/safetynet_body" /> + + <Button + android:id="@+id/safetynet_create_report" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="Create" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/safetynet_copy_jws" + app:layout_constraintTop_toBottomOf="@id/safetynet_body" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + + </LinearLayout> + </androidx.core.widget.NestedScrollView> +</layout> 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 a07bfc2263f776b94e395f36e40495cc7448078c..efdf3476513872f03ceb96c1e4951e82525b808e 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 @@ -40,6 +40,9 @@ <action android:id="@+id/action_test_menu_fragment_to_playgroundFragment" app:destination="@id/playgroundFragment" /> + <action + android:id="@+id/action_test_menu_fragment_to_dataDonationFragment" + app:destination="@id/test_datadonation_fragment" /> </fragment> <fragment @@ -108,5 +111,9 @@ android:name="de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment" tools:layout="@layout/fragment_test_playground" android:label="PlaygroundFragment" /> - + <fragment + android:id="@+id/test_datadonation_fragment" + tools:layout="@layout/fragment_test_datadonation" + android:name="de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment" + android:label="DataDonationFragment" /> </navigation> diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml index f7f4cc3eef5107e5ab6ff333ecde6950b9bc5615..ce4c559b2ad4236a87b7a4659aba8c8dedd01371 100644 --- a/Corona-Warn-App/src/main/AndroidManifest.xml +++ b/Corona-Warn-App/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:ignore="LockedOrientationActivity" - package="de.rki.coronawarnapp"> + package="de.rki.coronawarnapp" + tools:ignore="LockedOrientationActivity"> <uses-sdk tools:overrideLibrary="com.google.zxing.client.android" /> @@ -26,9 +26,9 @@ <application android:name="de.rki.coronawarnapp.CoronaWarnApplication" android:allowBackup="false" + android:extractNativeLibs="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:extractNativeLibs="true" android:networkSecurityConfig="@xml/network_security_config" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" @@ -50,7 +50,7 @@ <receiver android:name=".notification.NotificationReceiver" - android:enabled="true"/> + android:enabled="true" /> <activity android:name=".ui.launcher.LauncherActivity" @@ -65,7 +65,8 @@ android:name=".ui.main.MainActivity" android:exported="false" android:screenOrientation="portrait" - android:theme="@style/AppTheme.Main" /> + android:theme="@style/AppTheme.Main" + android:windowSoftInputMode="adjustResize" /> <activity android:name=".ui.onboarding.OnboardingActivity" android:exported="false" @@ -76,14 +77,6 @@ android:exported="false" android:screenOrientation="fullSensor" tools:replace="screenOrientation" /> - <activity - android:name=".contactdiary.ui.ContactDiaryActivity" - android:exported="false" - android:screenOrientation="portrait" - android:launchMode="singleTop" - android:label="@string/empty_string_to_avoid_toolbar_announcement" - android:theme="@style/AppTheme.ContactDiary" - android:windowSoftInputMode="adjustResize" /> <provider android:name="androidx.core.content.FileProvider" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigData.kt index 3f1be6e7e373510ce8512692a129849d7ef45380..1da1362a18433ba9af8b3cc1fae40899b5786777 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigData.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ConfigData.kt @@ -29,6 +29,8 @@ interface ConfigData : ConfigMapping { */ val isDeviceTimeCorrect: Boolean + val deviceTimeState: DeviceTimeState + /** * Returns the type config this is. */ @@ -57,6 +59,23 @@ interface ConfigData : ConfigMapping { */ fun isValid(nowUTC: Instant): Boolean + enum class DeviceTimeState(val key: String) { + /** + * Device time was compared against server time and deemed correct + */ + CORRECT("CORRECT"), + + /** + * Device time was not compared against server time for various reasons + */ + ASSUMED_CORRECT("ASSUMED_CORRECT"), + + /** + * Device time was compared against server time and deemed incorrect + */ + INCORRECT("INCORRECT") + } + companion object { val DEVICE_TIME_GRACE_RANGE: Duration = Duration.standardHours(2) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt index 5bceb72d05c3b175ab267b45ab66a70d35d6ea80..8a3d760bfaf2309404d89a634b040b86fdc59981 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSource.kt @@ -2,12 +2,14 @@ package de.rki.coronawarnapp.appconfig.internal import dagger.Reusable import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.appconfig.ConfigData.DeviceTimeState.CORRECT import de.rki.coronawarnapp.appconfig.sources.fallback.DefaultAppConfigSource import de.rki.coronawarnapp.appconfig.sources.local.LocalAppConfigSource import de.rki.coronawarnapp.appconfig.sources.remote.RemoteAppConfigSource import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.util.TimeStamper import org.joda.time.Duration +import org.joda.time.Instant import timber.log.Timber import javax.inject.Inject @@ -49,6 +51,20 @@ class AppConfigSource @Inject constructor( Timber.tag(TAG).i("Resetting previous incorrect device time acknowledgement.") cwaSettings.wasDeviceTimeIncorrectAcknowledged = false } + if (remoteConfig.deviceTimeState == CORRECT && cwaSettings.firstReliableDeviceTime == Instant.EPOCH) { + Timber.tag(TAG).i("Setting firstReliableDeviceTime to NOW (UTC). ") + cwaSettings.firstReliableDeviceTime = timeStamper.nowUTC + } + if (remoteConfig.deviceTimeState != cwaSettings.lastDeviceTimeStateChangeState) { + Timber.tag(TAG).i( + "New device time state, saving timestamp (old=%s(%s), new=%s#)", + cwaSettings.lastDeviceTimeStateChangeState, + cwaSettings.lastDeviceTimeStateChangeAt, + remoteConfig.deviceTimeState + ) + cwaSettings.lastDeviceTimeStateChangeState = remoteConfig.deviceTimeState + cwaSettings.lastDeviceTimeStateChangeAt = timeStamper.nowUTC + } remoteConfig } localConfig != null -> { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt index 40b3665714718f8805f141841d05bb2ed8d37107..03adfb0456b6b7db66db5859f8f3c2721a9d9471 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/internal/ConfigDataContainer.kt @@ -15,7 +15,14 @@ data class ConfigDataContainer( ) : ConfigData, ConfigMapping by mappedConfig { override val isDeviceTimeCorrect: Boolean - get() = !isDeviceTimeCheckEnabled || localOffset.abs() < ConfigData.DEVICE_TIME_GRACE_RANGE + get() = deviceTimeState != ConfigData.DeviceTimeState.INCORRECT + + override val deviceTimeState: ConfigData.DeviceTimeState + get() = when { + !isDeviceTimeCheckEnabled -> ConfigData.DeviceTimeState.ASSUMED_CORRECT + localOffset.abs() < ConfigData.DEVICE_TIME_GRACE_RANGE -> ConfigData.DeviceTimeState.CORRECT + else -> ConfigData.DeviceTimeState.INCORRECT + } override val updatedAt: Instant = serverTime.plus(localOffset) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ContactDiaryRootModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ContactDiaryRootModule.kt deleted file mode 100644 index 4215c795d879a7f4620f8cbfa7ad13317eb8a91a..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ContactDiaryRootModule.kt +++ /dev/null @@ -1,19 +0,0 @@ -package de.rki.coronawarnapp.contactdiary - -import dagger.Module -import dagger.android.ContributesAndroidInjector -import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryRetentionModule -import de.rki.coronawarnapp.contactdiary.storage.ContactDiaryStorageModule -import de.rki.coronawarnapp.contactdiary.ui.ContactDiaryActivity -import de.rki.coronawarnapp.contactdiary.ui.ContactDiaryUIModule - -@Module( - includes = [ - ContactDiaryStorageModule::class, - ContactDiaryRetentionModule::class - ] -) -abstract class ContactDiaryRootModule { - @ContributesAndroidInjector(modules = [ContactDiaryUIModule::class]) - abstract fun contactDiaryActivity(): ContactDiaryActivity -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryActivity.kt deleted file mode 100644 index 3aeaba9b7b028c060f73cb40144619832d7057d2..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryActivity.kt +++ /dev/null @@ -1,63 +0,0 @@ -package de.rki.coronawarnapp.contactdiary.ui - -import android.content.Context -import android.content.Intent -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.navigation.fragment.NavHostFragment -import dagger.android.AndroidInjector -import dagger.android.DispatchingAndroidInjector -import dagger.android.HasAndroidInjector -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.util.di.AppInjector -import javax.inject.Inject - -/** - * This activity holds all the contact diary fragments - */ -class ContactDiaryActivity : AppCompatActivity(), HasAndroidInjector { - - @Inject lateinit var settings: ContactDiarySettings - @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> - override fun androidInjector(): AndroidInjector<Any> = dispatchingAndroidInjector - - private val FragmentManager.currentNavigationFragment: Fragment? - get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first() - - override fun onCreate(savedInstanceState: Bundle?) { - AppInjector.setup(this) - super.onCreate(savedInstanceState) - setContentView(R.layout.contact_diary_activity) - - val navHost = supportFragmentManager.findFragmentById(R.id.contact_diary_fragment_container) as NavHostFragment? - val navController = navHost!!.navController - - val navInflater = navController.navInflater - val graph = navInflater.inflate(R.navigation.contact_diary_nav_graph) - - if (settings.onboardingStatus == ContactDiarySettings.OnboardingStatus.RISK_STATUS_1_12) { - graph.startDestination = R.id.contactDiaryOverviewFragment - } else { - graph.startDestination = R.id.contactDiaryOnboardingFragment - } - - navController.graph = graph - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - supportFragmentManager.currentNavigationFragment?.onActivityResult( - requestCode, - resultCode, - data - ) - } - - companion object { - fun start(context: Context) { - context.startActivity(Intent(context, ContactDiaryActivity::class.java)) - } - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiarySettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiarySettings.kt index 060f31727d356c5b69c4f561ecc37b3894f62713..c44a3355a72e5bc7c89e8aa92cc6232b2fe7bc3e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiarySettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiarySettings.kt @@ -14,6 +14,8 @@ class ContactDiarySettings @Inject constructor(val preferences: ContactDiaryPref } set(value) = preferences.onboardingStatusOrder.update { value.order } + inline val isOnboardingDone get() = onboardingStatus == OnboardingStatus.RISK_STATUS_1_12 + enum class OnboardingStatus(val order: Int) { NOT_ONBOARDED(0), RISK_STATUS_1_12(1) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt index 2d7674dffacdd0f4a5f0da5476ab913e2f278f13..e01721dc3e30acb05c08c3cf52134c0eb6ffd92a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt @@ -4,9 +4,11 @@ import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.databinding.ContactDiaryOnboardingFragmentBinding +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 @@ -23,6 +25,7 @@ class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboardin private val vm: ContactDiaryOnboardingFragmentViewModel by cwaViewModels { viewModelFactory } private val binding: ContactDiaryOnboardingFragmentBinding by viewBindingLazy() + private val args by navArgs<ContactDiaryOnboardingFragmentArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -31,9 +34,12 @@ class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboardin vm.onNextButtonClick() } - - toolbar.setNavigationOnClickListener { - vm.onBackButtonPress() + if (!args.showBottomNav) { + toolbar.apply { + navigationIcon = context.getDrawableCompat(R.drawable.ic_close) + navigationContentDescription = getString(R.string.accessibility_close) + setNavigationOnClickListener { vm.onBackButtonPress() } + } } contactDiaryOnboardingPrivacyInformation.setOnClickListener { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt index 4cef4c67c8851503d535c67b39320b5cd6523a82..166a253409f3b3d4cee436c0b4e469f0abf034f2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewFragment.kt @@ -96,7 +96,7 @@ class ContactDiaryOverviewFragment : Fragment(R.layout.contact_diary_overview_fr R.id.menu_contact_diary_information -> { doNavigate( ContactDiaryOverviewFragmentDirections - .actionContactDiaryOverviewFragmentToContactDiaryOnboardingFragment() + .actionContactDiaryOverviewFragmentToContactDiaryOnboardingFragment(showBottomNav = false) ) true } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/DataDonationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/DataDonationModule.kt index 461f5a68d1c7d5df7d14181d2aa927a25bbcbe42..be7a82167bf33668a2d8f567c694763cd2b5bb26 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/DataDonationModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/DataDonationModule.kt @@ -4,8 +4,11 @@ import dagger.Module import dagger.Provides import de.rki.coronawarnapp.datadonation.safetynet.CWASafetyNet import de.rki.coronawarnapp.datadonation.safetynet.DeviceAttestation +import de.rki.coronawarnapp.datadonation.survey.SurveyModule -@Module +@Module( + includes = [SurveyModule::class] +) class DataDonationModule { @Provides fun deviceAttestation(safetyNet: CWASafetyNet): DeviceAttestation = safetyNet diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt new file mode 100644 index 0000000000000000000000000000000000000000..bdd5fef0303cef83dc74709ae1d5f549c7de593a --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.datadonation + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName +import org.joda.time.Instant +import java.util.UUID + +@Keep +data class OneTimePassword( + @SerializedName("uuid") + val uuid: UUID = UUID.randomUUID(), + @SerializedName("time") + val time: Instant = Instant.now() +) { + + @Transient + val payloadForRequest = uuid.toString().toByteArray() +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..a35fb01fba3801ff429094d5c863dc25478f1dab --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainer.kt @@ -0,0 +1,65 @@ +package de.rki.coronawarnapp.datadonation.safetynet + +import de.rki.coronawarnapp.appconfig.SafetyNetRequirements +import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type +import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid +import okio.ByteString.Companion.toByteString + +internal data class AttestationContainer( + private val ourSalt: ByteArray, + private val report: SafetyNetClientWrapper.Report +) : DeviceAttestation.Result { + override val accessControlProtoBuf: PpacAndroid.PPACAndroid + get() = PpacAndroid.PPACAndroid.newBuilder().apply { + salt = ourSalt.toByteString().base64() + safetyNetJws = report.jwsResult + }.build() + + override fun requirePass(reqs: SafetyNetRequirements) { + if (reqs.requireBasicIntegrity && !report.basicIntegrity) { + throw SafetyNetException( + Type.BASIC_INTEGRITY_REQUIRED, + "Requirement 'basicIntegrity' not met (${report.advice})." + ) + } + + if (reqs.requireCTSProfileMatch && !report.ctsProfileMatch) { + throw SafetyNetException( + Type.CTS_PROFILE_MATCH_REQUIRED, + "Requirement 'ctsProfileMatch' not met (${report.advice})." + ) + } + + if (reqs.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")) { + throw SafetyNetException( + Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED, + "Evaluation type 'HARDWARE_BACKED' not met (${report.advice})." + ) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as AttestationContainer + + if (!ourSalt.contentEquals(other.ourSalt)) return false + if (report != other.report) return false + + return true + } + + override fun hashCode(): Int { + var result = ourSalt.contentHashCode() + result = 31 * result + report.hashCode() + return result + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt index 585d091600ef3e40ece80dd317a9cad8da0557d2..7b5dcff5fb16dbda68282afc546f31c544282c8f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt @@ -1,19 +1,93 @@ package de.rki.coronawarnapp.datadonation.safetynet -import de.rki.coronawarnapp.appconfig.SafetyNetRequirements -import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid +import android.content.Context +import androidx.annotation.VisibleForTesting +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.util.HashExtensions.toSHA256 +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.gplay.GoogleApiVersion +import org.joda.time.Duration +import org.joda.time.Instant +import timber.log.Timber +import java.security.SecureRandom import javax.inject.Inject import javax.inject.Singleton @Singleton -class CWASafetyNet @Inject constructor() : DeviceAttestation { +class CWASafetyNet @Inject constructor( + @AppContext private val context: Context, + private val client: SafetyNetClientWrapper, + private val secureRandom: SecureRandom, + private val appConfigProvider: AppConfigProvider, + private val googleApiVersion: GoogleApiVersion, + private val cwaSettings: CWASettings, + private val timeStamper: TimeStamper +) : DeviceAttestation { + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun generateSalt(): ByteArray = ByteArray(16).apply { + secureRandom.nextBytes(this) + } + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + fun calculateNonce(salt: ByteArray, payload: ByteArray): String { + val concat = salt + payload + return concat.toSHA256() + } + override suspend fun attest(request: DeviceAttestation.Request): DeviceAttestation.Result { - return object : DeviceAttestation.Result { - override val accessControlProtoBuf: PpacAndroid.PPACAndroid = PpacAndroid.PPACAndroid.getDefaultInstance() + if (!googleApiVersion.isPlayServicesVersionAvailable(13000000)) { + throw SafetyNetException(Type.PLAY_SERVICES_VERSION_MISMATCH, "Google Play Services too old.") + } - override fun requirePass(requirements: SafetyNetRequirements) { - // Passed + appConfigProvider.getAppConfig().apply { + if (deviceTimeState == ConfigData.DeviceTimeState.ASSUMED_CORRECT) { + throw SafetyNetException(Type.DEVICE_TIME_UNVERIFIED, "Device time is unverified") + } + if (deviceTimeState == ConfigData.DeviceTimeState.INCORRECT) { + throw SafetyNetException(Type.DEVICE_TIME_INCORRECT, "Device time is incorrect") } } + + val firstReliableTimeStamp = cwaSettings.firstReliableDeviceTime + if (firstReliableTimeStamp == Instant.EPOCH) { + throw SafetyNetException(Type.TIME_SINCE_ONBOARDING_UNVERIFIED, "No first reliable timestamp available") + } else if (Duration(firstReliableTimeStamp, timeStamper.nowUTC) < Duration.standardHours(24)) { + throw SafetyNetException(Type.TIME_SINCE_ONBOARDING_UNVERIFIED, "Time since first reliable timestamp <24h") + } + + val salt = generateSalt() + val nonce = calculateNonce(salt = salt, payload = request.scenarioPayload) + Timber.tag(TAG).d("With salt=%s and payload=%s, we created nonce=%s", salt, request.scenarioPayload, nonce) + + val report = client.attest(nonce.toByteArray()) + + report.error?.let { + Timber.tag(TAG).w("SafetyNet Response has an error message: %s", it) + } + + if (nonce != report.nonce) { + throw SafetyNetException( + Type.NONCE_MISMATCH, + "Request nonce doesn't match response ($nonce != ${report.nonce})" + ) + } + + if (context.packageName != report.apkPackageName) { + throw SafetyNetException( + Type.APK_PACKAGE_NAME_MISMATCH, + "Our APK name doesn't match response (${context.packageName} != ${report.apkPackageName})" + ) + } + + return AttestationContainer(salt, report) + } + + companion object { + private const val TAG = "CWASafetyNet" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..1aac1f6b52f7336239c232b0fc1531a344901960 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapper.kt @@ -0,0 +1,125 @@ +package de.rki.coronawarnapp.datadonation.safetynet + +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.safetynet.SafetyNetApi +import com.google.android.gms.safetynet.SafetyNetClient +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import dagger.Reusable +import de.rki.coronawarnapp.datadonation.safetynet.SafetyNetException.Type +import de.rki.coronawarnapp.environment.EnvironmentSetup +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.withTimeout +import okio.ByteString.Companion.decodeBase64 +import timber.log.Timber +import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Reusable +class SafetyNetClientWrapper @Inject constructor( + private val safetyNetClient: SafetyNetClient, + private val environmentSetup: EnvironmentSetup +) { + + suspend fun attest(nonce: ByteArray): Report { + val response = try { + withTimeout(30 * 1000L) { callClient(nonce) } + } catch (e: TimeoutCancellationException) { + throw SafetyNetException(Type.ATTESTATION_FAILED, "Attestation timeout.", e) + } + + val jwsResult = response.jwsResult ?: throw SafetyNetException( + Type.ATTESTATION_FAILED, + "JWS was null" + ) + + val components = jwsResult.split(".") + if (components.size != 3) throw SafetyNetException( + Type.ATTESTATION_FAILED, + "Invalid JWS: Components are missing." + ) + + val header = try { + components[0].decodeBase64Json() + } catch (e: Exception) { + throw SafetyNetException(Type.ATTESTATION_FAILED, "Failed to decode JWS header.", e) + } + val body = try { + components[1].decodeBase64Json() + } catch (e: Exception) { + throw SafetyNetException(Type.ATTESTATION_FAILED, "Failed to decode JWS body.", e) + } + + val signature = try { + components[2].decodeBase64()!!.toByteArray() + } catch (e: Exception) { + throw SafetyNetException(Type.ATTESTATION_FAILED, "Failed to decode JWS signature.", e) + } + + return Report( + jwsResult = jwsResult, + header = header, + body = body, + signature = signature + ) + } + + private fun String.decodeBase64Json(): JsonObject { + val rawJson = decodeBase64()!!.string(Charsets.UTF_8) + return JsonParser.parseString(rawJson).asJsonObject + } + + private suspend fun callClient(nonce: ByteArray): SafetyNetApi.AttestationResponse = suspendCoroutine { cont -> + safetyNetClient.attest(nonce, environmentSetup.safetyNetApiKey) + .addOnSuccessListener { + Timber.tag(TAG).v("Attestation finished with %s", it) + cont.resume(it) + } + .addOnFailureListener { + Timber.tag(TAG).w(it, "Attestation failed.") + val wrappedError = if (it is ApiException && it.statusCode == CommonStatusCodes.NETWORK_ERROR) { + SafetyNetException(Type.ATTESTATION_REQUEST_FAILED, "Network error", it) + } else { + SafetyNetException(Type.ATTESTATION_FAILED, "SafetyNet client returned an error.", it) + } + cont.resumeWithException(wrappedError) + } + } + + data class Report( + val jwsResult: String, + val header: JsonObject, + val body: JsonObject, + val signature: ByteArray + ) { + val nonce: String? = body.get("nonce")?.asString + + val apkPackageName: String? = body.get("apkPackageName")?.asString + + val basicIntegrity: Boolean = body.get("basicIntegrity")?.asBoolean == true + val ctsProfileMatch = body.get("ctsProfileMatch")?.asBoolean == true + + val evaluationTypes = body.get("evaluationType")?.asString + ?.split(",")?.map { it.trim() } ?: emptyList() + + val error: String? = body.get("error")?.asString + val advice: String? = body.get("advice")?.asString + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as Report + if (jwsResult != other.jwsResult) return false + return true + } + + override fun hashCode(): Int = jwsResult.hashCode() + } + + companion object { + private const val TAG = "SafetyNetWrapper" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt index b7148d7194f3213d4e32701f411930751309ab4f..498b5ce439e1bf361fd564e3a2472fedcde6796c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetException.kt @@ -1,6 +1,32 @@ package de.rki.coronawarnapp.datadonation.safetynet class SafetyNetException constructor( - message: String?, + val type: Type, + message: String, cause: Throwable? = null -) : Exception(message, cause) +) : Exception("$type: $message", cause) { + + enum class Type { + // TRY_AGAIN_LATER (Text Key) + APK_PACKAGE_NAME_MISMATCH, + ATTESTATION_FAILED, + ATTESTATION_REQUEST_FAILED, + DEVICE_TIME_UNVERIFIED, + NONCE_MISMATCH, + + // DEVICE_NOT_TRUSTED (Text Key) + BASIC_INTEGRITY_REQUIRED, + CTS_PROFILE_MATCH_REQUIRED, + EVALUATION_TYPE_BASIC_REQUIRED, + EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED, + + // CHANGE_DEVICE_TIME (Text Key) + DEVICE_TIME_INCORRECT, + + // UPDATE_PLAY_SERVICES (Text Key) + PLAY_SERVICES_VERSION_MISMATCH, + + // TIME_SINCE_ONBOARDING_UNVERIFIED (Text Key) + TIME_SINCE_ONBOARDING_UNVERIFIED + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/storage/OTPRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/storage/OTPRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..15a569b39f00457e34547d78966383fa5311defb --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/storage/OTPRepository.kt @@ -0,0 +1,23 @@ +package de.rki.coronawarnapp.datadonation.storage + +import de.rki.coronawarnapp.datadonation.OneTimePassword +import de.rki.coronawarnapp.datadonation.survey.SurveySettings +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class OTPRepository @Inject constructor( + private val surveySettings: SurveySettings +) { + + val lastOTP: OneTimePassword? + get() = surveySettings.oneTimePassword + + fun generateOTP(): OneTimePassword = OneTimePassword().also { + surveySettings.oneTimePassword = it + } + + fun clear() { + surveySettings.oneTimePassword = null + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyModule.kt index b93a6f114fb0961255468f7eb63994e8ceba1eb2..17d9c2221079e0682421a074cecef816f972a07b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveyModule.kt @@ -1,6 +1,9 @@ package de.rki.coronawarnapp.datadonation.survey import dagger.Module +import de.rki.coronawarnapp.datadonation.survey.consent.SurveyConsentModule -@Module +@Module( + includes = [SurveyConsentModule::class] +) class SurveyModule diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt index 141401f51bb84804406564faf3c167fc281b53fd..5de01d28fd76a7d7bde8eb43d68a1ccf553037c7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/SurveySettings.kt @@ -1,7 +1,48 @@ package de.rki.coronawarnapp.datadonation.survey +import android.content.Context +import com.google.gson.Gson +import de.rki.coronawarnapp.datadonation.OneTimePassword +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.preferences.clearAndNotify +import de.rki.coronawarnapp.util.serialization.BaseGson +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @Singleton -class SurveySettings @Inject constructor() +class SurveySettings @Inject constructor( + @AppContext val context: Context, + @BaseGson val gson: Gson +) { + + private val preferences by lazy { + context.getSharedPreferences("survey_localdata", Context.MODE_PRIVATE) + } + + var oneTimePassword: OneTimePassword? + get() { + try { + val json = preferences.getString(KEY_OTP, null) + if (json != null) { + val otp = gson.fromJson(json, OneTimePassword::class.java) + requireNotNull(otp.uuid) + requireNotNull(otp.time) + return otp + } + return null + } catch (t: Throwable) { + Timber.e(t, "failed to parse OTP from preferences") + return null + } + } + set(value) = + preferences + .edit() + .putString(KEY_OTP, if (value == null) null else gson.toJson(value)) + .apply() + + fun clear() = preferences.clearAndNotify() +} + +private const val KEY_OTP = "one_time_password" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt index a755f2e5455d33a1f84bc596721726e639149767..e5e57184b6b120e3c6477b96b3274630f7f85cde 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt @@ -18,7 +18,7 @@ class Surveys @Inject constructor( // TODO return Survey( type = Type.HIGH_RISK_ENCOUNTER, - surveyLink = "" + surveyLink = "Link to high risk encounter survey..." ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..96f19cfe34fc3bf75039fb557b80e5324a5d55e0 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentFragment.kt @@ -0,0 +1,57 @@ +package de.rki.coronawarnapp.datadonation.survey.consent + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.SurveyConsentFragmentBinding +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted +import javax.inject.Inject + +class SurveyConsentFragment : Fragment(R.layout.survey_consent_fragment), AutoInject { + + private val navArgs by navArgs<SurveyConsentFragmentArgs>() + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: SurveyConsentViewModel by cwaViewModelsAssisted( + factoryProducer = { viewModelFactory }, + constructorCall = { factory, _ -> + factory as SurveyConsentViewModel.Factory + factory.create(navArgs.SurveyType) + } + ) + + private val binding: SurveyConsentFragmentBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.apply { + toolbar.setNavigationOnClickListener { vm.onBackButtonPressed() } + surveyNextButton.setOnClickListener { vm.onNextButtonPressed() } + } + + vm.routeToScreen.observe2(this) { + when (it) { + is SurveyConsentNavigationEvents.NavigateBack -> + activity?.onBackPressed() + is SurveyConsentNavigationEvents.NavigateToWebView -> + Toast.makeText(requireContext(), "Open ${it.url} (WIP, next PR))", Toast.LENGTH_SHORT).show() + } + } + + vm.showLoadingIndicator.observe2(this) { + // TODO + } + + vm.showErrorDialog.observe2(this) { + // TODO + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..e01552dace8c536dbb12e2fcebb9ab6964b81bba --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentModule.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.datadonation.survey.consent + +import dagger.Binds +import dagger.Module +import dagger.android.ContributesAndroidInjector +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 SurveyConsentModule { + @Binds + @IntoMap + @CWAViewModelKey(SurveyConsentViewModel::class) + abstract fun surveyConsentVM( + factory: SurveyConsentViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> + + @ContributesAndroidInjector + abstract fun surveyConsentFragment(): SurveyConsentFragment +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentNavigationEvents.kt new file mode 100644 index 0000000000000000000000000000000000000000..7984d37d0d15b368a2d19e37b4c82f7d0b13b008 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentNavigationEvents.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.datadonation.survey.consent + +sealed class SurveyConsentNavigationEvents { + object NavigateBack : SurveyConsentNavigationEvents() + data class NavigateToWebView(val url: String) : SurveyConsentNavigationEvents() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..2852f720c46afe9cf66eb42d4a49068fbfd7eeb9 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/consent/SurveyConsentViewModel.kt @@ -0,0 +1,50 @@ +package de.rki.coronawarnapp.datadonation.survey.consent + +import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.datadonation.survey.SurveyException +import de.rki.coronawarnapp.datadonation.survey.Surveys +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.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import kotlinx.coroutines.flow.MutableStateFlow + +class SurveyConsentViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, + private val surveys: Surveys, + @Assisted private val surveyType: Surveys.Type +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + private val internalLoadingState = MutableStateFlow(false) + + val showLoadingIndicator: LiveData<Boolean> = internalLoadingState.asLiveData() + val showErrorDialog: SingleLiveEvent<SurveyException> = SingleLiveEvent() + val routeToScreen: SingleLiveEvent<SurveyConsentNavigationEvents> = SingleLiveEvent() + + fun onBackButtonPressed() { + routeToScreen.postValue(SurveyConsentNavigationEvents.NavigateBack) + } + + fun onNextButtonPressed() = launch { + internalLoadingState.emit(true) + try { + val surveyLink = surveys.requestDetails(surveyType).surveyLink + routeToScreen.postValue(SurveyConsentNavigationEvents.NavigateToWebView(surveyLink)) + } catch (surveyException: SurveyException) { + surveyException.report(ExceptionCategory.INTERNAL) + showErrorDialog.postValue(surveyException) + } + internalLoadingState.emit(false) + } + + @AssistedFactory + interface Factory : CWAViewModelFactory<SurveyConsentViewModel> { + fun create(type: Surveys.Type): SurveyConsentViewModel + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt index 729096de612f54afba63406b374b6a6aeb3f74b5..76bfaa4a444f2fdf833d31b60efe3f1de711923a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/environment/EnvironmentSetup.kt @@ -39,11 +39,11 @@ class EnvironmentSetup @Inject constructor( enum class Type(val rawKey: String) { PRODUCTION("PROD"), INT("INT"), - INT_FED("INT-FED"), DEV("DEV"), WRU("WRU"), - WRU_XA("WRU-XA"), // (aka ACME) - WRU_XD("WRU-XD"); // (aka Germany) + WRU_XA("WRU-XA"), // (aka ACME), + WRU_XD("WRU-XD"), // (aka Germany) + LOCAL("LOCAL"); // Emulator/CLI tooling companion object { internal fun String.toEnvironmentType(): Type = values().single { @@ -86,17 +86,24 @@ class EnvironmentSetup @Inject constructor( val targetEnvKey = if (environmentJson.has(currentEnvironment.rawKey)) { currentEnvironment.rawKey } else { - Timber.e("Tried to use unavailable environment: $variableKey on $currentEnvironment") + Timber.e("Tried to use unavailable environment: $currentEnvironment") Type.PRODUCTION.rawKey } - environmentJson + + val value = environmentJson .getAsJsonObject(targetEnvKey) .getAsJsonPrimitive(variableKey.rawKey) + + return@run if (value != null) { + Timber.v("getEnvironmentValue(endpoint=%s): %s", variableKey, value) + value + } else { + throw IllegalStateException("$currentEnvironment:$variableKey is missing in your *_environment.json") + } } catch (e: Exception) { - Timber.e(e, "Failed to retrieve endpoint URL for $currentEnvironment:$variableKey") - throw IllegalStateException("Failed to setup test environment", e) + throw IllegalStateException("Failed to retrieve $currentEnvironment:$variableKey", e) } - }.also { Timber.v("getEndpointUrl(endpoint=%s): %s", variableKey, it) } + } val submissionCdnUrl: String get() = getEnvironmentValue(SUBMISSION).asString diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt index 5e2616d5e85705e7f3986bbbd82cbc112943b153..b571c7f7f8b45e1fc65f5649fd00afa618bf62b1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/main/CWASettings.kt @@ -2,9 +2,11 @@ package de.rki.coronawarnapp.main import android.content.Context import androidx.core.content.edit +import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.preferences.clearAndNotify import de.rki.coronawarnapp.util.preferences.createFlowPreference +import org.joda.time.Instant import javax.inject.Inject /** @@ -25,6 +27,21 @@ class CWASettings @Inject constructor( get() = prefs.getBoolean(PKEY_DEVICE_TIME_INCORRECT_ACK, false) set(value) = prefs.edit { putBoolean(PKEY_DEVICE_TIME_INCORRECT_ACK, value) } + var firstReliableDeviceTime: Instant + get() = Instant.ofEpochMilli(prefs.getLong(PKEY_DEVICE_TIME_FIRST_RELIABLE, 0L)) + set(value) = prefs.edit { putLong(PKEY_DEVICE_TIME_FIRST_RELIABLE, value.millis) } + + var lastDeviceTimeStateChangeAt: Instant + get() = Instant.ofEpochMilli(prefs.getLong(PKEY_DEVICE_TIME_LAST_STATE_CHANGE_TIME, 0L)) + set(value) = prefs.edit { putLong(PKEY_DEVICE_TIME_LAST_STATE_CHANGE_TIME, value.millis) } + + var lastDeviceTimeStateChangeState: ConfigData.DeviceTimeState + get() = prefs.getString( + PKEY_DEVICE_TIME_LAST_STATE_CHANGE_STATE, + ConfigData.DeviceTimeState.INCORRECT.key + ).let { raw -> ConfigData.DeviceTimeState.values().single { it.key == raw } } + set(value) = prefs.edit { putString(PKEY_DEVICE_TIME_LAST_STATE_CHANGE_STATE, value.key) } + val lastChangelogVersion = prefs.createFlowPreference( key = LAST_CHANGELOG_VERSION, defaultValue = DEFAULT_APP_VERSION @@ -36,6 +53,9 @@ class CWASettings @Inject constructor( companion object { private const val PKEY_DEVICE_TIME_INCORRECT_ACK = "devicetime.incorrect.acknowledged" + private const val PKEY_DEVICE_TIME_FIRST_RELIABLE = "devicetime.correct.first" + private const val PKEY_DEVICE_TIME_LAST_STATE_CHANGE_TIME = "devicetime.laststatechange.timestamp" + private const val PKEY_DEVICE_TIME_LAST_STATE_CHANGE_STATE = "devicetime.laststatechange.state" private const val LAST_CHANGELOG_VERSION = "update.changelog.lastversion" private const val DEFAULT_APP_VERSION = 1L } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..d6040603f97d0d448b48284bb1c7fa7578a8dc10 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoFragment.kt @@ -0,0 +1,97 @@ +package de.rki.coronawarnapp.release + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent +import androidx.core.view.isGone +import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.NewReleaseInfoItemBinding +import de.rki.coronawarnapp.databinding.NewReleaseInfoScreenFragmentBinding +import de.rki.coronawarnapp.ui.lists.BaseAdapter +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.lists.BindableVH +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 +import javax.inject.Inject + +class NewReleaseInfoFragment : Fragment(R.layout.new_release_info_screen_fragment), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + + private val vm: NewReleaseInfoViewModel by cwaViewModels { viewModelFactory } + private val binding: NewReleaseInfoScreenFragmentBinding by viewBindingLazy() + private val args: NewReleaseInfoFragmentArgs by navArgs() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.apply { + newReleaseInfoNextButton.setOnClickListener { + vm.onNextButtonClick() + } + + toolbar.setNavigationOnClickListener { + vm.onNextButtonClick() + } + + headline.text = vm.title.get(requireContext()) + + newReleaseInfoNextButton.isGone = args.comesFromInfoScreen + + if (args.comesFromInfoScreen) { + toolbar.setNavigationIcon(R.drawable.ic_back) + } + + recyclerView.adapter = ItemAdapter(getItems()) + } + + vm.routeToScreen.observe2(this) { + if (it is NewReleaseInfoNavigationEvents.CloseScreen) { + popBackStack() + } + } + } + + private fun getItems(): List<NewReleaseInfoItem> { + val titles = resources.getStringArray(R.array.new_release_title) + val textBodies = resources.getStringArray(R.array.new_release_body) + return vm.getItems(titles, textBodies) + } + + override fun onResume() { + super.onResume() + binding.container.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) + } +} + +private class ItemAdapter( + private val items: List<NewReleaseInfoItem> +) : BaseAdapter<ItemAdapter.ViewHolder>() { + + inner class ViewHolder(parent: ViewGroup) : BaseAdapter.VH(R.layout.new_release_info_item, parent), + BindableVH<NewReleaseInfoItem, NewReleaseInfoItemBinding> { + override val viewBinding: + Lazy<NewReleaseInfoItemBinding> = + lazy { NewReleaseInfoItemBinding.bind(itemView) } + + override val onBindData: + NewReleaseInfoItemBinding.(item: NewReleaseInfoItem, payloads: List<Any>) -> Unit = + { item, _ -> + title.text = item.title + body.text = item.body + } + } + + override fun onCreateBaseVH(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder(parent) + + override fun onBindBaseVH(holder: ViewHolder, position: Int, payloads: MutableList<Any>) { + holder.bind(items[position], payloads) + } + + override fun getItemCount() = items.size +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoFragmentModule.kt similarity index 72% rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentModule.kt rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoFragmentModule.kt index 7b51134b59fbde9aed24be376ce9d57bce7a2683..e0bb1d3eb935185fea2d1c6a0649dbd70fa50f61 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoFragmentModule.kt @@ -1,4 +1,4 @@ -package de.rki.coronawarnapp.ui.release +package de.rki.coronawarnapp.release import dagger.Binds import dagger.Module @@ -11,8 +11,8 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey abstract class NewReleaseInfoFragmentModule { @Binds @IntoMap - @CWAViewModelKey(NewReleaseInfoFragmentViewModel::class) + @CWAViewModelKey(NewReleaseInfoViewModel::class) abstract fun newReleaseInfoFragmentVM( - factory: NewReleaseInfoFragmentViewModel.Factory + factory: NewReleaseInfoViewModel.Factory ): CWAViewModelFactory<out CWAViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoItem.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoItem.kt new file mode 100644 index 0000000000000000000000000000000000000000..91b8e7d8c24d1f156d65401ce4a624dc226c4093 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoItem.kt @@ -0,0 +1,6 @@ +package de.rki.coronawarnapp.release + +data class NewReleaseInfoItem( + val title: String, + val body: String +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoNavigationEvents.kt new file mode 100644 index 0000000000000000000000000000000000000000..e485871c4a3ab9b3e9a8ffec4acc29b42bdf5877 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoNavigationEvents.kt @@ -0,0 +1,5 @@ +package de.rki.coronawarnapp.release + +sealed class NewReleaseInfoNavigationEvents { + object CloseScreen : NewReleaseInfoNavigationEvents() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a88666abd3049a2205ef65675eba638eeb5076b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/release/NewReleaseInfoViewModel.kt @@ -0,0 +1,45 @@ +package de.rki.coronawarnapp.release + +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.BuildConfig +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.ui.toResolvingString +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import timber.log.Timber + +class NewReleaseInfoViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, + private val settings: CWASettings +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + val routeToScreen: SingleLiveEvent<NewReleaseInfoNavigationEvents> = SingleLiveEvent() + + val title = R.string.release_info_version_title.toResolvingString(BuildConfig.VERSION_NAME) + + fun onNextButtonClick() { + settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE } + routeToScreen.postValue(NewReleaseInfoNavigationEvents.CloseScreen) + } + + fun getItems(titles: Array<String>, bodies: Array<String>): List<NewReleaseInfoItem> { + if (titles.size != bodies.size) { + Timber.e("R.array.new_release_title and R.array.new_release_body must have the same size!") + } + val items = mutableListOf<NewReleaseInfoItem>() + titles.indices.forEach { + if (it <= bodies.lastIndex) { + items.add(NewReleaseInfoItem(titles[it], bodies[it])) + } + } + return items + } + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<NewReleaseInfoViewModel> +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/StatisticsExplanationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/StatisticsExplanationFragment.kt index 669d806cde31ffe6cd300de7b57e9a334ca8e538..f796aac3fcbc4842c540078da93e541604b98bee 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/StatisticsExplanationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/StatisticsExplanationFragment.kt @@ -7,6 +7,7 @@ import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentStatisticsExplanationBinding import de.rki.coronawarnapp.util.setUrl +import de.rki.coronawarnapp.util.ui.popBackStack import de.rki.coronawarnapp.util.ui.viewBindingLazy /** @@ -28,6 +29,12 @@ class StatisticsExplanationFragment : Fragment(R.layout.fragment_statistics_expl R.string.statistics_explanation_seven_day_r_link_label, R.string.statistics_explanation_faq_url ) + + binding.statisticsExplanationTrendText.apply { + val label = String.format(getString(R.string.statistics_explanation_trend_text)) + text = label + contentDescription = label + } } override fun onResume() { @@ -37,7 +44,7 @@ class StatisticsExplanationFragment : Fragment(R.layout.fragment_statistics_expl private fun setButtonOnClickListener() { binding.statisticsExplanationHeaderButtonBack.buttonIcon.setOnClickListener { - activity?.onBackPressed() + popBackStack() } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/StatisticsHomeCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/StatisticsHomeCard.kt index 59939f7f25a9052f33f2a838575c206db18c9763..63acaad5087eaa094e560c9206aa16599d850087 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/StatisticsHomeCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/StatisticsHomeCard.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.statistics.ui.homecards +import android.os.Parcelable import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.recyclerview.widget.DefaultItemAnimator @@ -13,20 +14,28 @@ import de.rki.coronawarnapp.statistics.ui.homecards.cards.StatisticsCardItem import de.rki.coronawarnapp.ui.main.home.HomeAdapter import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.util.lists.diffutil.update +import de.rki.coronawarnapp.util.lists.modular.mods.SavedStateMod class StatisticsHomeCard( parent: ViewGroup, @LayoutRes containerLayout: Int = R.layout.home_statistics_scrollcontainer -) : HomeAdapter.HomeItemVH<StatisticsHomeCard.Item, HomeStatisticsScrollcontainerBinding>(containerLayout, parent) { +) : HomeAdapter.HomeItemVH<StatisticsHomeCard.Item, HomeStatisticsScrollcontainerBinding>(containerLayout, parent), + SavedStateMod.StateSavingVH { - private val statsAdapter by lazy { StatisticsCardAdapter() } + override var savedStateKey: String? = null + + private val statisticsLayoutManager: StatisticsLayoutManager by lazy { + StatisticsLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + } + + private val statisticsCardAdapter by lazy { StatisticsCardAdapter() } override val viewBinding = lazy { HomeStatisticsScrollcontainerBinding.bind(itemView).apply { statisticsRecyclerview.apply { setHasFixedSize(false) - adapter = statsAdapter - layoutManager = StatisticsLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + adapter = statisticsCardAdapter + layoutManager = statisticsLayoutManager itemAnimator = DefaultItemAnimator() addItemDecoration( StatisticsCardPaddingDecorator( @@ -44,11 +53,19 @@ class StatisticsHomeCard( item: Item, payloads: List<Any> ) -> Unit = { item, _ -> + savedStateKey = "stats:${item.stableId}" + item.data.items.map { StatisticsCardItem(it, item.onHelpAction) - }.let { statsAdapter.update(it) } + }.let { + statisticsCardAdapter.update(it) + } } + override fun onSaveState(): Parcelable? = statisticsLayoutManager.onSaveInstanceState() + + override fun restoreState(state: Parcelable) = statisticsLayoutManager.onRestoreInstanceState(state) + data class Item( val data: StatisticsData, val onHelpAction: (StatsItem) -> Unit @@ -69,9 +86,7 @@ class StatisticsHomeCard( } override fun hashCode(): Int { - var result = data.hashCode() - result = 31 * result + stableId.hashCode() - return result + return 31 * data.hashCode() + stableId.hashCode() } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/IncidenceCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/IncidenceCard.kt index d5433c1fc991a1a57f63f20ce4918cffc4558367..fafe09299dc76f1bce35db8c079449a8e3cc374b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/IncidenceCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/IncidenceCard.kt @@ -3,10 +3,15 @@ package de.rki.coronawarnapp.statistics.ui.homecards.cards import android.view.ViewGroup import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.HomeStatisticsCardsIncidenceLayoutBinding +import de.rki.coronawarnapp.server.protocols.internal.stats.KeyFigureCardOuterClass import de.rki.coronawarnapp.statistics.IncidenceStats +import de.rki.coronawarnapp.statistics.StatsItem import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsCardAdapter import de.rki.coronawarnapp.statistics.util.formatStatisticalValue +import de.rki.coronawarnapp.statistics.util.getContentDescriptionForTrends import de.rki.coronawarnapp.statistics.util.getLocalizedSpannableString +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithLineBreak +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithTrailingSpace import de.rki.coronawarnapp.util.formatter.getPrimaryLabel class IncidenceCard(parent: ViewGroup) : @@ -32,12 +37,40 @@ class IncidenceCard(parent: ViewGroup) : } with(item.stats as IncidenceStats) { + + incidenceContainer.contentDescription = + buildAccessibilityStringForIncidenceCard(item.stats, sevenDayIncidence) + primaryLabel.text = getPrimaryLabel(context) primaryValue.text = getLocalizedSpannableString( context, formatStatisticalValue(context, sevenDayIncidence.value, sevenDayIncidence.decimals) ) + + primaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.statistics_explanation_seven_day_incidence_title)) + .appendWithTrailingSpace(getPrimaryLabel(context)) + .appendWithTrailingSpace(formatStatisticalValue(context, sevenDayIncidence.value, + sevenDayIncidence.decimals)) + .append(getContentDescriptionForTrends(context, sevenDayIncidence.trend)) + trendArrow.setTrend(sevenDayIncidence.trend, sevenDayIncidence.trendSemantic) } } + + private fun buildAccessibilityStringForIncidenceCard( + item: StatsItem, + sevenDayIncidence: KeyFigureCardOuterClass.KeyFigure + ): StringBuilder { + + return StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.accessibility_statistics_card_announcement)) + .appendWithLineBreak(context.getString(R.string.statistics_explanation_seven_day_incidence_title)) + .appendWithTrailingSpace(item.getPrimaryLabel(context)) + .appendWithTrailingSpace(formatStatisticalValue(context, sevenDayIncidence.value, + sevenDayIncidence.decimals)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_incidence_value_description)) + .appendWithLineBreak(getContentDescriptionForTrends(context, sevenDayIncidence.trend)) + .append(context.getString(R.string.accessibility_statistics_card_navigation_information)) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/InfectionsCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/InfectionsCard.kt index c1fad337fe9ace15e485796f28f913e75eff677d..1e7f9bfc3821c847a2508e146413bb16a121a4b2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/InfectionsCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/InfectionsCard.kt @@ -3,10 +3,15 @@ package de.rki.coronawarnapp.statistics.ui.homecards.cards import android.view.ViewGroup import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.HomeStatisticsCardsInfectionsLayoutBinding +import de.rki.coronawarnapp.server.protocols.internal.stats.KeyFigureCardOuterClass import de.rki.coronawarnapp.statistics.InfectionStats +import de.rki.coronawarnapp.statistics.StatsItem import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsCardAdapter import de.rki.coronawarnapp.statistics.util.formatStatisticalValue import de.rki.coronawarnapp.util.formatter.getPrimaryLabel +import de.rki.coronawarnapp.statistics.util.getContentDescriptionForTrends +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithLineBreak +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithTrailingSpace class InfectionsCard(parent: ViewGroup) : StatisticsCardAdapter.ItemVH<StatisticsCardItem, HomeStatisticsCardsInfectionsLayoutBinding>( @@ -31,11 +36,52 @@ class InfectionsCard(parent: ViewGroup) : } with(item.stats as InfectionStats) { + + infectionsContainer.contentDescription = + buildAccessibilityStringForInfectionsCard(item.stats, newInfections, sevenDayAverage, total) + primaryLabel.text = getPrimaryLabel(context) primaryValue.text = formatStatisticalValue(context, newInfections.value, newInfections.decimals) + primaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(getPrimaryLabel(context)) + .appendWithTrailingSpace(formatStatisticalValue(context, newInfections.value, newInfections.decimals)) + .append(context.getString(R.string.statistics_card_infections_title)) + secondaryValue.text = formatStatisticalValue(context, sevenDayAverage.value, sevenDayAverage.decimals) + secondaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_secondary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, sevenDayAverage.value, + sevenDayAverage.decimals)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_title)) + .append(getContentDescriptionForTrends(context, sevenDayAverage.trend)) + tertiaryValue.text = formatStatisticalValue(context, total.value, total.decimals) + tertiaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_tertiary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, total.value, total.decimals)) + .append(context.getString(R.string.statistics_card_infections_title)) + trendArrow.setTrend(sevenDayAverage.trend, sevenDayAverage.trendSemantic) } } + + private fun buildAccessibilityStringForInfectionsCard( + item: StatsItem, + newInfections: KeyFigureCardOuterClass.KeyFigure, + sevenDayAverage: KeyFigureCardOuterClass.KeyFigure, + total: KeyFigureCardOuterClass.KeyFigure + ): StringBuilder { + + return StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.accessibility_statistics_card_announcement)) + .appendWithLineBreak(context.getString(R.string.statistics_card_infections_title)) + .appendWithTrailingSpace(item.getPrimaryLabel(context)) + .appendWithLineBreak(formatStatisticalValue(context, newInfections.value, newInfections.decimals)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_secondary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, sevenDayAverage.value, sevenDayAverage.decimals)) + .appendWithLineBreak(getContentDescriptionForTrends(context, sevenDayAverage.trend)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_tertiary_label)) + .appendWithLineBreak(formatStatisticalValue(context, total.value, total.decimals)) + .append(context.getString(R.string.accessibility_statistics_card_navigation_information)) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/KeySubmissionsCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/KeySubmissionsCard.kt index 08218528f43b04ab12ec4bff2ecf1812d41e5238..a722f23d298856f48b21d4f1ba82e0e272bfc7e8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/KeySubmissionsCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/KeySubmissionsCard.kt @@ -3,9 +3,14 @@ package de.rki.coronawarnapp.statistics.ui.homecards.cards import android.view.ViewGroup import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.HomeStatisticsCardsKeysubmissionsLayoutBinding +import de.rki.coronawarnapp.server.protocols.internal.stats.KeyFigureCardOuterClass import de.rki.coronawarnapp.statistics.KeySubmissionsStats +import de.rki.coronawarnapp.statistics.StatsItem import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsCardAdapter import de.rki.coronawarnapp.statistics.util.formatStatisticalValue +import de.rki.coronawarnapp.statistics.util.getContentDescriptionForTrends +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithLineBreak +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithTrailingSpace import de.rki.coronawarnapp.util.formatter.getPrimaryLabel class KeySubmissionsCard(parent: ViewGroup) : @@ -31,11 +36,53 @@ class KeySubmissionsCard(parent: ViewGroup) : } with(item.stats as KeySubmissionsStats) { + + keysubmissionsContainer.contentDescription = + buildAccessibilityStringForKeySubmissionsCard(item.stats, keySubmissions, sevenDayAverage, total) + primaryLabel.text = getPrimaryLabel(context) primaryValue.text = formatStatisticalValue(context, keySubmissions.value, keySubmissions.decimals) + primaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(getPrimaryLabel(context)) + .appendWithTrailingSpace(formatStatisticalValue(context, keySubmissions.value, keySubmissions.decimals)) + .append(context.getString(R.string.statistics_card_submission_title)) + secondaryValue.text = formatStatisticalValue(context, sevenDayAverage.value, sevenDayAverage.decimals) + secondaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_secondary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, sevenDayAverage.value, + sevenDayAverage.decimals)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_submission_title)) + .append(getContentDescriptionForTrends(context, sevenDayAverage.trend)) + tertiaryValue.text = formatStatisticalValue(context, total.value, total.decimals) + tertiaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_tertiary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, total.value, total.decimals)) + .append(context.getString(R.string.statistics_card_submission_title)) + trendArrow.setTrend(sevenDayAverage.trend, sevenDayAverage.trendSemantic) } } + + private fun buildAccessibilityStringForKeySubmissionsCard( + item: StatsItem, + keySubmissions: KeyFigureCardOuterClass.KeyFigure, + sevenDayAverage: KeyFigureCardOuterClass.KeyFigure, + total: KeyFigureCardOuterClass.KeyFigure + ): StringBuilder { + + return StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.accessibility_statistics_card_announcement)) + .appendWithLineBreak(context.getString(R.string.statistics_card_submission_title)) + .appendWithTrailingSpace(item.getPrimaryLabel(context)) + .appendWithLineBreak(formatStatisticalValue(context, keySubmissions.value, keySubmissions.decimals)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_secondary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, sevenDayAverage.value, sevenDayAverage.decimals)) + .appendWithLineBreak(getContentDescriptionForTrends(context, sevenDayAverage.trend)) + .appendWithTrailingSpace(context.getString(R.string.statistics_card_infections_tertiary_label)) + .appendWithTrailingSpace(formatStatisticalValue(context, total.value, total.decimals)) + .appendWithLineBreak(context.getString(R.string.statistics_card_submission_bottom_text)) + .append(context.getString(R.string.accessibility_statistics_card_navigation_information)) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/SevenDayRValueCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/SevenDayRValueCard.kt index b21614ef32e06d843ac78f8181399a7863a82270..cc384a068d37978b8127d740cb00ca6ddc428704 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/SevenDayRValueCard.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/ui/homecards/cards/SevenDayRValueCard.kt @@ -3,10 +3,15 @@ package de.rki.coronawarnapp.statistics.ui.homecards.cards import android.view.ViewGroup import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.HomeStatisticsCardsSevendayrvalueLayoutBinding +import de.rki.coronawarnapp.server.protocols.internal.stats.KeyFigureCardOuterClass import de.rki.coronawarnapp.statistics.SevenDayRValue +import de.rki.coronawarnapp.statistics.StatsItem import de.rki.coronawarnapp.statistics.ui.homecards.StatisticsCardAdapter import de.rki.coronawarnapp.statistics.util.formatStatisticalValue +import de.rki.coronawarnapp.statistics.util.getContentDescriptionForTrends import de.rki.coronawarnapp.statistics.util.getLocalizedSpannableString +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithLineBreak +import de.rki.coronawarnapp.util.StringBuilderExtension.appendWithTrailingSpace import de.rki.coronawarnapp.util.formatter.getPrimaryLabel class SevenDayRValueCard(parent: ViewGroup) : @@ -32,12 +37,40 @@ class SevenDayRValueCard(parent: ViewGroup) : } with(item.stats as SevenDayRValue) { + + sevenDayRValueContainer.contentDescription = + buildAccessibilityStringForSevenDayRValueCard(item.stats, reproductionNumber) + primaryLabel.text = getPrimaryLabel(context) primaryValue.text = getLocalizedSpannableString( context, formatStatisticalValue(context, reproductionNumber.value, reproductionNumber.decimals) ) + + primaryValue.contentDescription = StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.statistics_title_reproduction)) + .appendWithTrailingSpace(getPrimaryLabel(context)) + .appendWithTrailingSpace(formatStatisticalValue(context, reproductionNumber.value, + reproductionNumber.decimals)) + .append(getContentDescriptionForTrends(context, reproductionNumber.trend)) + trendArrow.setTrend(reproductionNumber.trend, reproductionNumber.trendSemantic) } } + + private fun buildAccessibilityStringForSevenDayRValueCard( + item: StatsItem, + reproductionNumber: KeyFigureCardOuterClass.KeyFigure + ): StringBuilder { + + return StringBuilder() + .appendWithTrailingSpace(context.getString(R.string.accessibility_statistics_card_announcement)) + .appendWithLineBreak(context.getString(R.string.statistics_title_reproduction)) + .appendWithTrailingSpace(item.getPrimaryLabel(context)) + .appendWithTrailingSpace(formatStatisticalValue(context, reproductionNumber.value, + reproductionNumber.decimals)) + .appendWithTrailingSpace(context.getString(R.string.statistics_reproduction_average)) + .appendWithLineBreak(getContentDescriptionForTrends(context, reproductionNumber.trend)) + .append(context.getString(R.string.accessibility_statistics_card_navigation_information)) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/util/AccessibilityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/util/AccessibilityHelper.kt index 1c9bff6793ff87ac42d657cb863d75944a74a172..ac2bc08813cc2e4614098923be48bb829d50e441 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/util/AccessibilityHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/statistics/util/AccessibilityHelper.kt @@ -3,7 +3,9 @@ package de.rki.coronawarnapp.statistics.util import android.content.Context import android.text.SpannableString import android.text.style.LocaleSpan +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.util.getLocale +import de.rki.coronawarnapp.server.protocols.internal.stats.KeyFigureCardOuterClass /** * returns localized spannable string so that screen readers read out decimal values appropriately @@ -11,3 +13,16 @@ import de.rki.coronawarnapp.contactdiary.util.getLocale fun getLocalizedSpannableString(context: Context, source: String) = SpannableString(source).apply { setSpan(LocaleSpan(context.getLocale()), 0, this.length, 0) } + +fun getContentDescriptionForTrends( + context: Context, + trend: KeyFigureCardOuterClass.KeyFigure.Trend +): String { + return context.getString( + when (trend) { + KeyFigureCardOuterClass.KeyFigure.Trend.INCREASING -> R.string.statistics_trend_increasing + KeyFigureCardOuterClass.KeyFigure.Trend.DECREASING -> R.string.statistics_trend_decreasing + else -> R.string.statistics_trend_stable + } + ) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt index faf48fe87616a866f9bc0d9c41107ac810dee267..00891017c663c5837c2b50d4e63e00817afcdc6d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt @@ -71,10 +71,24 @@ class SubmissionRepository @Inject constructor( } } - // TODO this should be more UI agnostic fun refreshDeviceUIState(refreshTestResult: Boolean = true) { - var refresh = refreshTestResult + if (LocalData.submissionWasSuccessful()) { + deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL) + return + } + + val registrationToken = LocalData.registrationToken() + if (registrationToken == null) { + deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED) + return + } + if (LocalData.isAllowedToSubmitDiagnosisKeys() == true) { + deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE) + return + } + + var refresh = refreshTestResult deviceUIStateFlowInternal.value.withSuccess { if (it != DeviceUIState.PAIRED_NO_RESULT && it != DeviceUIState.UNPAIRED) { refresh = false @@ -82,41 +96,24 @@ class SubmissionRepository @Inject constructor( } } - deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestStarted - - scope.launch { - try { - deviceUIStateFlowInternal.value = refreshUIState(refresh) - } catch (err: CwaWebException) { - deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err) - } catch (err: Exception) { - deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err) - err.report(ExceptionCategory.INTERNAL) - } - } - } - - // TODO this should be more UI agnostic - private suspend fun refreshUIState(refreshTestResult: Boolean): NetworkRequestWrapper<DeviceUIState, Throwable> { - var uiState = DeviceUIState.UNPAIRED - - if (LocalData.submissionWasSuccessful()) { - uiState = DeviceUIState.SUBMITTED_FINAL - } else { - val registrationToken = LocalData.registrationToken() - if (registrationToken != null) { - uiState = when { - LocalData.isAllowedToSubmitDiagnosisKeys() == true -> { - DeviceUIState.PAIRED_POSITIVE - } - refreshTestResult -> fetchTestResult(registrationToken) - else -> { - deriveUiState(testResultFlow.value) - } + if (refresh) { + deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestStarted + + scope.launch { + try { + deviceUIStateFlowInternal.value = + NetworkRequestWrapper.RequestSuccessful(fetchTestResult(registrationToken)) + } catch (err: CwaWebException) { + deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err) + } catch (err: Exception) { + deviceUIStateFlowInternal.value = NetworkRequestWrapper.RequestFailed(err) + err.report(ExceptionCategory.INTERNAL) } } + } else { + deviceUIStateFlowInternal.value = + NetworkRequestWrapper.RequestSuccessful(deriveUiState(testResultFlow.value)) } - return NetworkRequestWrapper.RequestSuccessful(uiState) } suspend fun asyncRegisterDeviceViaTAN(tan: String) { 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 8b201929b5f2e2f3f8031991573e2fed2550d852..8bf8482ae23e6ecf992c5af41aa5368ec076f0a6 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 @@ -39,43 +39,60 @@ data class IncreasedRisk( val showUpdateButton: Boolean = allowManualUpdate && !isInDetailsMode - fun getTimeFetched(c: Context): String = if (lastExposureDetectionTime != null) { - c.getString( + fun getTimeFetched(context: Context): String = if (lastExposureDetectionTime != null) { + context.getString( R.string.risk_card_body_time_fetched, - formatRelativeDateTimeString(c, lastExposureDetectionTime) + formatRelativeDateTimeString(context, lastExposureDetectionTime) ) } else { - c.getString(R.string.risk_card_body_not_yet_fetched) + context.getString(R.string.risk_card_body_not_yet_fetched) } - fun getRiskContactBody(c: Context): String = if (daysWithEncounters == 0) { + fun getRiskContactBody(context: Context): String = if (daysWithEncounters == 0) { // LEGACY MIGRATION CASE FROM 1.7.x -> 1.8.x ('days with encounter' doesn't exit in 1.7.x) // see RiskLevelResultMigrator.kt "" } else { - c.resources.getQuantityString( + context.resources.getQuantityString( R.plurals.risk_card_high_risk_encounter_days_body, daysWithEncounters, daysWithEncounters ) } - fun getRiskActiveTracingDaysInRetentionPeriod(c: Context): String { + fun getRiskContactBodyDescription(context: Context): String = if (daysWithEncounters == 0) { + "" + } else { + context.resources.getQuantityString( + R.plurals.risk_card_high_risk_encounter_days_body_description, + daysWithEncounters, + daysWithEncounters + ) + } + + fun getRiskActiveTracingDaysInRetentionPeriod(context: Context): String { if (!isInDetailsMode) return "" return if (activeTracingDays < TimeVariables.getDefaultRetentionPeriodInDays()) { - c.getString(R.string.risk_card_body_saved_days).format(activeTracingDays) + context.getString(R.string.risk_card_body_saved_days).format(activeTracingDays) } else { - c.getString(R.string.risk_card_body_saved_days_full) + context.getString(R.string.risk_card_body_saved_days_full) } } - fun getRiskContactLast(c: Context): String? { + fun getRiskContactLast(context: Context): String? { if (lastEncounterAt == null) return null // caution! lastEncounterAt is null after migration from 1.7.x -> 1.8.x // see RiskLevelResultMigrator.kt - return c.getString( - R.string.risk_card_high_risk_most_recent_body, + + val stringRes = if (daysWithEncounters == 1) { + R.string.risk_card_high_risk_most_recent_body_encounter_on_single_day + } else { + R.string.risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day + } + + return context.getString( + stringRes, lastEncounterAt.toLocalDate().toString(DateTimeFormat.mediumDate()) ) } @@ -86,6 +103,7 @@ data class LowRisk( override val riskState: RiskState, override val isInDetailsMode: Boolean, val lastExposureDetectionTime: Instant?, + val lastEncounterAt: Instant?, val allowManualUpdate: Boolean, val daysWithEncounters: Int, val activeTracingDays: Int @@ -93,33 +111,60 @@ data class LowRisk( val showUpdateButton: Boolean = allowManualUpdate && !isInDetailsMode - fun getTimeFetched(c: Context): String = if (lastExposureDetectionTime != null) { - c.getString( + fun getTimeFetched(context: Context): String = if (lastExposureDetectionTime != null) { + context.getString( R.string.risk_card_body_time_fetched, - formatRelativeDateTimeString(c, lastExposureDetectionTime) + formatRelativeDateTimeString(context, lastExposureDetectionTime) ) } else { - c.getString(R.string.risk_card_body_not_yet_fetched) + context.getString(R.string.risk_card_body_not_yet_fetched) } - fun getRiskContactBody(c: Context): String = if (daysWithEncounters == 0) { + fun getRiskContactBody(context: Context): String = if (daysWithEncounters == 0) { // caution! is 0 after migration from 1.7.x -> 1.8.x // see RiskLevelResultMigrator.kt - c.getString(R.string.risk_card_low_risk_no_encounters_body) + context.getString(R.string.risk_card_low_risk_no_encounters_body) } else { - c.resources.getQuantityString( + context.resources.getQuantityString( R.plurals.risk_card_low_risk_encounter_days_body, daysWithEncounters, daysWithEncounters ) } - fun getRiskActiveTracingDaysInRetentionPeriod(c: Context): String = + fun getRiskContactBodyDescription(context: Context): String = if (daysWithEncounters == 0) { + context.getString(R.string.risk_card_low_risk_no_encounters_body) + } else { + context.resources.getQuantityString( + R.plurals.risk_card_low_risk_encounter_days_body_description, + daysWithEncounters, + daysWithEncounters + ) + } + + fun getRiskActiveTracingDaysInRetentionPeriod(context: Context): String = if (activeTracingDays < TimeVariables.getDefaultRetentionPeriodInDays()) { - c.getString(R.string.risk_card_body_saved_days).format(activeTracingDays) + context.getString(R.string.risk_card_body_saved_days).format(activeTracingDays) } else { - c.getString(R.string.risk_card_body_saved_days_full) + context.getString(R.string.risk_card_body_saved_days_full) } + + fun getRiskContactLast(context: Context): String? { + if (lastEncounterAt == null) return null + // caution! lastEncounterAt is null after migration from 1.7.x -> 1.8.x + // see RiskLevelResultMigrator.kt + + val stringRes = if (daysWithEncounters == 1) { + R.string.risk_card_low_risk_most_recent_body_encounter_on_single_day + } else { + R.string.risk_card_low_risk_most_recent_body_encounters_on_more_than_one_day + } + + return context.getString( + stringRes, + lastEncounterAt.toLocalDate().toString(DateTimeFormat.mediumDate()) + ) + } } // tracing_content_failed_view diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt index a21a5486700a3f9249a3480c4bf2095ecc15e2b0..cb94ae132c27493a2fd52f84207670d1529f868f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/states/TracingStateProvider.kt @@ -74,6 +74,7 @@ class TracingStateProvider @AssistedInject constructor( isInDetailsMode = isDetailsMode, riskState = latestCalc.riskState, lastExposureDetectionTime = latestSubmission?.startedAt, + lastEncounterAt = latestCalc.lastRiskEncounterAt, daysWithEncounters = latestCalc.daysWithEncounters, activeTracingDays = activeTracingDaysInRetentionPeriod.toInt(), allowManualUpdate = !isBackgroundJobEnabled diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsAdapter.kt index 9a51aafcd13fc69c3d85e923df674bfc9bdde971..692acb888ce3dd4aa103beb9fb71f45e17ec7d75 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsAdapter.kt @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.tracing.ui.details.items.risk.TracingProgressBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsFailedCalculationBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsLowRiskBox +import de.rki.coronawarnapp.tracing.ui.details.items.survey.UserSurveyBox import de.rki.coronawarnapp.util.lists.BindableVH import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffUtilAdapter import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffer @@ -24,7 +25,9 @@ import de.rki.coronawarnapp.util.lists.modular.mods.DataBinderMod import de.rki.coronawarnapp.util.lists.modular.mods.StableIdMod import de.rki.coronawarnapp.util.lists.modular.mods.TypedVHCreatorMod -class TracingDetailsAdapter : ModularAdapter<TracingDetailsAdapter.DetailsItemVH<DetailsItem, ViewBinding>>(), +class TracingDetailsAdapter( + private val onItemClickListener: (item: DetailsItem) -> Unit +) : ModularAdapter<TracingDetailsAdapter.DetailsItemVH<DetailsItem, ViewBinding>>(), AsyncDiffUtilAdapter<DetailsItem> { override val asyncDiffer: AsyncDiffer<DetailsItem> = AsyncDiffer(this) @@ -44,7 +47,13 @@ class TracingDetailsAdapter : ModularAdapter<TracingDetailsAdapter.DetailsItemVH TypedVHCreatorMod({ data[it] is PeriodLoggedBox.Item }) { PeriodLoggedBox(it) }, TypedVHCreatorMod({ data[it] is BehaviorIncreasedRiskBox.Item }) { BehaviorIncreasedRiskBox(it) }, TypedVHCreatorMod({ data[it] is BehaviorNormalRiskBox.Item }) { BehaviorNormalRiskBox(it) }, - TypedVHCreatorMod({ data[it] is AdditionalInfoLowRiskBox.Item }) { AdditionalInfoLowRiskBox(it) } + TypedVHCreatorMod({ data[it] is AdditionalInfoLowRiskBox.Item }) { AdditionalInfoLowRiskBox(it) }, + TypedVHCreatorMod({ data[it] is UserSurveyBox.Item }) { + UserSurveyBox( + parent = it, + onItemClickListener = onItemClickListener + ) + } )) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt index 5b7bd025b82ab7a8acf11560e3243c86b4e41659..ce3598a2c71e79bc644d74cce0dd026ea1a33625 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragment.kt @@ -30,7 +30,7 @@ class TracingDetailsFragment : Fragment(R.layout.tracing_details_fragment_layout ) private val binding: TracingDetailsFragmentLayoutBinding by viewBindingLazy() - private val detailsAdapter = TracingDetailsAdapter() + private val detailsAdapter = TracingDetailsAdapter { vm.onItemClicked(it) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -49,6 +49,14 @@ class TracingDetailsFragment : Fragment(R.layout.tracing_details_fragment_layout binding.tracingDetailsState = it } + vm.routeToScreen.observe2(this) { + when (it) { + is TracingDetailsNavigationEvents.NavigateToSurveyConsentFragment -> doNavigate( + TracingDetailsFragmentDirections.actionRiskDetailsFragmentToSurveyConsentFragment(it.type) + ) + } + } + binding.riskDetailsHeaderButtonBack.setOnClickListener { (activity as MainActivity).goBack() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt index a6e23363794e73848da3ed4cc3c54133b8e8fae1..df6725509fbc286e8108d1f01abe82866a2596c0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsFragmentViewModel.kt @@ -21,8 +21,10 @@ import de.rki.coronawarnapp.tracing.ui.details.items.risk.LowRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.risk.TracingDisabledBox import de.rki.coronawarnapp.tracing.ui.details.items.risk.TracingFailedBox import de.rki.coronawarnapp.tracing.ui.details.items.risk.TracingProgressBox +import de.rki.coronawarnapp.tracing.ui.details.items.survey.UserSurveyBox import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.device.BackgroundModeStatus +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 @@ -90,6 +92,8 @@ class TracingDetailsFragmentViewModel @AssistedInject constructor( .onCompletion { Timber.v("TracingDetailsState FLOW completed.") } .asLiveData(dispatcherProvider.Default) + val routeToScreen: SingleLiveEvent<TracingDetailsNavigationEvents> = SingleLiveEvent() + fun refreshData() { launch { tracingRepository.refreshRiskLevel() @@ -101,6 +105,13 @@ class TracingDetailsFragmentViewModel @AssistedInject constructor( tracingRepository.refreshDiagnosisKeys() } + fun onItemClicked(item: DetailsItem) { + when (item) { + is UserSurveyBox.Item -> + routeToScreen.postValue(TracingDetailsNavigationEvents.NavigateToSurveyConsentFragment(item.type)) + } + } + @AssistedFactory interface Factory : SimpleCWAViewModelFactory<TracingDetailsFragmentViewModel> } 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 22b2b6df8467f9e02f043b10e51645901b05f425..39ee3aac120db01ab303ce57138d75e94dd23886 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 @@ -1,11 +1,13 @@ package de.rki.coronawarnapp.tracing.ui.details import dagger.Reusable +import de.rki.coronawarnapp.datadonation.survey.Surveys import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.risk.tryLatestResultsWithDefaults import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.tracing.GeneralTracingStatus +import de.rki.coronawarnapp.tracing.GeneralTracingStatus.Status import de.rki.coronawarnapp.tracing.ui.details.items.DetailsItem import de.rki.coronawarnapp.tracing.ui.details.items.additionalinfos.AdditionalInfoLowRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.behavior.BehaviorIncreasedRiskBox @@ -14,6 +16,7 @@ import de.rki.coronawarnapp.tracing.ui.details.items.periodlogged.PeriodLoggedBo import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsFailedCalculationBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsLowRiskBox +import de.rki.coronawarnapp.tracing.ui.details.items.survey.UserSurveyBox import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.onCompletion @@ -53,23 +56,30 @@ class TracingDetailsItemProvider @Inject constructor( ) }.also { add(it) } - if (latestCalc.riskState != RiskState.CALCULATION_FAILED) { + if (latestCalc.riskState == RiskState.INCREASED_RISK) { + add(UserSurveyBox.Item(Surveys.Type.HIGH_RISK_ENCOUNTER)) + } + + if (latestCalc.riskState != RiskState.CALCULATION_FAILED && status != Status.TRACING_INACTIVE) { PeriodLoggedBox.Item( activeTracingDaysInRetentionPeriod = activeTracingDaysInRetentionPeriod.toInt() ).also { add(it) } } - when (latestCalc.riskState) { - RiskState.LOW_RISK -> DetailsLowRiskBox.Item( + when { + status == Status.TRACING_INACTIVE || latestCalc.riskState == RiskState.CALCULATION_FAILED -> { + DetailsFailedCalculationBox.Item + } + latestCalc.riskState == RiskState.LOW_RISK -> DetailsLowRiskBox.Item( riskState = latestCalc.riskState, matchedKeyCount = latestCalc.matchedKeyCount ) - RiskState.INCREASED_RISK -> DetailsIncreasedRiskBox.Item( + latestCalc.riskState == RiskState.INCREASED_RISK -> DetailsIncreasedRiskBox.Item( riskState = latestCalc.riskState, lastEncounteredAt = latestCalc.lastRiskEncounterAt ?: Instant.EPOCH ) - RiskState.CALCULATION_FAILED -> DetailsFailedCalculationBox.Item - }.also { add(it) } + else -> null + }?.let { add(it) } } } .onStart { Timber.v("TracingDetailsState FLOW start") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt new file mode 100644 index 0000000000000000000000000000000000000000..221ebc91e8e9ebd767706a140276e2647c3f1749 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsNavigationEvents.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.tracing.ui.details + +import de.rki.coronawarnapp.datadonation.survey.Surveys + +sealed class TracingDetailsNavigationEvents { + class NavigateToSurveyConsentFragment(val type: Surveys.Type) : TracingDetailsNavigationEvents() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/behavior/BehaviorInfoRow.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/behavior/BehaviorInfoRow.kt index 1e511812d472a67993cd81160d7d82a68ba5ac7f..9c7a0c565df370eaf870b874126789dc6d5ce63d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/behavior/BehaviorInfoRow.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/behavior/BehaviorInfoRow.kt @@ -14,6 +14,7 @@ import androidx.core.content.withStyledAttributes import androidx.core.view.ViewCompat import androidx.core.widget.ImageViewCompat import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.util.setUrl class BehaviorInfoRow @JvmOverloads constructor( context: Context, @@ -45,6 +46,14 @@ class BehaviorInfoRow @JvmOverloads constructor( else getString(R.styleable.TracingDetailsBehaviorRow_android_text) } } + + if (body.text == context.getString(R.string.risk_details_increased_risk_faq_link_text)) { + body.setUrl( + R.string.risk_details_increased_risk_faq_link_text, + R.string.risk_details_increased_risk_faq_link_label, + R.string.risk_details_increased_risk_faq_url + ) + } } fun setText(text: String) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/survey/UserSurveyBox.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/survey/UserSurveyBox.kt new file mode 100644 index 0000000000000000000000000000000000000000..bdb5a7a04a06b286db0ea9b83048b2da3b880cd5 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/tracing/ui/details/items/survey/UserSurveyBox.kt @@ -0,0 +1,41 @@ +package de.rki.coronawarnapp.tracing.ui.details.items.survey + +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.TracingDetailsAccessSurveyCardBinding +import de.rki.coronawarnapp.datadonation.survey.Surveys +import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsAdapter +import de.rki.coronawarnapp.tracing.ui.details.items.DetailsItem + +class UserSurveyBox( + parent: ViewGroup, + private val onItemClickListener: (item: DetailsItem) -> Unit, + @LayoutRes containerLayout: Int = R.layout.home_card_container_layout +) : TracingDetailsAdapter.DetailsItemVH<UserSurveyBox.Item, TracingDetailsAccessSurveyCardBinding>( + containerLayout, + parent +) { + + override val viewBinding: Lazy<TracingDetailsAccessSurveyCardBinding> = lazy { + TracingDetailsAccessSurveyCardBinding.inflate( + layoutInflater, + itemView.findViewById(R.id.card_container), + true + ) + } + + override val onBindData: TracingDetailsAccessSurveyCardBinding.( + item: Item, + payloads: List<Any> + ) -> Unit = { item, _ -> + tracingDetailsSurveyCardButton.setOnClickListener { onItemClickListener(item) } + } + + data class Item( + val type: Surveys.Type + ) : DetailsItem { + override val stableId: Long + get() = Item::class.java.name.hashCode().toLong() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt index be99bd0647f1cfd07e087f595989465870bc5099..4f190973a29828954fcdc0c1c44a06702f5f79c5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/ActivityBinder.kt @@ -2,6 +2,9 @@ package de.rki.coronawarnapp.ui import dagger.Module import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryRetentionModule +import de.rki.coronawarnapp.contactdiary.storage.ContactDiaryStorageModule +import de.rki.coronawarnapp.contactdiary.ui.ContactDiaryUIModule import de.rki.coronawarnapp.ui.launcher.LauncherActivity import de.rki.coronawarnapp.ui.launcher.LauncherActivityModule import de.rki.coronawarnapp.ui.main.MainActivity @@ -10,9 +13,20 @@ import de.rki.coronawarnapp.ui.main.MainActivityTestModule import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity import de.rki.coronawarnapp.ui.onboarding.OnboardingActivityModule -@Module +@Module( + includes = [ + ContactDiaryStorageModule::class, + ContactDiaryRetentionModule::class + ] +) abstract class ActivityBinder { - @ContributesAndroidInjector(modules = [MainActivityModule::class, MainActivityTestModule::class]) + @ContributesAndroidInjector( + modules = [ + MainActivityModule::class, + MainActivityTestModule::class, + ContactDiaryUIModule::class + ] + ) abstract fun mainActivity(): MainActivity @ContributesAndroidInjector(modules = [LauncherActivityModule::class]) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt index ed1f696f784ab156db60f8c31b7db67cd5917bf2..3863ee8015d64f51679e2a9f09645580fad0d187 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/UIExtensions.kt @@ -1,7 +1,17 @@ package de.rki.coronawarnapp.ui +import android.os.Bundle +import androidx.core.view.isVisible import androidx.navigation.NavController +import androidx.navigation.NavDestination import androidx.navigation.NavDirections +import androidx.navigation.ui.NavigationUI +import androidx.navigation.ui.setupWithNavController +import com.google.android.material.bottomnavigation.BottomNavigationView +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragment +import de.rki.coronawarnapp.ui.main.home.HomeFragment +import java.lang.ref.WeakReference /** * Extends NavController to prevent navigation error when the user clicks on two buttons at almost @@ -13,3 +23,45 @@ fun NavController.doNavigate(direction: NavDirections) { currentDestination?.getAction(direction.actionId) ?.let { navigate(direction) } } + +/** + * Similar to [setupWithNavController],but it executes the passed action on item selection + * and shows [BottomNavigationView] on [HomeFragment], [ContactDiaryOverviewFragment] only + */ +fun BottomNavigationView.setupWithNavController2( + navController: NavController, + onItemSelected: () -> Unit +) { + setupWithNavController(navController) + setOnNavigationItemSelectedListener { item -> + onItemSelected() + NavigationUI.onNavDestinationSelected(item, navController) + } + val weakBottomNavView = WeakReference(this) + navController.addOnDestinationChangedListener( + object : NavController.OnDestinationChangedListener { + override fun onDestinationChanged( + controller: NavController, + destination: NavDestination, + arguments: Bundle? + ) { + val bottomView = weakBottomNavView.get() + // Remove listener if View does not exit + if (bottomView == null) { + navController.removeOnDestinationChangedListener(this) + return + } + // For destinations that always show the bottom bar + val inShowList = destination.id in listOf( + R.id.mainFragment, + R.id.contactDiaryOverviewFragment + ) + // For destinations that can show or hide the bottom bar in different cases + // for example [ContactDiaryOnboardingFragment] + val hasShowArgument = arguments?.getBoolean("showBottomNav") ?: false + + bottomView.isVisible = inShowList || hasShowArgument + } + } + ) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt index 053520fcdf03846d17fb0fd564e4650806d80e12..d8163148d1f9e9d2e8ad6fee06c69211cc5e740f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivity.kt @@ -9,18 +9,23 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.navigation.NavController +import androidx.navigation.NavGraph import dagger.android.AndroidInjector import dagger.android.DispatchingAndroidInjector import dagger.android.HasAndroidInjector import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.retention.ContactDiaryWorkScheduler +import de.rki.coronawarnapp.databinding.ActivityMainBinding import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.ui.base.startActivitySafely +import de.rki.coronawarnapp.ui.setupWithNavController2 import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.ConnectivityHelper import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.device.PowerManagement import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.util.ui.findNavController import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import de.rki.coronawarnapp.worker.BackgroundWorkScheduler @@ -35,8 +40,6 @@ import javax.inject.Inject */ class MainActivity : AppCompatActivity(), HasAndroidInjector { companion object { - private val TAG: String? = MainActivity::class.simpleName - fun start(context: Context) { context.startActivity(Intent(context, MainActivity::class.java)) } @@ -55,14 +58,15 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first() @Inject lateinit var powerManagement: PowerManagement - @Inject lateinit var deadmanScheduler: DeadmanNotificationScheduler @Inject lateinit var contactDiaryWorkScheduler: ContactDiaryWorkScheduler override fun onCreate(savedInstanceState: Bundle?) { AppInjector.setup(this) super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + + val binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) if (CWADebug.isDeviceForTestersBuild) { vm.showEnvironmentHint.observe(this) { @@ -76,6 +80,23 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { vm.showEnergyOptimizedEnabledForBackground.observe(this) { showEnergyOptimizedEnabledForBackground() } + + val navController = supportFragmentManager.findNavController(R.id.nav_host_fragment) + binding.mainBottomNavigation.setupWithNavController2(navController) { + vm.onBottomNavSelected() + } + vm.isOnboardingDone.observe(this) { isOnboardingDone -> + startNestedGraphDestination(navController, isOnboardingDone) + } + } + + private fun startNestedGraphDestination(navController: NavController, isOnboardingDone: Boolean) { + val nestedGraph = navController.graph.findNode(R.id.contact_diary_nav_graph) as NavGraph + nestedGraph.startDestination = if (isOnboardingDone) { + R.id.contactDiaryOverviewFragment + } else { + R.id.contactDiaryOnboardingFragment + } } /** diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt index 113dd5c88ee941a135f72ddfc8b7eae3c3eae191..fb861b80f148237d61e05bdef97be50872ecdd5b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityModule.kt @@ -5,14 +5,14 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import dagger.multibindings.IntoMap import de.rki.coronawarnapp.datadonation.analytics.ui.AnalyticsUIModule +import de.rki.coronawarnapp.release.NewReleaseInfoFragment +import de.rki.coronawarnapp.release.NewReleaseInfoFragmentModule import de.rki.coronawarnapp.tracing.ui.details.TracingDetailsFragmentModule import de.rki.coronawarnapp.ui.information.InformationFragmentModule import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragment import de.rki.coronawarnapp.ui.interoperability.InteroperabilityConfigurationFragmentModule import de.rki.coronawarnapp.ui.main.home.HomeFragmentModule import de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaInteroperabilityModule -import de.rki.coronawarnapp.ui.release.NewReleaseInfoFragment -import de.rki.coronawarnapp.ui.release.NewReleaseInfoFragmentModule import de.rki.coronawarnapp.ui.settings.SettingFragmentsModule import de.rki.coronawarnapp.ui.settings.SettingsResetFragment import de.rki.coronawarnapp.ui.settings.SettingsResetModule @@ -34,13 +34,6 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey ] ) abstract class MainActivityModule { - - // activity specific injection module for future dependencies - - // example: - // @ContributesAndroidInjector - // abstract fun mainFragment(): MainFragment - @ContributesAndroidInjector(modules = [InteroperabilityConfigurationFragmentModule::class]) abstract fun intertopConfigScreen(): InteroperabilityConfigurationFragment 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 37e600e8552e1caf9e6e6b797dfefeddac2bda40..f4c94877b38e49d3153cdcc70f89e5d24a258838 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 @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.main import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.environment.EnvironmentSetup import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.storage.LocalData @@ -16,7 +17,8 @@ import kotlinx.coroutines.flow.first class MainActivityViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val environmentSetup: EnvironmentSetup, - private val backgroundModeStatus: BackgroundModeStatus + private val backgroundModeStatus: BackgroundModeStatus, + private val contactDiarySettings: ContactDiarySettings ) : CWAViewModel( dispatcherProvider = dispatcherProvider ) { @@ -25,6 +27,7 @@ class MainActivityViewModel @AssistedInject constructor( val showBackgroundJobDisabledNotification = SingleLiveEvent<Unit>() val showEnergyOptimizedEnabledForBackground = SingleLiveEvent<Unit>() + val isOnboardingDone = SingleLiveEvent<Boolean>() init { if (CWADebug.isDeviceForTestersBuild) { @@ -60,6 +63,10 @@ class MainActivityViewModel @AssistedInject constructor( } } + fun onBottomNavSelected() { + isOnboardingDone.value = contactDiarySettings.isOnboardingDone + } + private suspend fun checkForEnergyOptimizedEnabled() { if (!backgroundModeStatus.isIgnoringBatteryOptimizations.first()) { showEnergyOptimizedEnabledForBackground.postValue(Unit) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt index 5e7565d58883d2f8959be2b0f45c252f787ce799..e971e4ee82b66d74d8964ea5b51fb4d39bc1a17e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeAdapter.kt @@ -18,7 +18,6 @@ import de.rki.coronawarnapp.tracing.ui.homecards.LowRiskCard import de.rki.coronawarnapp.tracing.ui.homecards.TracingDisabledCard import de.rki.coronawarnapp.tracing.ui.homecards.TracingFailedCard import de.rki.coronawarnapp.tracing.ui.homecards.TracingProgressCard -import de.rki.coronawarnapp.ui.main.home.items.DiaryCard import de.rki.coronawarnapp.ui.main.home.items.FAQCard import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.util.lists.BindableVH @@ -26,6 +25,7 @@ import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffUtilAdapter import de.rki.coronawarnapp.util.lists.diffutil.AsyncDiffer import de.rki.coronawarnapp.util.lists.modular.ModularAdapter import de.rki.coronawarnapp.util.lists.modular.mods.DataBinderMod +import de.rki.coronawarnapp.util.lists.modular.mods.SavedStateMod import de.rki.coronawarnapp.util.lists.modular.mods.StableIdMod import de.rki.coronawarnapp.util.lists.modular.mods.TypedVHCreatorMod @@ -53,9 +53,10 @@ class HomeAdapter : ModularAdapter<HomeAdapter.HomeItemVH<HomeItem, ViewBinding> TypedVHCreatorMod({ data[it] is TestReadyCard.Item }) { TestReadyCard(it) }, TypedVHCreatorMod({ data[it] is TestPendingCard.Item }) { TestPendingCard(it) }, TypedVHCreatorMod({ data[it] is TestUnregisteredCard.Item }) { TestUnregisteredCard(it) }, - TypedVHCreatorMod({ data[it] is DiaryCard.Item }) { DiaryCard(it) }, - TypedVHCreatorMod({ data[it] is StatisticsHomeCard.Item }) { StatisticsHomeCard(it) } - )) + TypedVHCreatorMod({ data[it] is StatisticsHomeCard.Item }) { StatisticsHomeCard(it) }, + SavedStateMod<HomeItemVH<HomeItem, ViewBinding>>() // For statistics card scroll position + ) + ) } override fun getItemCount(): Int = data.size diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt index f5236041c850c55a5bd3cae3d520a28d8a67a36f..656a937f2fe9c83b0e2d4d2c4185bff635eb8254 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt @@ -8,7 +8,6 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.contactdiary.ui.ContactDiaryActivity import de.rki.coronawarnapp.databinding.HomeFragmentLayoutBinding import de.rki.coronawarnapp.tracing.ui.TracingExplanationDialog import de.rki.coronawarnapp.ui.main.home.popups.DeviceTimeIncorrectDialog @@ -97,24 +96,20 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { onPositive = { vm.errorResetDialogDismissed() } ) } - HomeFragmentEvents.ShowDeleteTestDialog -> { - showRemoveTestDialog() - } - HomeFragmentEvents.GoToContactDiary -> { - context?.let { ContactDiaryActivity.start(it) } - } + HomeFragmentEvents.ShowDeleteTestDialog -> showRemoveTestDialog() - HomeFragmentEvents.ShowNewReleaseFragment -> { - doNavigate(HomeFragmentDirections.actionMainFragmentToNewReleaseInfoFragment(false)) - } - HomeFragmentEvents.GoToStatisticsExplanation -> { - doNavigate( - HomeFragmentDirections.actionMainFragmentToStatisticsExplanationFragment() - ) - } + HomeFragmentEvents.ShowNewReleaseFragment -> doNavigate( + HomeFragmentDirections.actionMainFragmentToNewReleaseInfoFragment(false) + ) + + HomeFragmentEvents.GoToStatisticsExplanation -> doNavigate( + HomeFragmentDirections.actionMainFragmentToStatisticsExplanationFragment() + ) } } + vm.showPopUpsOrNavigate() + vm.showLoweredRiskLevelDialog.observe2(this) { if (it) showRiskLevelLoweredDialog() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt index 9e54306e57ce3e0ba7ad2f634e44f5891bab773b..5069ba21fb6a88ce1c1395067f1b4ae9b5915f2c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt @@ -11,8 +11,6 @@ sealed class HomeFragmentEvents { object ShowDeleteTestDialog : HomeFragmentEvents() - object GoToContactDiary : HomeFragmentEvents() - object ShowNewReleaseFragment : HomeFragmentEvents() object GoToStatisticsExplanation : HomeFragmentEvents() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index 559d1ef7e6099d14e91db50b44e1dce1be67d024..15642b11a6b9afe71dd13f6afee71d8caf9ec306 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -51,7 +51,6 @@ import de.rki.coronawarnapp.tracing.ui.statusbar.toHeaderState import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowErrorResetDialog import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowInteropDeltaOnboarding import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents.ShowTracingExplanation -import de.rki.coronawarnapp.ui.main.home.items.DiaryCard import de.rki.coronawarnapp.ui.main.home.items.FAQCard import de.rki.coronawarnapp.ui.main.home.items.HomeItem import de.rki.coronawarnapp.util.DeviceUIState @@ -91,14 +90,20 @@ class HomeFragmentViewModel @AssistedInject constructor( .map { it.toHeaderState() } .asLiveData(dispatcherProvider.Default) - val popupEvents: SingleLiveEvent<HomeFragmentEvents> by lazy { - SingleLiveEvent<HomeFragmentEvents>().apply { - if (!LocalData.isInteroperabilityShownAtLeastOnce) { - postValue(ShowInteropDeltaOnboarding) - } else { + val popupEvents = SingleLiveEvent<HomeFragmentEvents>() + + fun showPopUpsOrNavigate() { + when { + !LocalData.isInteroperabilityShownAtLeastOnce -> { + popupEvents.postValue(ShowInteropDeltaOnboarding) + } + cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE -> { + popupEvents.postValue(HomeFragmentEvents.ShowNewReleaseFragment) + } + else -> { launch { if (!LocalData.tracingExplanationDialogWasShown()) { - postValue( + popupEvents.postValue( ShowTracingExplanation( TimeVariables.getActiveTracingDaysInRetentionPeriod() ) @@ -107,13 +112,10 @@ class HomeFragmentViewModel @AssistedInject constructor( } launch { if (errorResetTool.isResetNoticeToBeShown) { - postValue(ShowErrorResetDialog) + popupEvents.postValue(ShowErrorResetDialog) } } } - if (cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE) { - postValue(HomeFragmentEvents.ShowNewReleaseFragment) - } } } @@ -170,7 +172,7 @@ class HomeFragmentViewModel @AssistedInject constructor( onRetryClick = { refreshDiagnosisKeys() } ) } - } + }.distinctUntilChanged() private val submissionCardItems = submissionStateProvider.state.map { state -> when (state) { @@ -212,13 +214,13 @@ class HomeFragmentViewModel @AssistedInject constructor( } is SubmissionDone -> TestSubmissionDoneCard.Item(state) } - } + }.distinctUntilChanged() val homeItems: LiveData<List<HomeItem>> = combine( tracingCardItems, submissionCardItems, - submissionStateProvider.state, - statisticsProvider.current + submissionStateProvider.state.distinctUntilChanged(), + statisticsProvider.current.distinctUntilChanged() ) { tracingItem, submissionItem, submissionState, statsData -> mutableListOf<HomeItem>().apply { when (submissionState) { @@ -236,8 +238,6 @@ class HomeFragmentViewModel @AssistedInject constructor( })) } - add(DiaryCard.Item(onClickAction = { popupEvents.postValue(HomeFragmentEvents.GoToContactDiary) })) - add(FAQCard.Item(onClickAction = { openFAQUrlEvent.postValue(Unit) })) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/DiaryCard.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/DiaryCard.kt deleted file mode 100644 index 3cf7983ce2528d2e0fabf647e88edecba3e94f77..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/items/DiaryCard.kt +++ /dev/null @@ -1,38 +0,0 @@ -package de.rki.coronawarnapp.ui.main.home.items - -import android.view.ViewGroup -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.ContactDiaryHomescreenCardIncludeBinding -import de.rki.coronawarnapp.ui.main.home.HomeAdapter -import de.rki.coronawarnapp.ui.main.home.items.DiaryCard.Item -import de.rki.coronawarnapp.util.lists.diffutil.HasPayloadDiffer - -class DiaryCard(parent: ViewGroup) : HomeAdapter.HomeItemVH<Item, ContactDiaryHomescreenCardIncludeBinding>( - R.layout.home_card_container_layout, parent -) { - - override val viewBinding = lazy { - ContactDiaryHomescreenCardIncludeBinding.inflate( - layoutInflater, - itemView.findViewById(R.id.card_container), - true - ) - } - - override val onBindData: ContactDiaryHomescreenCardIncludeBinding.( - item: Item, - payloads: List<Any> - ) -> Unit = { item, payloads -> - itemView.setOnClickListener { - val curItem = payloads.filterIsInstance<Item>().singleOrNull() ?: item - curItem.onClickAction(item) - } - contactDiaryCardHomescreenButton.setOnClickListener { itemView.performClick() } - } - - data class Item(val onClickAction: (Item) -> Unit) : HomeItem, HasPayloadDiffer { - override val stableId: Long = Item::class.java.name.hashCode().toLong() - - override fun diffPayload(old: Any, new: Any): Any? = if (old::class == new::class) new else null - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragment.kt deleted file mode 100644 index 0e75882fc2e7469eacbf9b18525f849c1b23d5d8..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragment.kt +++ /dev/null @@ -1,62 +0,0 @@ -package de.rki.coronawarnapp.ui.release - -import android.os.Bundle -import android.view.View -import android.view.accessibility.AccessibilityEvent -import androidx.core.view.isGone -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.navArgs -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.databinding.NewReleaseInfoScreenFragmentBinding -import de.rki.coronawarnapp.util.di.AutoInject -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 -import javax.inject.Inject - -class NewReleaseInfoFragment : Fragment(R.layout.new_release_info_screen_fragment), AutoInject { - - @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory - - private val vm: NewReleaseInfoFragmentViewModel by cwaViewModels { viewModelFactory } - private val binding: NewReleaseInfoScreenFragmentBinding by viewBindingLazy() - private val args: NewReleaseInfoFragmentArgs by navArgs() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.apply { - newReleaseInfoNextButton.setOnClickListener { - vm.onNextButtonClick() - } - - newReleaseInfoToolbar.setNavigationOnClickListener { - vm.onNextButtonClick() - } - } - - vm.appVersion.observe2(this) { - binding.newReleaseInfoHeadline.text = it.get(requireContext()) - } - - if (args.comesFromInfoScreen) { - vm.navigationIcon.observe2(this) { - binding.newReleaseInfoToolbar.navigationIcon = it - } - } - - binding.newReleaseInfoNextButton.isGone = args.comesFromInfoScreen - - vm.routeToScreen.observe2(this) { - if (it is NewReleaseInfoFragmentNavigationEvents.CloseFragment) { - popBackStack() - } - } - } - - override fun onResume() { - super.onResume() - binding.newReleaseInfoScreenContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentNavigationEvents.kt deleted file mode 100644 index 5a6366a02b0da93e82e822c1c7b52d7e2fbbdc7f..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentNavigationEvents.kt +++ /dev/null @@ -1,5 +0,0 @@ -package de.rki.coronawarnapp.ui.release - -sealed class NewReleaseInfoFragmentNavigationEvents { - object CloseFragment : NewReleaseInfoFragmentNavigationEvents() -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentViewModel.kt deleted file mode 100644 index 4beec938fcca7a9d57355f2a01638ab3053cc71b..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/release/NewReleaseInfoFragmentViewModel.kt +++ /dev/null @@ -1,43 +0,0 @@ -package de.rki.coronawarnapp.ui.release - -import android.content.Context -import androidx.lifecycle.asLiveData -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.BuildConfig -import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.environment.BuildConfigWrap -import de.rki.coronawarnapp.main.CWASettings -import de.rki.coronawarnapp.ui.SingleLiveEvent -import de.rki.coronawarnapp.util.ContextExtensions.getDrawableCompat -import de.rki.coronawarnapp.util.coroutine.DispatcherProvider -import de.rki.coronawarnapp.util.di.AppContext -import de.rki.coronawarnapp.util.ui.toResolvingString -import de.rki.coronawarnapp.util.viewmodel.CWAViewModel -import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOf - -class NewReleaseInfoFragmentViewModel @AssistedInject constructor( - dispatcherProvider: DispatcherProvider, - @AppContext private val context: Context, - var settings: CWASettings -) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val routeToScreen: SingleLiveEvent<NewReleaseInfoFragmentNavigationEvents> = SingleLiveEvent() - - fun onNextButtonClick() { - settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE } - routeToScreen.postValue(NewReleaseInfoFragmentNavigationEvents.CloseFragment) - } - - val appVersion = flow { - emit(R.string.release_info_version_title.toResolvingString(BuildConfig.VERSION_NAME)) - }.asLiveData(context = dispatcherProvider.Default) - - val navigationIcon = flowOf( - context.getDrawableCompat(R.drawable.ic_back) - ).asLiveData(context = dispatcherProvider.Default) - - @AssistedFactory - interface Factory : SimpleCWAViewModelFactory<NewReleaseInfoFragmentViewModel> -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt index a41b3c46567c7bb4f5bd73cf1e3f6515374a63ff..864629916a35ab0a185955bbbe351502c80e48c3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt @@ -9,7 +9,6 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultPendingBinding import de.rki.coronawarnapp.exception.http.CwaClientError import de.rki.coronawarnapp.exception.http.CwaServerError -import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.NetworkRequestWrapper @@ -119,30 +118,28 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio } } - private fun handleError(exception: CwaWebException) { - errorDialog = when (exception) { - is CwaClientError, is CwaServerError -> { - DialogHelper.showDialog(buildErrorDialog(exception)) - } - else -> { - DialogHelper.showDialog(genericErrorDialog) - } + private fun handleError(exception: Throwable) { + val dialogInstance = when (exception) { + is CwaClientError, is CwaServerError -> networkErrorDialog + else -> genericErrorDialog } + errorDialog = DialogHelper.showDialog(dialogInstance) } private fun navigateToMainScreen() { popBackStack() } - private fun buildErrorDialog(exception: CwaWebException) = DialogHelper.DialogInstance( - requireActivity(), - R.string.submission_error_dialog_web_generic_error_title, - R.string.submission_error_dialog_web_generic_network_error_body, - R.string.submission_error_dialog_web_generic_error_button_positive, - null, - true, - ::navigateToMainScreen - ) + private val networkErrorDialog: DialogHelper.DialogInstance + get() = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + R.string.submission_error_dialog_web_generic_network_error_body, + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToMainScreen + ) private val genericErrorDialog: DialogHelper.DialogInstance get() = DialogHelper.DialogInstance( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt index 931a617e78df44f4836e5e604c99e6aefb4078f9..72772175d3c7ef5b2e19bc214082cdc3206b3c26 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState @@ -90,7 +89,7 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( .asLiveData(context = dispatcherProvider.Default) val cwaWebExceptionLiveData = submissionRepository.deviceUIStateFlow - .filterIsInstance<NetworkRequestWrapper.RequestFailed<DeviceUIState, CwaWebException>>() + .filterIsInstance<NetworkRequestWrapper.RequestFailed<DeviceUIState, Throwable>>() .map { it.error } .asLiveData() 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 7bafedc84dc7597b0b5d06bebe9f1da504072641..b2c0555a70b90f928b90d414379154c12adcc23e 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 @@ -3,8 +3,9 @@ package de.rki.coronawarnapp.util import android.annotation.SuppressLint import android.content.Context import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository import de.rki.coronawarnapp.contactdiary.storage.ContactDiaryPreferences +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.datadonation.survey.SurveySettings import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysSettings import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.main.CWASettings @@ -38,7 +39,8 @@ class DataReset @Inject constructor( private val contactDiaryRepository: ContactDiaryRepository, private var contactDiaryPreferences: ContactDiaryPreferences, private val cwaSettings: CWASettings, - private val statisticsProvider: StatisticsProvider + private val statisticsProvider: StatisticsProvider, + private val surveySettings: SurveySettings ) { private val mutex = Mutex() @@ -66,6 +68,7 @@ class DataReset @Inject constructor( riskLevelStorage.clear() contactDiaryPreferences.clear() cwaSettings.clear() + surveySettings.clear() // Clear contact diary database contactDiaryRepository.clear() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt index 47d1c57e48abe6bf8d816ad9305c0d9366ea74ee..ddef4e3140281c5ddf59dd4ef68bffc640cd0537 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/NetworkRequestWrapper.kt @@ -1,13 +1,13 @@ package de.rki.coronawarnapp.util -sealed class NetworkRequestWrapper<out T, out U> { +sealed class NetworkRequestWrapper<out T : Any, out U : Any> { object RequestIdle : NetworkRequestWrapper<Nothing, Nothing>() object RequestStarted : NetworkRequestWrapper<Nothing, Nothing>() - data class RequestSuccessful<T, U>(val data: T) : NetworkRequestWrapper<T, U>() - data class RequestFailed<T, U>(val error: U) : NetworkRequestWrapper<T, U>() + data class RequestSuccessful<T : Any, U : Any>(val data: T) : NetworkRequestWrapper<T, U>() + data class RequestFailed<T : Any, U : Throwable>(val error: U) : NetworkRequestWrapper<T, U>() companion object { - fun <T, U, W> NetworkRequestWrapper<T, U>?.withSuccess(without: W, block: (data: T) -> W): W { + fun <T : Any, U : Any, W> NetworkRequestWrapper<T, U>?.withSuccess(without: W, block: (data: T) -> W): W { return if (this is RequestSuccessful) { block(this.data) } else { @@ -15,13 +15,13 @@ sealed class NetworkRequestWrapper<out T, out U> { } } - fun <T, U> NetworkRequestWrapper<T, U>?.withSuccess(block: (data: T) -> Unit) { + fun <T : Any, U : Any> NetworkRequestWrapper<T, U>?.withSuccess(block: (data: T) -> Unit) { if (this is RequestSuccessful) { block(this.data) } } - fun <T, U> NetworkRequestWrapper<T, U>?.withFailure(block: (error: U) -> Unit) { + fun <T : Any, U : Any> NetworkRequestWrapper<T, U>?.withFailure(block: (error: U) -> Unit) { if (this is RequestFailed) { block(this.error) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/StringBuilderExtension.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/StringBuilderExtension.kt new file mode 100644 index 0000000000000000000000000000000000000000..e1ac8d8c2768500f2b2a7dca651ce983d157ab17 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/StringBuilderExtension.kt @@ -0,0 +1,9 @@ +package de.rki.coronawarnapp.util + +import kotlin.text.StringBuilder + +object StringBuilderExtension { + + fun StringBuilder.appendWithTrailingSpace(str: String): StringBuilder = this.append(str).append(" ") + fun StringBuilder.appendWithLineBreak(str: String): StringBuilder = this.append(str).append(" \n ") +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt index 3d3cda2e2a3d9f3367d6a69c28b02a7823440477..e70f708ad45a87382060f765cff9ee3bb4d31560 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt @@ -12,6 +12,8 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner import androidx.navigation.NavDeepLinkBuilder import androidx.work.WorkManager +import com.google.android.gms.safetynet.SafetyNet +import com.google.android.gms.safetynet.SafetyNetClient import dagger.Module import dagger.Provides import de.rki.coronawarnapp.CoronaWarnApplication @@ -70,4 +72,8 @@ class AndroidModule { @Singleton @ProcessLifecycle fun procressLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get() + + @Provides + @Singleton + fun safetyNet(@AppContext context: Context): SafetyNetClient = SafetyNet.getClient(context) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt index 9c5d073cef3f719ac372ff64edfdc86be5f9c0fb..da4d4e081a5b64cb8664754d5b950c536c7513f6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ApplicationComponent.kt @@ -11,7 +11,6 @@ import de.rki.coronawarnapp.bugreporting.BugReporter import de.rki.coronawarnapp.bugreporting.BugReportingModule import de.rki.coronawarnapp.bugreporting.BugReportingSharedModule import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger -import de.rki.coronawarnapp.contactdiary.ContactDiaryRootModule import de.rki.coronawarnapp.datadonation.DataDonationModule import de.rki.coronawarnapp.diagnosiskeys.DiagnosisKeysModule import de.rki.coronawarnapp.diagnosiskeys.DownloadDiagnosisKeysTaskModule @@ -38,6 +37,7 @@ import de.rki.coronawarnapp.util.coroutine.CoroutineModule import de.rki.coronawarnapp.util.device.DeviceModule import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.security.SecurityModule import de.rki.coronawarnapp.util.serialization.SerializationModule import de.rki.coronawarnapp.util.worker.WorkerBinder import de.rki.coronawarnapp.verification.VerificationModule @@ -70,9 +70,9 @@ import javax.inject.Singleton BugReportingSharedModule::class, SerializationModule::class, WorkerBinder::class, - ContactDiaryRootModule::class, StatisticsModule::class, - DataDonationModule::class + DataDonationModule::class, + SecurityModule::class ] ) interface ApplicationComponent : AndroidInjector<CoronaWarnApplication> { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/gplay/GoogleApiVersion.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/gplay/GoogleApiVersion.kt new file mode 100644 index 0000000000000000000000000000000000000000..a4a883d420d5dc9c842a3234f6fdea68f7987c70 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/gplay/GoogleApiVersion.kt @@ -0,0 +1,20 @@ +package de.rki.coronawarnapp.util.gplay + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import dagger.Reusable +import de.rki.coronawarnapp.util.di.AppContext +import javax.inject.Inject + +@Reusable +class GoogleApiVersion @Inject constructor( + @AppContext private val context: Context +) { + + private val apiAvailability = GoogleApiAvailability.getInstance() + + fun isPlayServicesVersionAvailable(requiredVersion: Int): Boolean { + return apiAvailability.isGooglePlayServicesAvailable(context, requiredVersion) == ConnectionResult.SUCCESS + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/ModularAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/ModularAdapter.kt index bedaae51c24210ee6907c2a37b1fa36a4994ac06..347550c7e149b7092922dac45b536812c253cf8e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/ModularAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/ModularAdapter.kt @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.util.lists.modular import android.view.ViewGroup import androidx.annotation.CallSuper import androidx.annotation.LayoutRes +import androidx.recyclerview.widget.RecyclerView import de.rki.coronawarnapp.ui.lists.BaseAdapter abstract class ModularAdapter<VH : ModularAdapter.VH> : BaseAdapter<VH>() { @@ -41,9 +42,22 @@ abstract class ModularAdapter<VH : ModularAdapter.VH> : BaseAdapter<VH>() { override fun onBindBaseVH(holder: VH, position: Int, payloads: MutableList<Any>) { modules.filterIsInstance<Module.Binder<VH>>().forEach { it.onBindModularVH(this, holder, position, payloads) + it.onPostBind(this, holder, position) } } + @CallSuper + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + modules.filterIsInstance<Module.RecyclerViewLifecycle>().forEach { it.onAttachedToRecyclerView(recyclerView) } + super.onAttachedToRecyclerView(recyclerView) + } + + @CallSuper + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + modules.filterIsInstance<Module.RecyclerViewLifecycle>().forEach { it.onDetachedFromRecyclerView(recyclerView) } + super.onDetachedFromRecyclerView(recyclerView) + } + abstract class VH(@LayoutRes layoutRes: Int, parent: ViewGroup) : BaseAdapter.VH(layoutRes, parent) interface Module { @@ -56,7 +70,8 @@ abstract class ModularAdapter<VH : ModularAdapter.VH> : BaseAdapter<VH>() { } interface Binder<T : VH> : Module { - fun onBindModularVH(adapter: ModularAdapter<T>, vh: T, pos: Int, payloads: MutableList<Any>) + fun onBindModularVH(adapter: ModularAdapter<T>, vh: T, pos: Int, payloads: MutableList<Any>) {} + fun onPostBind(adapter: ModularAdapter<T>, vh: T, pos: Int) {} } interface Typing : Module { @@ -66,5 +81,10 @@ abstract class ModularAdapter<VH : ModularAdapter.VH> : BaseAdapter<VH>() { interface ItemId : Module { fun getItemId(adapter: ModularAdapter<*>, position: Int): Long? } + + interface RecyclerViewLifecycle : Module { + fun onDetachedFromRecyclerView(recyclerView: RecyclerView) + fun onAttachedToRecyclerView(recyclerView: RecyclerView) + } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt new file mode 100644 index 0000000000000000000000000000000000000000..b517643cde2b790c471c1374f7de7cfd41926f85 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/lists/modular/mods/SavedStateMod.kt @@ -0,0 +1,75 @@ +package de.rki.coronawarnapp.util.lists.modular.mods + +import android.os.Parcelable +import androidx.annotation.Keep +import androidx.fragment.app.Fragment +import androidx.fragment.app.findFragment +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.util.lists.modular.ModularAdapter +import timber.log.Timber + +@Keep +class SavedStateMod<T : ModularAdapter.VH> : ModularAdapter.Module.RecyclerViewLifecycle, + ModularAdapter.Module.Binder<T> { + + private val savedStates = mutableMapOf<String, Parcelable>() + + override fun onPostBind(adapter: ModularAdapter<T>, vh: T, pos: Int) { + if (vh !is StateSavingVH) return + val key = vh.savedStateKey ?: return + + savedStates.remove(key)?.let { vh.restoreState(it) } + super.onPostBind(adapter, vh, pos) + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + val hostLifecycle = recyclerView.hostLifecycle ?: return + + val observer = object : LifecycleObserver { + @OnLifecycleEvent(Lifecycle.Event.ON_STOP) + fun onStop() { + savedStates.clear() + + getAllViewHolders(recyclerView).filterIsInstance<StateSavingVH>().forEach { vh -> + val key = vh.savedStateKey + val state = vh.onSaveState() + if (key != null && state != null) { + savedStates[key] = state + } + } + + hostLifecycle.removeObserver(this) + } + } + hostLifecycle.addObserver(observer) + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + // NOOP + } + + private val RecyclerView.hostLifecycle: Lifecycle? + get() = try { + findFragment<Fragment>().viewLifecycleOwner.lifecycle + } catch (e: Exception) { + null + } + + private fun getAllViewHolders(recyclerView: RecyclerView): List<RecyclerView.ViewHolder> = try { + (0..recyclerView.childCount) + .mapNotNull { recyclerView.getChildAt(it) } + .mapNotNull { recyclerView.getChildViewHolder(it) } + } catch (e: Exception) { + Timber.e(e, "getAllViewHolders() failed.") + emptyList() + } + + interface StateSavingVH { + val savedStateKey: String? + fun onSaveState(): Parcelable? + fun restoreState(state: Parcelable) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt index eb123c167aa7599cf2c0f198c4a755fda248abeb..c811176af4423b34caca3e1e945c6e841c50db17 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityHelper.kt @@ -2,7 +2,6 @@ package de.rki.coronawarnapp.util.security import android.annotation.SuppressLint import android.content.SharedPreferences -import android.os.Build import android.util.Base64 import androidx.annotation.VisibleForTesting import de.rki.coronawarnapp.exception.CwaSecurityException @@ -14,7 +13,6 @@ import de.rki.coronawarnapp.util.security.SecurityConstants.DB_PASSWORD_MAX_LENG import de.rki.coronawarnapp.util.security.SecurityConstants.DB_PASSWORD_MIN_LENGTH import de.rki.coronawarnapp.util.security.SecurityConstants.ENCRYPTED_SHARED_PREFERENCES_FILE import timber.log.Timber -import java.security.SecureRandom /** * Key Store and Password Access @@ -78,11 +76,7 @@ object SecurityHelper { } private fun generateDBPassword(): ByteArray { - val secureRandom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - SecureRandom.getInstanceStrong() - } else { - SecureRandom() - } + val secureRandom = SecurityModule().secureRandom() val max = DB_PASSWORD_MAX_LENGTH val min = DB_PASSWORD_MIN_LENGTH val passwordLength = secureRandom.nextInt(max - min + 1) + min diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..a7eebea7b14130d56537078d763f04c3af94fa73 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/security/SecurityModule.kt @@ -0,0 +1,17 @@ +package de.rki.coronawarnapp.util.security + +import android.os.Build +import dagger.Module +import dagger.Provides +import java.security.SecureRandom + +@Module +class SecurityModule { + + @Provides + fun secureRandom(): SecureRandom = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + SecureRandom.getInstanceStrong() + } else { + SecureRandom() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt index 493fd21f626f5e4e3438dcae4e0911d157a31e5a..4cdc9ec41071c7d3b397661ba3cc01786c07a905 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/FragmentExtensions.kt @@ -1,7 +1,13 @@ package de.rki.coronawarnapp.util.ui +import android.app.Activity +import androidx.annotation.IdRes import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentContainerView +import androidx.fragment.app.FragmentManager +import androidx.navigation.NavController import androidx.navigation.NavDirections +import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import de.rki.coronawarnapp.ui.doNavigate import timber.log.Timber @@ -17,3 +23,15 @@ fun Fragment.popBackStack(): Boolean { } return findNavController().popBackStack() } + +/** + * [FragmentContainerView] does not access [NavController] in [Activity.onCreate] + * as workaround [FragmentManager] is used to get the [NavController] + * @param id [Int] NavFragment id + * @see <a href="https://issuetracker.google.com/issues/142847973">issue-142847973</a> + */ +@Throws(IllegalStateException::class) +fun FragmentManager.findNavController(@IdRes id: Int): NavController { + val fragment = findFragmentById(id) ?: throw IllegalStateException("Fragment is not found for id:$id") + return NavHostFragment.findNavController(fragment) +} diff --git a/Corona-Warn-App/src/main/res/color/nav_item_color.xml b/Corona-Warn-App/src/main/res/color/nav_item_color.xml new file mode 100644 index 0000000000000000000000000000000000000000..943ce8e797addd2da47e8e80f550d2d96c1b6ce1 --- /dev/null +++ b/Corona-Warn-App/src/main/res/color/nav_item_color.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@color/navItemColorSelected" android:state_selected="true" /> + <item android:color="@color/navItemColorNormal" /> +</selector> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/drawable-night/ic_new_release_info.xml b/Corona-Warn-App/src/main/res/drawable-night/ic_new_release_info.xml new file mode 100644 index 0000000000000000000000000000000000000000..b79f1552bfd3bc62e76042e10c5757c155384c69 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable-night/ic_new_release_info.xml @@ -0,0 +1,41 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="180dp" + android:height="180dp" + android:viewportWidth="180" + android:viewportHeight="180"> + <path + android:fillColor="#3F3F43" + android:pathData="M90,180C139.706,180 180,139.706 180,90C180,40.294 139.706,0 90,0C40.294,0 0,40.294 0,90C0,139.706 40.294,180 90,180Z" /> + <path + android:fillColor="#F6B893" + android:pathData="M41.34,115.979C41.75,107.969 42.36,99.979 42.51,91.949C42.83,75.229 42.39,58.499 42.29,41.779C34.11,39.529 28.39,37.999 28.39,37.999C28.39,37.999 13.38,33.399 12.18,40.399C10.98,47.399 27.79,53.409 27.79,53.409L29.98,54.119L29.91,63.199C29.91,63.199 13.18,78.419 13.38,84.029C13.58,89.629 22.39,88.429 22.39,88.429C22.39,88.429 15.32,102.589 18.59,107.049C20.99,110.319 23.99,109.449 23.99,109.449C23.99,109.449 16,114.889 18.39,121.059C19.44,123.779 27.6,125.659 27.6,125.659L41,127.129C40.52,123.389 40.83,119.679 41.34,115.979Z" /> + <path + android:fillColor="#4A4A4A" + android:pathData="M90.97,11.35H40.48C34.35,11.35 29.37,16.33 29.37,22.46V148.43C29.37,154.56 34.35,159.54 40.48,159.54H90.97C97.1,159.54 102.08,154.56 102.08,148.43V22.46C102.08,16.33 97.1,11.35 90.97,11.35ZM98.49,148.43C98.49,152.57 95.12,155.94 90.98,155.94H40.48C36.34,155.94 32.97,152.57 32.97,148.43V22.46C32.97,18.32 36.34,14.95 40.48,14.95H90.97C95.11,14.95 98.48,18.32 98.48,22.46V148.43H98.49Z" /> + <path + android:fillColor="#121212" + android:pathData="M90.97,14.939H40.48C36.34,14.939 32.97,18.309 32.97,22.449V148.419C32.97,152.559 36.34,155.929 40.48,155.929H90.97C95.11,155.929 98.48,152.559 98.48,148.419V22.459C98.49,18.309 95.12,14.939 90.97,14.939Z" /> + <path + android:fillColor="#F6B893" + android:fillType="evenOdd" + android:pathData="M163.91,124.93C163.91,124.93 152.17,122.04 138.53,114.45C136.94,113.56 137.72,110.54 137.12,108.2C136.94,107.03 136.72,105.81 136.47,104.55C135.59,100.1 134.78,97.68 132.15,89.17C129.46,80.48 129.26,76.43 123.15,65.86C123.15,65.86 119.07,48.94 113,42.72C112.04,41.73 111.39,40.51 111.15,39.17C110.37,34.72 108.61,25.38 107.66,25.3C106.41,25.19 100.15,24.67 97.74,32.28C95.45,39.51 95.81,52.8 102.06,58.04C102.07,64.98 102.17,146.31 102.14,146.56C101.5,153.6 100.86,153.15 99.57,155.41C113.73,161.16 131.79,169.73 131.79,169.73C131.79,169.73 143.18,165.07 155,152.24C167.48,138.69 171.78,127.59 171.78,127.59L163.91,124.93Z" /> + <path + android:fillColor="#282828" + android:fillType="evenOdd" + android:pathData="M42.28,27.391H88.48C91.17,27.391 93.34,29.321 93.34,31.691V40.291C93.34,42.671 91.16,44.591 88.48,44.591H42.28C39.59,44.591 37.42,42.661 37.42,40.291V31.691C37.42,29.311 39.6,27.391 42.28,27.391Z" /> + <path + android:fillColor="#282828" + android:fillType="evenOdd" + android:pathData="M42.28,52.121H88.48C91.17,52.121 93.34,54.051 93.34,56.421V91.911C93.34,94.291 91.16,96.211 88.48,96.211H42.28C39.59,96.211 37.42,94.281 37.42,91.911V56.421C37.42,54.051 39.6,52.121 42.28,52.121Z" /> + <path + android:fillColor="#282828" + android:fillType="evenOdd" + android:pathData="M42.28,104.82H88.48C91.17,104.82 93.34,106.75 93.34,109.12V142.46C93.34,144.84 91.16,146.76 88.48,146.76H42.28C39.59,146.76 37.42,144.83 37.42,142.46V109.12C37.42,106.74 39.6,104.82 42.28,104.82Z" /> + <path + android:fillColor="#657888" + android:pathData="M65.25,106.891C76.848,106.891 86.25,97.713 86.25,86.391C86.25,75.069 76.848,65.891 65.25,65.891C53.652,65.891 44.25,75.069 44.25,86.391C44.25,97.713 53.652,106.891 65.25,106.891Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M55.655,86.881C56.532,86.008 57.954,86.008 58.827,86.881L61.816,89.861C62.06,90.104 62.459,90.104 62.703,89.861L72.882,79.705C73.823,78.765 75.35,78.765 76.294,79.705C77.235,80.645 77.235,82.168 76.294,83.108L63.996,95.38C63.17,96.207 61.83,96.207 61,95.38L55.655,90.047C54.782,89.174 54.782,87.757 55.655,86.881Z" /> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_faq_information.xml b/Corona-Warn-App/src/main/res/drawable/ic_faq_information.xml new file mode 100644 index 0000000000000000000000000000000000000000..85da8f26c888463a37cf8f7542b1d6dbad1945a4 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_faq_information.xml @@ -0,0 +1,17 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="23dp" + android:viewportWidth="20" + android:viewportHeight="23"> + <path + android:fillColor="#ffffff" + android:pathData="M5.9861,0.8335L14.0139,0.8335A0.9861,0.9861 0,0 1,15 1.8196L15,1.8196A0.9861,0.9861 0,0 1,14.0139 2.8057L5.9861,2.8057A0.9861,0.9861 0,0 1,5 1.8196L5,1.8196A0.9861,0.9861 0,0 1,5.9861 0.8335z" /> + <path + android:fillColor="#00000000" + android:pathData="M7.087,6.9143V3.8055H12.913V6.9143V7.1024L12.9813,7.2776L16.411,16.0749C17.4334,18.6975 15.4991,21.5278 12.6842,21.5278H7.3158C4.5009,21.5278 2.5666,18.6975 3.589,16.0749L7.0187,7.2776L7.087,7.1024V6.9143Z" + android:strokeWidth="2" + android:strokeColor="#ffffff" /> + <path + android:fillColor="#ffffff" + android:pathData="M3.5,17.1335L4.5,20.0919L5.5,21.0789L10,21.5719L15.5,20.5858L16.5,17.1335C16.8333,16.1474 16.4,12.0763 14,13.6541C11.6,15.2319 8,18.5713 4,16.6114L3.5,17.1335Z" /> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_illustration_datenspende.xml b/Corona-Warn-App/src/main/res/drawable/ic_illustration_datenspende.xml new file mode 100644 index 0000000000000000000000000000000000000000..e90a4704e1bac669b3f516a451028bd1708f166f --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_illustration_datenspende.xml @@ -0,0 +1,157 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="360dp" + android:height="185dp" + android:viewportWidth="360" + android:viewportHeight="185"> + <path + android:fillColor="#E8F5FF" + android:fillType="evenOdd" + android:pathData="M230.919,133.645C273.583,133.645 292.169,106.736 332.003,111.83C341.472,113.041 353.496,116.824 367,127.435V157.207L-8,159.288V125.288C4,120.288 8,122.288 21,125.288C64.498,135.326 115.003,109.323 138.503,108.413C140.698,108.328 142.815,108.288 144.85,108.288C195.624,108.284 198.052,133.645 230.919,133.645Z" /> + <group> + <clip-path + android:fillType="evenOdd" + android:pathData="M-7.961,133.288H367.096V166.288H-7.961V133.288Z" /> + <path + android:fillColor="#D8ECF9" + android:fillType="evenOdd" + android:pathData="M106.597,149.828C70.681,149.828 55.034,132.275 21.5,135.598C13.529,136.388 3.407,138.856 -7.961,145.777V165.197L367.097,166.288V152.224C365.015,151.253 362.939,150.423 360.974,149.816C360.974,149.816 323.18,137.903 281.325,148.096C245.476,156.835 204.18,133.963 184.396,133.37C182.549,133.314 180.766,133.288 179.053,133.288C136.31,133.286 134.266,149.828 106.597,149.828Z" /> + </group> + <path + android:fillColor="#B8E0FA" + android:fillType="evenOdd" + android:pathData="M288.671,126.16C288.671,126.16 274.869,106.905 284.581,104.519C294.294,102.134 293.612,114.062 300.088,109.631C306.563,105.201 295.161,84.554 310.573,82.508C329.624,79.981 315.179,99.89 313.836,104.783C313.55,105.823 313.854,106.953 314.668,107.661C316.328,109.105 321.699,109.474 329.737,104.519C342.177,96.851 333.316,132.465 309.971,133.487C309.971,133.487 295.317,133.828 288.671,126.16Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M307.322,133.593C307.311,133.507 307.001,124.844 306.717,115.255C306.333,102.35 308.349,91.905 310.899,88.248L311.408,88.767C305.431,97.337 308.604,133.183 308.646,133.526L307.322,133.593Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M307.551,127.486C307.507,127.472 303.058,126.08 298.401,123.455C294.085,121.021 288.7,116.998 287.557,111.65L287.859,111.36C289.991,121.341 307.37,126.242 307.546,126.296L307.551,127.486Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M307.487,124.102L307.375,122.935C307.545,122.882 324.4,118.144 327.204,110.783L327.573,111.095C326.364,114.269 322.611,117.424 316.419,120.472C311.827,122.733 307.53,124.088 307.487,124.102Z" /> + <path + android:fillColor="#B8E0FA" + android:fillType="evenOdd" + android:pathData="M38.992,43.291C33.473,43.441 30.315,51.868 30.051,55.87C28.926,72.999 19.154,85.217 20.058,111.9C20.652,129.383 26.37,147.347 32.418,150.491C44.063,156.547 51.876,143.06 53.191,130.481C54.506,117.903 55.294,99.607 46.617,76.738C41.8,64.039 50.043,42.989 38.992,43.291Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M38.557,152.643C37.528,147.893 35.22,124.088 35.159,98.318C35.122,82.671 35.912,66.293 38.32,53.001L38.968,53.127C36.569,66.376 35.781,82.71 35.818,98.318C35.878,124.035 38.177,147.764 39.2,152.493L38.557,152.643Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M35.845,122.732C35.869,122.69 43.451,109.301 44.619,103.905L45.263,104.056C44.072,109.557 36.438,123.04 36.414,123.083L35.845,122.732Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M35.228,92.462C35.211,92.439 28.679,84.119 29.118,77.912L29.716,77.964C29.298,83.889 35.667,91.998 35.684,92.02L35.228,92.462Z" /> + <path + android:fillColor="#B8E0FA" + android:fillType="evenOdd" + android:pathData="M63.111,153.573C68.352,149.667 72.911,142.82 73.59,126.783C74.268,110.746 72.938,106.813 69.188,105.412C65.439,104.012 62.491,106.007 60.388,111.19C58.285,116.375 57.405,121.804 55.357,123.549C53.309,125.293 41.595,135.014 44.896,146.029C48.198,157.044 57.713,156.153 63.111,153.573Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M51.709,154.603C53.75,151.785 58.164,143.848 61.771,134.759C64.191,128.662 66.251,122.042 66.993,116.092L66.483,116.026C65.745,121.934 63.698,128.51 61.293,134.571C57.703,143.617 53.318,151.506 51.292,154.302L51.709,154.603Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M58.758,141.558C58.366,141.853 66.87,138.414 69.272,133.819L68.832,133.585C66.52,138.007 58.896,140.92 58.874,140.929L58.758,141.558Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M54.611,149.963C54.602,149.943 51.466,142.964 51.725,138.299L51.251,138.288C50.985,143.094 54.173,150.188 54.182,150.208L54.611,149.963Z" /> + <path + android:fillColor="#D8ECF9" + android:fillType="evenOdd" + android:pathData="M267.985,7.147C265.714,7.607 262.907,9.681 263.002,10.288C263.035,10.5 270.118,10.3 277.398,10.288C288.375,10.271 293.863,10.288 294.562,10.288H308.403C309.484,10.288 307.501,6.726 306.742,6.1C305.429,5.018 303.437,4.543 301.759,5.053C299.461,5.751 299.504,7.765 297.33,8.194C295.911,8.474 294.387,7.914 293.454,7.147C292.032,5.978 292.623,4.856 291.239,3.483C290.27,2.521 289.006,2.079 287.364,2.436C285.447,2.852 285.703,2.959 282.934,5.053C281.962,5.789 280.912,7.902 279.059,8.194C274.03,8.987 271.932,6.347 267.985,7.147Z" /> + <path + android:fillColor="#D8ECF9" + android:fillType="evenOdd" + android:pathData="M7.75,21.288H37.201C36.069,19.735 34.948,19.245 34.133,19.085C32.131,18.69 31.285,20.154 27.998,20.186C27.414,20.192 25.971,20.194 24.316,19.635C23.335,19.304 23.308,19.122 21.862,18.534C20.667,18.048 19.619,17.621 18.181,17.432C17.083,17.288 16.259,17.348 15.113,17.432C13.812,17.528 13.009,17.593 12.045,17.983C11.33,18.273 10.878,18.599 10.205,19.085C9.555,19.554 8.669,20.269 7.75,21.288Z" /> + <path + android:fillColor="#D8ECF9" + android:fillType="evenOdd" + android:pathData="M95.787,11.288H58.854C60.273,9 61.68,8.277 62.701,8.041C65.213,7.46 66.272,9.617 70.396,9.664C71.129,9.673 72.938,9.676 75.012,8.853C76.243,8.364 76.277,8.095 78.09,7.229C79.588,6.513 80.904,5.884 82.706,5.605C84.084,5.393 85.117,5.482 86.554,5.605C88.185,5.746 89.191,5.842 90.401,6.417C91.299,6.844 91.865,7.324 92.71,8.041C93.524,8.732 94.635,9.786 95.787,11.288Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M238,37.288H196C195.448,37.288 195,36.84 195,36.288C195,35.736 195.448,35.288 196,35.288H238C238.552,35.288 239,35.736 239,36.288C239,36.84 238.552,37.288 238,37.288Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M230,44.288H196C195.448,44.288 195,43.84 195,43.288C195,42.736 195.448,42.288 196,42.288H230C230.552,42.288 231,42.736 231,43.288C231,43.84 230.552,44.288 230,44.288Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M224,30.288H196C195.448,30.288 195,29.84 195,29.288C195,28.736 195.448,28.288 196,28.288H224C224.552,28.288 225,28.736 225,29.288C225,29.84 224.552,30.288 224,30.288Z" /> + <path + android:fillColor="#657887" + android:fillType="evenOdd" + android:pathData="M247.705,48H191.294C188.922,48 187,45.621 187,42.684V22.316C187,19.379 188.922,17 191.294,17H247.705C250.076,17 252,19.379 252,22.316V42.684C252,45.621 250.076,48 247.705,48Z" /> + <path + android:fillColor="#657887" + android:fillType="evenOdd" + android:pathData="M196.689,44.594L188.167,51.835C187.678,52.251 187,51.835 187,51.12V43.879C187,43.394 187.33,43 187.738,43H196.26C196.978,43 197.273,44.097 196.689,44.594Z" /> + <path + android:fillColor="#F6B893" + android:fillType="evenOdd" + android:pathData="M158.927,46.469C158.927,46.469 166.919,53.947 169.944,55.966C172.45,57.64 180.079,61.146 180.079,61.146L177.161,65.083C177.161,65.083 169.899,63.465 167.664,62.424C165.372,61.357 159.307,56.726 159.307,56.726L158.927,46.469Z" /> + <group> + <clip-path + android:fillType="evenOdd" + android:pathData="M117.893,171.26H136.353V184.924H117.893V171.26Z" /> + <path + android:fillColor="#4A4A4A" + android:fillType="evenOdd" + android:pathData="M120.963,171.26L118.077,176.74C117.68,177.495 117.945,178.429 118.681,178.859C122.169,180.892 131.702,186.088 135.818,184.688C136.395,184.491 136.537,183.737 136.088,183.324C133.463,180.908 125.299,173.198 125.931,171.304L120.963,171.26Z" /> + </group> + <path + android:fillColor="#B96161" + android:fillType="evenOdd" + android:pathData="M120.698,171.77L126.278,172.513L153.875,108.792L139.503,79.926L139.336,119.436C139.336,119.436 126.518,144.915 124.472,154.911C122.453,164.779 120.698,171.77 120.698,171.77Z" /> + <path + android:fillColor="#C66A61" + android:fillType="evenOdd" + android:pathData="M138.6,70.71L160.256,70.998C160.256,70.998 179.024,165.129 186.532,171.481L182.49,174.369C182.49,174.369 159.39,142.896 161.411,122.683C161.411,122.683 132.825,76.773 138.6,70.71Z" /> + <path + android:fillColor="#F6B893" + android:fillType="evenOdd" + android:pathData="M153.883,34.496L152.492,24.656C152.492,24.656 158.542,24.925 158.402,16.485C158.376,14.921 158.267,12.244 156.823,10.215L151.289,8.445L147.787,19.63L144.457,33.204L150.354,36.956L154.418,36.849L153.883,34.496Z" /> + <path + android:fillColor="#4A4A4A" + android:fillType="evenOdd" + android:pathData="M182.49,174.369L183.381,180.499C183.503,181.342 184.267,181.942 185.115,181.856C189.132,181.448 199.898,180.04 202.402,176.486C202.753,175.988 202.424,175.295 201.818,175.225C198.274,174.816 187.136,173.385 186.532,171.482L182.49,174.369Z" /> + <path + android:fillColor="#F6B893" + android:fillType="evenOdd" + android:pathData="M133.89,83.725L135.397,65.307L143.183,67.53L137.718,86.474C137.718,86.474 136.344,97.468 131.73,94.228C126.004,90.208 133.694,85.983 133.89,83.725Z" /> + <path + android:fillColor="#B1DAEF" + android:fillType="evenOdd" + android:pathData="M152.935,34.452C150.849,36.798 148.529,32.461 143.966,33.634C139.653,34.743 135.807,62.288 135.397,65.306C135.374,65.482 135.362,65.574 135.362,65.574C136.538,68.869 141.947,67.812 142.993,67.574L142.225,71.016L160.257,70.998C167.291,51.522 155.021,32.105 152.935,34.452Z" /> + <path + android:fillColor="#663014" + android:fillType="evenOdd" + android:pathData="M158.412,16.555C158.412,16.555 162.76,13.748 159.872,6.261C159.872,6.261 159.23,3.053 154.738,3.16C154.738,3.16 147.573,-3.257 140.835,2.197C134.097,7.651 138.696,13.106 138.696,13.106C138.696,13.106 129.712,21.234 132.813,32.25C132.813,32.25 136.129,39.416 139.979,36.956C143.829,34.496 143.616,39.844 145.647,32.678C147.68,25.512 151.209,26.582 149.819,14.496C149.819,14.496 154.631,14.817 155.701,9.898C155.701,9.898 158.253,11.1 158.412,16.555Z" /> + <path + android:fillColor="#4A4A4A" + android:fillType="evenOdd" + android:pathData="M174.341,67.847C175.252,68.352 178.71,69.325 179.852,69.554C180.13,69.609 180.827,69.645 181.118,69.093C182.736,66.026 187.601,58.544 187.904,58.094C188.234,57.604 188.204,57.472 187.776,57.193C187.491,57.008 183.608,55.917 182.479,55.589C182.095,55.477 181.762,56.088 181.641,56.233C179.761,58.478 174.639,66.238 174.311,66.729C173.914,67.322 173.971,67.642 174.341,67.847Z" /> + <path + android:fillColor="#F6B893" + android:fillType="evenOdd" + android:pathData="M185.209,61.816C186.749,61.493 184.794,64.428 184.052,65.474C183.373,66.43 182.761,67.38 182.299,67.156C181.837,66.932 182.211,65.432 182.786,64.247C183.36,63.062 184.272,62.013 185.209,61.816Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M202,39.773C202.164,39.773 202.398,39.664 202.57,39.562C206.968,36.75 209.757,33.476 209.757,30.148C209.757,27.382 207.859,25.429 205.406,25.429C203.882,25.429 202.71,26.273 202,27.562C201.304,26.281 200.117,25.429 198.593,25.429C196.14,25.429 194.242,27.382 194.242,30.148C194.242,33.476 197.031,36.75 201.437,39.562C201.601,39.664 201.835,39.773 202,39.773Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M218.5,27L244.5,27A1.5,1.5 0,0 1,246 28.5L246,28.5A1.5,1.5 0,0 1,244.5 30L218.5,30A1.5,1.5 0,0 1,217 28.5L217,28.5A1.5,1.5 0,0 1,218.5 27z" /> + <path + android:fillColor="#ffffff" + android:pathData="M218.5,34L236.5,34A1.5,1.5 0,0 1,238 35.5L238,35.5A1.5,1.5 0,0 1,236.5 37L218.5,37A1.5,1.5 0,0 1,217 35.5L217,35.5A1.5,1.5 0,0 1,218.5 34z" /> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_nav_diary.xml b/Corona-Warn-App/src/main/res/drawable/ic_nav_diary.xml new file mode 100644 index 0000000000000000000000000000000000000000..93bb9a8bf7313906c68f40d91db1735588525fd8 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_nav_diary.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="22dp" + android:viewportWidth="18" + android:viewportHeight="22"> + <path + android:fillColor="#999999" + android:pathData="M17.0753,19.7501V2.5C17.0753,1 16,0 14.5,0H2.3493C1.088,0 -0.0081,1.2401 0,2.5C0,8.0716 0,13.9291 0,19.5C0,21 1.0002,22 2.5582,22H16.5C16.5,22 17,22 17,21.5C17,21 17,20.5 17,20.5C17,20.5 17,20.0056 16.5,20C16,19.9944 3.5002,20 3.5002,20C3.5002,20 2.0001,20 2.0001,18.5C2.0001,17.8138 2.0001,17.5 2.0001,17.5C2.0001,16 3.5002,16 3.5002,16C2.1767,16 13.2552,16 16,16C16,16 15.5,17 15.5,18C15.5,18.5728 16,19.7501 16,19.7501C16.5,19.7501 17.0753,19.7501 17.0753,19.7501Z" /> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_nav_home.xml b/Corona-Warn-App/src/main/res/drawable/ic_nav_home.xml new file mode 100644 index 0000000000000000000000000000000000000000..0edf55373932aa864f029554f7e3d13e5603156b --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_nav_home.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="22dp" + android:height="20dp" + android:viewportWidth="22" + android:viewportHeight="20"> + <path + android:fillColor="#017FAD" + android:fillType="evenOdd" + android:pathData="M11,0.5C10.73,0.5 10.48,0.61 10.29,0.8L0.2,9.6C0.07,9.69 0,9.84 0,10C0,10.28 0.22,10.5 0.5,10.5H3V18.5C3,19.05 3.45,19.5 4,19.5H8C8.55,19.5 9,19.05 9,18.5V12.5H13V18.5C13,19.05 13.45,19.5 14,19.5H18C18.55,19.5 19,19.05 19,18.5V10.5H21.5C21.78,10.5 22,10.28 22,10C22,9.84 21.92,9.69 21.8,9.6L11.72,0.8C11.72,0.8 11.72,0.8 11.71,0.79C11.52,0.61 11.27,0.5 11,0.5Z" /> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_new_release_info.xml b/Corona-Warn-App/src/main/res/drawable/ic_new_release_info.xml index 7548a9aa62f06f7375edbe4c632b1ff745779b95..a66e6cd56b6dbff38b2df30941cb6b9e3774a788 100644 --- a/Corona-Warn-App/src/main/res/drawable/ic_new_release_info.xml +++ b/Corona-Warn-App/src/main/res/drawable/ic_new_release_info.xml @@ -1,114 +1,41 @@ <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="364dp" - android:height="204dp" - android:viewportWidth="364" - android:viewportHeight="204"> - <group> - <clip-path - android:pathData="M0,0.944h364v202.222h-364z"/> - </group> - <group> - <clip-path - android:pathData="M172.9,92.956h43.478v59.656h-43.478z"/> - <path - android:pathData="M203.673,126.094C220.612,126.404 220.614,152.3 203.673,152.613C186.736,152.303 186.733,126.407 203.673,126.094ZM203.561,151.12C218.492,150.849 218.494,128.018 203.561,127.744C188.631,128.014 188.628,150.846 203.561,151.12Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M186.294,132.628C186.294,133.312 186.32,134.088 187.532,134.088L202.452,133.872C203.979,133.826 204.129,132.762 204.475,131.821C204.946,130.058 189.393,131.336 188.496,130.969C187.045,131.013 186.131,131.584 186.294,132.628Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M185.461,126.975C188.902,126.3 199.991,120.649 202.689,127.416C203.583,132.862 189.524,130.914 186.827,131.591L181.011,145.601L178.369,144.2L182.163,130.12C182.166,130.12 183.099,127.742 185.461,126.975Z" - android:fillColor="#C66A61"/> - <path - android:pathData="M202.051,107.565C201.416,107.893 200.515,107.783 200.225,107.237L199.125,103.986C198.495,102.722 201.11,101.903 201.622,102.988L203.169,105.933C203.436,106.408 203.085,106.737 202.449,106.992" - android:fillColor="#F6B893" - android:fillType="evenOdd"/> - <path - android:pathData="M195.019,98.352C194.07,101.208 196.06,105.973 197.764,105.351C201.3,104.062 202.302,104.216 203.252,101.359C205.738,94.599 197.197,91.714 195.019,98.352Z" - android:fillColor="#F6B893"/> - <path - android:pathData="M197.551,97.645C194.757,98.567 194.199,94.427 196.702,94.428C197.169,93.854 201.852,90.945 204.729,95.354C205.573,96.646 205.253,99.715 204.088,101.926C204.053,101.991 204.015,102.053 203.971,102.112C203.541,102.701 202.766,103.729 202.482,103.72C201.573,103.687 200.85,102.977 200.675,102.502C200.518,102.076 201.483,101.257 201.662,100.695C202.176,99.093 200.694,98.473 200.117,100.028C199.696,99.953 198.41,98.954 198.016,97.885C197.945,97.692 197.743,97.581 197.551,97.645Z" - android:fillColor="#663014"/> - <path - android:pathData="M202.036,105.351C196.363,105.241 194.057,116.31 192.394,124.118C193.484,126.628 200.193,130.512 202.606,128.827C203.148,127.079 205.903,117.191 206.306,113.797C206.734,110.178 204.205,105.303 202.036,105.351Z" - android:fillColor="#B1DAEF"/> - <path - android:pathData="M183.84,112.228C183.445,112.248 182.625,111.871 182.851,112.698L183.627,118.078C183.67,118.774 184.617,118.282 184.974,118.303C185.187,118.268 185.346,118.234 185.301,117.947L184.495,112.661C184.451,112.373 184.391,112.297 184.11,112.262" - android:fillColor="#4A4A4A"/> - <path - android:pathData="M186.253,118.312C185.762,118.385 184.047,119.333 183.6,118.816L182.529,117.882C181.791,117.182 182.561,114.987 183.436,115.423L184.555,115.916H186.259L186.253,118.312Z" - android:fillColor="#F6B893"/> - <path - android:pathData="M199.076,108.159C201.403,105.792 204.596,109.69 203.193,112.059L197.597,118.018C197.115,118.532 196.174,119.673 194.814,119.458L186.15,119.162L185.633,115.358L194.215,115.258L199.076,108.159Z" - android:fillColor="#B1DAEF"/> - <path - android:pathData="M187.075,124.596C187.091,125.828 188.859,125.355 189.891,125.51C191.607,125.146 205.842,127.334 205.956,124.726C205.732,122.247 193.034,123.848 190.234,123.675C189.99,123.661 189.745,123.651 189.501,123.659C188.468,123.698 187.223,123.515 187.075,124.596Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M202.89,133.79C203.409,133.898 203.958,133.211 204.48,131.781C206.339,126.679 208.065,114.114 208.76,112.149C208.83,111.953 208.882,111.751 208.901,111.542C209.001,110.432 208.156,111.049 208.065,111.101C206.188,112.187 203.102,127.879 202.163,130.422C202.119,130.541 202.084,130.663 202.06,130.789C201.708,132.539 201.809,133.924 202.89,133.79Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M188.4,133.827H186.916V147.871H188.4V133.827Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M202.298,139.824C202.27,141.838 205.344,141.838 205.316,139.824C205.344,137.81 202.27,137.81 202.298,139.824Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M184.178,148.928C184.256,153.591 190.997,153.589 191.075,148.928C190.997,144.265 184.254,144.265 184.178,148.928Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M185.134,148.936C185.149,152.326 190.161,152.326 190.175,148.936C190.161,145.546 185.149,145.546 185.134,148.936Z" - android:fillColor="#ffffff"/> - <path - android:pathData="M189.607,124.799L179.776,148.567L180.87,149.046L190.7,125.278L189.607,124.799Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M175.175,147.465L180.151,149.642C180.576,149.837 180.776,149.321 180.887,148.999C180.893,148.991 175.515,146.647 175.515,146.644C175.002,146.412 174.653,147.246 175.175,147.465Z" - android:fillColor="#6A7F90"/> - <path - android:pathData="M173.063,145.473C172.481,145.055 173.581,144.338 174.366,144.35C175.093,144.347 178.336,144.782 178.463,143.875L181.247,144.967C180.897,145.985 179.983,148.719 178.828,148.076C178.038,147.753 173.863,146.047 173.063,145.473Z" - android:fillColor="#4A4A4A"/> - <path - android:pathData="M186.903,148.932C186.889,149.934 188.419,149.934 188.404,148.932C188.42,147.929 186.889,147.929 186.903,148.932Z" - android:fillColor="#6A7F90"/> - </group> - <group> - <clip-path - android:pathData="M98.078,1.956h182v182h-182z"/> - <path - android:pathData="M189.078,183.956C239.336,183.956 280.078,143.213 280.078,92.956C280.078,42.698 239.336,1.956 189.078,1.956C138.82,1.956 98.078,42.698 98.078,92.956C98.078,143.213 138.82,183.956 189.078,183.956Z" - android:fillColor="#E9F6FF"/> - <path - android:pathData="M139.877,119.224C140.292,111.125 140.908,103.047 141.06,94.927C141.384,78.021 140.939,61.106 140.838,44.2C132.567,41.925 126.783,40.378 126.783,40.378C126.783,40.378 111.606,35.727 110.393,42.805C109.18,49.882 126.177,55.959 126.177,55.959L128.391,56.677L128.32,65.858C128.32,65.858 111.404,81.247 111.606,86.919C111.809,92.581 120.717,91.368 120.717,91.368C120.717,91.368 113.568,105.686 116.874,110.195C119.301,113.501 122.334,112.622 122.334,112.622C122.334,112.622 114.256,118.122 116.672,124.361C117.734,127.111 125.984,129.012 125.984,129.012L139.533,130.498C139.048,126.717 139.361,122.965 139.877,119.224Z" - android:fillColor="#F6B893"/> - <path - android:pathData="M190.058,13.432H139.007C132.809,13.432 127.774,18.467 127.774,24.665V152.035C127.774,158.233 132.809,163.268 139.007,163.268H190.058C196.257,163.268 201.292,158.233 201.292,152.035V24.665C201.292,18.467 196.257,13.432 190.058,13.432ZM197.662,152.035C197.662,156.221 194.255,159.628 190.069,159.628H139.007C134.821,159.628 131.414,156.221 131.414,152.035V24.665C131.414,20.479 134.821,17.072 139.007,17.072H190.058C194.244,17.072 197.652,20.479 197.652,24.665V152.035H197.662Z" - android:fillColor="#4A4A4A"/> - <path - android:pathData="M190.059,17.062H139.008C134.822,17.062 131.414,20.469 131.414,24.655V152.025C131.414,156.211 134.822,159.618 139.008,159.618H190.059C194.245,159.618 197.652,156.211 197.652,152.025V24.665C197.662,20.469 194.255,17.062 190.059,17.062Z" - android:fillColor="#ffffff"/> - <path - android:pathData="M263.809,128.274C263.809,128.274 251.939,125.351 238.147,117.677C236.539,116.777 237.328,113.724 236.721,111.358C236.539,110.175 236.317,108.941 236.064,107.667C235.174,103.168 234.355,100.721 231.696,92.116C228.976,83.33 228.774,79.235 222.596,68.547C222.596,68.547 218.471,51.439 212.333,45.15C211.363,44.149 210.705,42.916 210.463,41.561C209.674,37.061 207.895,27.618 206.934,27.537C205.67,27.425 199.341,26.9 196.904,34.594C194.588,41.904 194.952,55.342 201.272,60.64C201.282,67.658 201.383,149.891 201.353,150.144C200.706,157.262 200.058,156.807 198.754,159.092C213.071,164.906 231.332,173.571 231.332,173.571C231.332,173.571 242.849,168.86 254.8,155.887C267.419,142.186 271.766,130.963 271.766,130.963L263.809,128.274Z" - android:fillColor="#F6B893" - android:fillType="evenOdd"/> - <path - android:pathData="M140.828,29.65H187.541C190.261,29.65 192.455,31.601 192.455,33.998V42.693C192.455,45.1 190.251,47.041 187.541,47.041H140.828C138.108,47.041 135.914,45.09 135.914,42.693V33.998C135.914,31.591 138.118,29.65 140.828,29.65Z" - android:fillColor="#E7E7E7" - android:fillType="evenOdd"/> - <path - android:pathData="M140.828,54.655H187.541C190.261,54.655 192.455,56.606 192.455,59.002V94.887C192.455,97.293 190.251,99.234 187.541,99.234H140.828C138.108,99.234 135.914,97.283 135.914,94.887V59.002C135.914,56.606 138.118,54.655 140.828,54.655Z" - android:fillColor="#E7E7E7" - android:fillType="evenOdd"/> - <path - android:pathData="M140.828,107.94H187.541C190.261,107.94 192.455,109.892 192.455,112.288V145.999C192.455,148.405 190.251,150.346 187.541,150.346H140.828C138.108,150.346 135.914,148.395 135.914,145.999V112.288C135.914,109.882 138.118,107.94 140.828,107.94Z" - android:fillColor="#E7E7E7" - android:fillType="evenOdd"/> - <path - android:pathData="M164.053,110.033C175.78,110.033 185.286,100.753 185.286,89.305C185.286,77.858 175.78,68.578 164.053,68.578C152.326,68.578 142.819,77.858 142.819,89.305C142.819,100.753 152.326,110.033 164.053,110.033Z" - android:fillColor="#657888"/> - <path - android:pathData="M154.351,89.802C155.238,88.919 156.675,88.919 157.558,89.802L160.581,92.814C160.827,93.061 161.231,93.061 161.477,92.814L171.77,82.546C172.721,81.596 174.265,81.596 175.22,82.546C176.171,83.496 176.171,85.036 175.22,85.987L162.785,98.395C161.95,99.231 160.594,99.231 159.756,98.395L154.351,93.003C153.468,92.12 153.468,90.688 154.351,89.802Z" - android:fillColor="#ffffff" - android:fillType="evenOdd"/> - </group> + android:width="180dp" + android:height="180dp" + android:viewportWidth="180" + android:viewportHeight="180"> + <path + android:fillColor="#E9F6FF" + android:pathData="M90,180C139.706,180 180,139.706 180,90C180,40.294 139.706,0 90,0C40.294,0 0,40.294 0,90C0,139.706 40.294,180 90,180Z" /> + <path + android:fillColor="#F6B893" + android:pathData="M41.34,115.979C41.75,107.969 42.36,99.979 42.51,91.949C42.83,75.229 42.39,58.499 42.29,41.779C34.11,39.529 28.39,37.999 28.39,37.999C28.39,37.999 13.38,33.399 12.18,40.399C10.98,47.399 27.79,53.409 27.79,53.409L29.98,54.119L29.91,63.199C29.91,63.199 13.18,78.419 13.38,84.029C13.58,89.629 22.39,88.429 22.39,88.429C22.39,88.429 15.32,102.589 18.59,107.049C20.99,110.319 23.99,109.449 23.99,109.449C23.99,109.449 16,114.889 18.39,121.059C19.44,123.779 27.6,125.659 27.6,125.659L41,127.129C40.52,123.389 40.83,119.679 41.34,115.979Z" /> + <path + android:fillColor="#4A4A4A" + android:pathData="M90.97,11.35H40.48C34.35,11.35 29.37,16.33 29.37,22.46V148.43C29.37,154.56 34.35,159.54 40.48,159.54H90.97C97.1,159.54 102.08,154.56 102.08,148.43V22.46C102.08,16.33 97.1,11.35 90.97,11.35ZM98.49,148.43C98.49,152.57 95.12,155.94 90.98,155.94H40.48C36.34,155.94 32.97,152.57 32.97,148.43V22.46C32.97,18.32 36.34,14.95 40.48,14.95H90.97C95.11,14.95 98.48,18.32 98.48,22.46V148.43H98.49Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M90.97,14.939H40.48C36.34,14.939 32.97,18.309 32.97,22.449V148.419C32.97,152.559 36.34,155.929 40.48,155.929H90.97C95.11,155.929 98.48,152.559 98.48,148.419V22.459C98.49,18.309 95.12,14.939 90.97,14.939Z" /> + <path + android:fillColor="#F6B893" + android:fillType="evenOdd" + android:pathData="M163.91,124.93C163.91,124.93 152.17,122.04 138.53,114.45C136.94,113.56 137.72,110.54 137.12,108.2C136.94,107.03 136.72,105.81 136.47,104.55C135.59,100.1 134.78,97.68 132.15,89.17C129.46,80.48 129.26,76.43 123.15,65.86C123.15,65.86 119.07,48.94 113,42.72C112.04,41.73 111.39,40.51 111.15,39.17C110.37,34.72 108.61,25.38 107.66,25.3C106.41,25.19 100.15,24.67 97.74,32.28C95.45,39.51 95.81,52.8 102.06,58.04C102.07,64.98 102.17,146.31 102.14,146.56C101.5,153.6 100.86,153.15 99.57,155.41C113.73,161.16 131.79,169.73 131.79,169.73C131.79,169.73 143.18,165.07 155,152.24C167.48,138.69 171.78,127.59 171.78,127.59L163.91,124.93Z" /> + <path + android:fillColor="#E7E7E7" + android:fillType="evenOdd" + android:pathData="M42.28,27.391H88.48C91.17,27.391 93.34,29.321 93.34,31.691V40.291C93.34,42.671 91.16,44.591 88.48,44.591H42.28C39.59,44.591 37.42,42.661 37.42,40.291V31.691C37.42,29.311 39.6,27.391 42.28,27.391Z" /> + <path + android:fillColor="#E7E7E7" + android:fillType="evenOdd" + android:pathData="M42.28,52.121H88.48C91.17,52.121 93.34,54.051 93.34,56.421V91.911C93.34,94.291 91.16,96.211 88.48,96.211H42.28C39.59,96.211 37.42,94.281 37.42,91.911V56.421C37.42,54.051 39.6,52.121 42.28,52.121Z" /> + <path + android:fillColor="#E7E7E7" + android:fillType="evenOdd" + android:pathData="M42.28,104.82H88.48C91.17,104.82 93.34,106.75 93.34,109.12V142.46C93.34,144.84 91.16,146.76 88.48,146.76H42.28C39.59,146.76 37.42,144.83 37.42,142.46V109.12C37.42,106.74 39.6,104.82 42.28,104.82Z" /> + <path + android:fillColor="#657888" + android:pathData="M65.25,106.891C76.848,106.891 86.25,97.713 86.25,86.391C86.25,75.069 76.848,65.891 65.25,65.891C53.652,65.891 44.25,75.069 44.25,86.391C44.25,97.713 53.652,106.891 65.25,106.891Z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M55.655,86.881C56.532,86.008 57.954,86.008 58.827,86.881L61.816,89.861C62.06,90.104 62.459,90.104 62.703,89.861L72.882,79.705C73.823,78.765 75.35,78.765 76.294,79.705C77.235,80.645 77.235,82.168 76.294,83.108L63.996,95.38C63.17,96.207 61.83,96.207 61,95.38L55.655,90.047C54.782,89.174 54.782,87.757 55.655,86.881Z" /> </vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_tracing_survey_icon.xml b/Corona-Warn-App/src/main/res/drawable/ic_tracing_survey_icon.xml new file mode 100644 index 0000000000000000000000000000000000000000..cbaa9dcc6ffcf8b459552958a2fe6a87b82e62ed --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_tracing_survey_icon.xml @@ -0,0 +1,75 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="125dp" + android:viewportWidth="108" + android:viewportHeight="125"> + <group> + <clip-path android:pathData="M-6,-5h114v130h-114z" /> + <path + android:fillColor="#ffffff" + android:fillType="evenOdd" + android:pathData="M79.807,114.696L39.983,122.087C35.927,122.841 32.02,120.156 31.266,116.101L12.828,16.711C12.073,12.656 14.747,8.76 18.802,8.006H18.814L58.638,0.615C62.682,-0.117 66.566,2.557 67.321,6.601L85.793,105.991C86.547,110.046 83.874,113.942 79.818,114.696C79.818,114.696 79.818,114.696 79.807,114.696Z" /> + <group> + <clip-path android:pathData="M26.674,38.891l37.031,-7.066l10.133,53.1l-37.031,7.066z" /> + <path + android:fillAlpha="0.7" + android:fillColor="#657888" + android:pathData="M47.256,87.315L38.36,89.012C37.62,89.154 36.904,88.673 36.763,87.936L35.077,79.099C34.937,78.364 35.422,77.651 36.165,77.51L45.061,75.812C45.801,75.671 46.516,76.151 46.657,76.889L48.343,85.726C48.481,86.464 47.996,87.174 47.256,87.315Z" + android:strokeAlpha="0.7" /> + <path + android:fillAlpha="0.7" + android:fillColor="#657888" + android:pathData="M69.677,82.601L61.127,84.233C60.407,84.37 59.714,83.903 59.577,83.187L57.957,74.695C57.82,73.979 58.292,73.289 59.013,73.152L67.562,71.52C68.283,71.383 68.976,71.851 69.112,72.566L70.733,81.059C70.869,81.774 70.397,82.464 69.677,82.601Z" + android:strokeAlpha="0.7" /> + <path + android:fillColor="#ffffff" + android:pathData="M63.99,81.632C62.918,80.925 61.853,80.205 60.79,79.481C60.579,79.339 60.545,79.143 60.682,78.93C60.851,78.665 60.669,78.927 60.853,78.675C61.024,78.437 61.221,78.402 61.468,78.562C62.086,78.968 62.702,79.382 63.318,79.793C63.321,79.795 63.321,79.795 63.324,79.797C63.485,79.893 63.683,80.007 63.806,80.08C63.869,80.116 63.946,80.094 63.983,80.031C64.043,79.921 64.138,79.748 64.213,79.597C65.07,77.64 65.929,75.679 66.789,73.723C66.952,73.35 67.137,73.279 67.52,73.442C67.756,73.541 68.046,73.729 67.923,74.018C67.597,74.781 67.269,75.542 66.938,76.304C66.196,77.999 65.452,79.695 64.708,81.388C64.595,81.693 64.314,81.823 63.99,81.632Z" /> + <group> + <clip-path android:pathData="M63.99,81.632C62.918,80.925 61.853,80.205 60.79,79.481C60.579,79.339 60.545,79.143 60.682,78.93C60.851,78.665 60.669,78.927 60.853,78.675C61.024,78.437 61.221,78.402 61.468,78.562C62.086,78.968 62.702,79.382 63.318,79.793C63.321,79.795 63.321,79.795 63.324,79.797C63.485,79.893 63.683,80.007 63.806,80.08C63.869,80.116 63.946,80.094 63.983,80.031C64.043,79.921 64.138,79.748 64.213,79.597C65.07,77.64 65.929,75.679 66.789,73.723C66.952,73.35 67.137,73.279 67.52,73.442C67.756,73.541 68.046,73.729 67.923,74.018C67.597,74.781 67.269,75.542 66.938,76.304C66.196,77.999 65.452,79.695 64.708,81.388C64.595,81.693 64.314,81.823 63.99,81.632Z" /> + <path + android:fillColor="#ffffff" + android:pathData="M63.99,81.632L61.239,85.807L61.344,85.876L61.451,85.939L63.99,81.632ZM60.79,79.481L63.603,75.348L63.595,75.342L63.586,75.336L60.79,79.481ZM60.682,78.93L64.885,81.638L64.893,81.626L64.901,81.613L60.682,78.93ZM60.853,78.675L64.897,81.614L64.904,81.606L64.91,81.597L60.853,78.675ZM61.468,78.562L64.215,74.384L64.202,74.376L64.19,74.368L61.468,78.562ZM63.318,79.793L66.115,75.648L66.105,75.642L66.095,75.635L63.318,79.793ZM63.324,79.797L60.528,83.942L60.645,84.021L60.766,84.094L63.324,79.797ZM63.806,80.08L61.27,84.389L61.295,84.404L61.319,84.418L63.806,80.08ZM63.983,80.031L68.304,82.546L68.336,82.492L68.366,82.436L63.983,80.031ZM64.213,79.597L68.685,81.835L68.742,81.72L68.794,81.602L64.213,79.597ZM66.789,73.723L71.366,75.735L71.369,75.729L66.789,73.723ZM67.52,73.442L65.567,78.045L65.577,78.049L67.52,73.442ZM67.923,74.018L72.522,75.98L72.523,75.977L67.923,74.018ZM66.938,76.304L71.519,78.307L71.523,78.297L66.938,76.304ZM64.708,81.388L60.131,79.376L60.07,79.515L60.017,79.657L64.708,81.388ZM66.741,77.456C65.701,76.771 64.661,76.068 63.603,75.348L57.977,83.615C59.045,84.342 60.135,85.079 61.239,85.807L66.741,77.456ZM63.586,75.336C64.252,75.785 65.219,76.732 65.517,78.295C65.81,79.828 65.28,81.025 64.885,81.638L56.479,76.221C55.947,77.048 55.366,78.45 55.694,80.17C56.028,81.919 57.118,83.035 57.994,83.626L63.586,75.336ZM64.901,81.613C64.916,81.59 64.93,81.567 64.944,81.545C64.958,81.523 64.972,81.5 64.986,81.478C65,81.455 65.016,81.43 65.032,81.403C65.04,81.389 65.049,81.374 65.059,81.357C65.068,81.341 65.079,81.323 65.091,81.302C65.103,81.282 65.116,81.257 65.132,81.23C65.14,81.216 65.148,81.2 65.157,81.183C65.166,81.167 65.176,81.148 65.187,81.127C65.198,81.106 65.21,81.083 65.223,81.056C65.237,81.03 65.252,81 65.268,80.965C65.277,80.948 65.286,80.929 65.295,80.909C65.304,80.888 65.315,80.866 65.325,80.842C65.336,80.818 65.348,80.792 65.36,80.763C65.372,80.735 65.385,80.703 65.399,80.668C65.413,80.633 65.429,80.594 65.445,80.551C65.461,80.508 65.479,80.459 65.497,80.404C65.516,80.349 65.536,80.285 65.558,80.214C65.568,80.178 65.579,80.139 65.59,80.097C65.602,80.055 65.613,80.01 65.625,79.962C65.637,79.913 65.649,79.86 65.66,79.802C65.672,79.745 65.684,79.682 65.695,79.614C65.706,79.545 65.718,79.47 65.727,79.388C65.737,79.305 65.746,79.214 65.752,79.114C65.759,79.014 65.763,78.902 65.762,78.78C65.762,78.732 65.76,78.635 65.758,78.585C65.755,78.532 65.748,78.424 65.744,78.369C65.738,78.31 65.725,78.19 65.717,78.13C65.708,78.064 65.687,77.932 65.674,77.864C65.66,77.792 65.628,77.646 65.61,77.572C65.589,77.492 65.543,77.332 65.517,77.251C65.445,77.029 65.349,76.786 65.22,76.533C65.091,76.279 64.929,76.014 64.727,75.751C64.649,75.652 64.483,75.458 64.397,75.364C64.304,75.269 64.113,75.086 64.013,74.999C63.909,74.912 63.695,74.748 63.586,74.671C63.473,74.596 63.244,74.457 63.129,74.393C63.011,74.332 62.776,74.221 62.659,74.171C62.541,74.125 62.308,74.042 62.193,74.007C61.886,73.915 61.594,73.86 61.325,73.829C61.225,73.819 61.033,73.805 60.939,73.801C60.849,73.798 60.674,73.798 60.59,73.8C60.509,73.804 60.352,73.814 60.277,73.821C60.204,73.829 60.065,73.846 59.998,73.856C59.933,73.867 59.809,73.889 59.75,73.901C59.693,73.913 59.583,73.939 59.53,73.952C59.479,73.965 59.382,73.992 59.335,74.005C59.29,74.019 59.204,74.047 59.162,74.06C59.054,74.097 58.957,74.134 58.871,74.169C58.786,74.204 58.708,74.238 58.639,74.27C58.571,74.303 58.508,74.334 58.453,74.363C58.397,74.392 58.347,74.419 58.302,74.445C58.256,74.47 58.215,74.494 58.178,74.517C58.141,74.539 58.108,74.56 58.078,74.579C58.048,74.598 58.021,74.616 57.996,74.632C57.972,74.648 57.95,74.663 57.931,74.676C57.912,74.689 57.895,74.701 57.881,74.711C57.867,74.721 57.855,74.73 57.845,74.736C57.836,74.743 57.828,74.748 57.824,74.751C57.823,74.753 57.821,74.754 57.82,74.755C57.819,74.755 57.82,74.755 57.82,74.754C57.821,74.754 57.824,74.752 57.826,74.75C57.828,74.748 57.834,74.744 57.838,74.742C57.842,74.739 57.852,74.732 57.858,74.727C57.864,74.723 57.878,74.713 57.887,74.707C57.896,74.701 57.917,74.686 57.928,74.678C57.941,74.669 57.97,74.65 57.986,74.639C58.004,74.627 58.045,74.6 58.068,74.585C58.094,74.569 58.152,74.533 58.185,74.513C58.223,74.49 58.309,74.44 58.358,74.413C58.416,74.382 58.551,74.313 58.628,74.276C58.723,74.232 58.951,74.137 59.084,74.088C59.258,74.029 59.688,73.914 59.944,73.865C60.303,73.819 61.198,73.817 61.727,73.892C62.428,74.084 63.847,74.863 64.5,75.478C65.005,76.154 65.6,77.539 65.725,78.194C65.759,78.655 65.72,79.442 65.667,79.764C65.618,79.987 65.508,80.37 65.452,80.531C65.406,80.65 65.317,80.859 65.275,80.949C65.241,81.02 65.175,81.147 65.145,81.204C65.119,81.25 65.071,81.334 65.048,81.372C65.029,81.404 64.992,81.462 64.975,81.489C64.961,81.512 64.933,81.555 64.92,81.575C64.908,81.592 64.886,81.624 64.876,81.639C64.867,81.652 64.849,81.677 64.841,81.689C64.834,81.699 64.82,81.719 64.813,81.728C64.807,81.736 64.796,81.752 64.79,81.759C64.777,81.777 64.765,81.793 64.756,81.805C64.747,81.817 64.739,81.828 64.732,81.836C64.726,81.845 64.72,81.852 64.716,81.858C64.707,81.869 64.7,81.878 64.696,81.883C64.692,81.888 64.689,81.893 64.687,81.895C64.685,81.897 64.684,81.899 64.684,81.899C64.683,81.899 64.683,81.899 64.683,81.899C64.683,81.899 64.683,81.899 64.683,81.899C64.683,81.899 64.684,81.899 64.684,81.899C64.684,81.898 64.685,81.897 64.686,81.896C64.69,81.891 64.696,81.884 64.702,81.875C64.709,81.867 64.715,81.858 64.722,81.85C64.735,81.834 64.747,81.818 64.758,81.803C64.769,81.788 64.78,81.774 64.79,81.76C64.801,81.746 64.812,81.731 64.823,81.716C64.844,81.687 64.869,81.653 64.897,81.614L56.808,75.735C56.77,75.787 56.769,75.788 56.787,75.765C56.799,75.748 56.818,75.724 56.839,75.697C56.851,75.682 56.835,75.702 56.827,75.712C56.824,75.717 56.798,75.749 56.767,75.79C56.751,75.811 56.72,75.853 56.682,75.907C56.662,75.934 56.635,75.974 56.604,76.021C56.573,76.066 56.528,76.136 56.475,76.224C56.428,76.303 56.341,76.451 56.248,76.646C56.206,76.737 56.117,76.946 56.071,77.065C56.015,77.225 55.905,77.608 55.856,77.832C55.803,78.154 55.764,78.94 55.798,79.401C55.923,80.057 56.518,81.442 57.023,82.118C57.676,82.732 59.095,83.512 59.796,83.704C60.325,83.779 61.22,83.777 61.578,83.731C61.835,83.681 62.265,83.566 62.439,83.508C62.572,83.459 62.799,83.364 62.895,83.32C63.103,83.222 63.26,83.13 63.338,83.083C63.426,83.03 63.496,82.984 63.537,82.957C63.58,82.928 63.615,82.904 63.636,82.889C63.658,82.873 63.676,82.861 63.685,82.854C63.694,82.847 63.702,82.842 63.703,82.841C63.71,82.836 63.694,82.848 63.678,82.859C63.645,82.883 63.559,82.944 63.446,83.016C63.339,83.084 63.143,83.203 62.884,83.325C62.75,83.387 62.574,83.463 62.362,83.535C62.152,83.605 61.869,83.686 61.526,83.739C61.186,83.791 60.73,83.826 60.199,83.766C59.657,83.704 59.029,83.543 58.396,83.202C57.752,82.856 57.213,82.385 56.797,81.844C56.392,81.316 56.151,80.786 56.007,80.344C55.865,79.907 55.806,79.519 55.781,79.226C55.755,78.93 55.76,78.676 55.772,78.481C55.796,78.106 55.858,77.808 55.899,77.635C55.944,77.448 55.992,77.297 56.027,77.193C56.063,77.086 56.097,76.997 56.124,76.93C56.177,76.8 56.224,76.698 56.254,76.637C56.285,76.572 56.312,76.518 56.332,76.481C56.37,76.409 56.401,76.353 56.417,76.325C56.45,76.268 56.473,76.23 56.475,76.229C56.479,76.222 56.476,76.226 56.463,76.246L64.901,81.613ZM64.91,81.597C64.568,82.071 63.66,83.092 62.025,83.389C60.405,83.683 59.211,83.058 58.747,82.757L64.19,74.368C63.478,73.906 62.062,73.219 60.239,73.549C58.401,73.883 57.308,75.04 56.796,75.752L64.91,81.597ZM58.721,82.74C59.319,83.133 59.912,83.531 60.542,83.952L66.095,75.635C65.492,75.233 64.852,74.803 64.215,74.384L58.721,82.74ZM60.522,83.938C60.522,83.938 60.522,83.938 60.522,83.938C60.521,83.938 60.521,83.938 60.522,83.938C60.523,83.939 60.525,83.94 60.528,83.942L66.12,75.652C66.12,75.652 66.12,75.652 66.121,75.653C66.121,75.653 66.121,75.653 66.121,75.653C66.119,75.652 66.118,75.65 66.115,75.648L60.522,83.938ZM60.766,84.094C60.929,84.19 61.214,84.356 61.27,84.389L66.342,75.771C66.151,75.658 66.041,75.596 65.882,75.501L60.766,84.094ZM61.319,84.418C63.863,85.876 66.941,84.887 68.304,82.546L59.661,77.516C60.951,75.3 63.875,74.356 66.293,75.742L61.319,84.418ZM68.366,82.436C68.42,82.339 68.56,82.085 68.685,81.835L59.742,77.359C59.734,77.376 59.715,77.411 59.686,77.466C59.658,77.519 59.629,77.571 59.599,77.626L68.366,82.436ZM68.794,81.602C69.65,79.645 70.508,77.687 71.366,75.735L62.212,71.711C61.35,73.67 60.49,75.634 59.633,77.592L68.794,81.602ZM71.369,75.729C71.319,75.842 70.699,77.345 68.865,78.049C67.074,78.737 65.637,78.074 65.567,78.044L69.473,68.839C69.02,68.647 67.299,67.938 65.279,68.714C63.216,69.507 62.422,71.231 62.209,71.717L71.369,75.729ZM65.577,78.049C65.48,78.008 64.805,77.737 64.155,77.02C63.783,76.611 63.294,75.917 63.069,74.929C62.831,73.878 62.977,72.872 63.323,72.059L72.523,75.977C72.931,75.019 73.087,73.881 72.821,72.711C72.568,71.603 72.016,70.802 71.557,70.297C70.733,69.39 69.795,68.975 69.463,68.835L65.577,78.049ZM63.324,72.055C63.004,72.806 62.68,73.556 62.352,74.311L71.523,78.297C71.857,77.528 72.191,76.756 72.522,75.98L63.324,72.055ZM62.356,74.3C61.617,75.992 60.874,77.685 60.131,79.376L69.285,83.4C70.031,81.705 70.776,80.006 71.519,78.307L62.356,74.3ZM60.017,79.657C60.341,78.78 61.138,77.587 62.68,77.012C64.276,76.416 65.704,76.838 66.529,77.324L61.451,85.939C62.6,86.616 64.331,87.069 66.177,86.38C67.969,85.712 68.962,84.302 69.399,83.119L60.017,79.657Z" /> + </group> + <path + android:fillAlpha="0.7" + android:fillColor="#657888" + android:pathData="M30.411,51.324L65.021,44.72C65.717,44.679 66.294,45.387 66.5,46.47C66.682,47.423 66.458,48.563 65.769,48.642L31.159,55.246C30.28,55.47 29.676,54.622 29.47,53.54C29.262,52.453 29.538,51.582 30.411,51.324Z" + android:strokeAlpha="0.7" /> + <path + android:fillAlpha="0.7" + android:fillColor="#657888" + android:pathData="M27.991,38.64L62.601,32.036C63.296,31.995 63.873,32.704 64.08,33.787C64.262,34.741 64.038,35.882 63.35,35.961L28.74,42.565C27.86,42.789 27.256,41.941 27.049,40.854C26.842,39.77 27.118,38.899 27.991,38.64Z" + android:strokeAlpha="0.7" /> + <path + android:fillAlpha="0.7" + android:fillColor="#657888" + android:pathData="M32.791,63.798L67.401,57.194C68.097,57.153 68.674,57.863 68.881,58.948C69.064,59.903 68.84,61.045 68.151,61.123L33.541,67.728C32.661,67.948 32.058,67.102 31.85,66.014C31.643,64.929 31.919,64.057 32.791,63.798Z" + android:strokeAlpha="0.7" /> + </group> + <path + android:fillColor="#F7B994" + android:fillType="evenOdd" + android:pathData="M16.232,25.588L13.205,24.926C13.205,24.926 0.866,21.738 0.409,28.136C-0.047,34.533 13.833,38.92 13.833,38.92L14.964,39.012L19.111,40.988L19.808,44.758L15.558,48.448C15.558,48.448 3.951,62.442 4.522,67.48C5.093,72.518 11.331,72.153 11.331,72.153C11.331,72.153 4.739,76.802 7.664,80.618C9.823,83.417 14.45,84.891 14.45,84.891C14.45,84.891 8.418,89.906 10.783,95.321C11.834,97.709 18.482,98.897 18.482,98.897L29.587,98.257C29.015,94.784 16.7,28.239 16.232,25.588Z" /> + <path + android:fillColor="#00000000" + android:pathData="M79.807,114.696L39.983,122.087C35.927,122.841 32.02,120.156 31.266,116.101L12.828,16.711C12.073,12.656 14.747,8.76 18.802,8.006H18.814L58.638,0.615C62.682,-0.117 66.566,2.557 67.321,6.601L85.793,105.991C86.547,110.046 83.874,113.942 79.818,114.696C79.818,114.696 79.818,114.696 79.807,114.696Z" + android:strokeWidth="3.1542" + android:strokeColor="#4A4A4A" /> + <path + android:fillColor="#F7B994" + android:fillType="evenOdd" + android:pathData="M15.535,71.684L28.729,70.039C28.729,70.039 35.641,67.24 34.853,62.899C33.928,57.621 27.998,57.45 27.998,57.45L17.397,57.781L15.535,71.684Z" /> + <path + android:fillColor="#F7B994" + android:fillType="evenOdd" + android:pathData="M15.775,84.548C15.775,84.548 26.502,84.765 29.975,83.908C32.306,83.325 41.136,80.698 37.869,74.575C35.344,69.811 31.003,69.091 29.038,69.799C25.451,71.067 18.311,70.782 18.311,70.782L15.775,84.548Z" /> + <path + android:fillColor="#F7B994" + android:fillType="evenOdd" + android:pathData="M18.414,98.737C18.414,98.737 29.472,98.325 33.562,98.017C35.71,97.845 42.221,94.772 41.136,91.105C39.937,86.65 35.63,83.748 31.049,84.319C27.85,84.902 22.56,83.759 22.56,83.759L18.414,98.737Z" /> + <path + android:fillColor="#F7B994" + android:fillType="evenOdd" + android:pathData="M108.984,68.177C103.261,57.621 93.448,35.595 90.226,33.95C90.226,33.95 86.273,19.373 77.465,18.573C77.465,18.573 74.049,6.418 73.101,6.452C72.153,6.486 67.435,6.624 66.327,12.69C65.219,18.756 67.675,29.7 73.558,32.579L86.982,102.346C88.067,107.761 87.701,108.115 87.347,110.24C94.693,111.794 103.204,114.41 110.927,116.878C120.626,119.962 135.1,123.618 138.996,125C139.007,118.123 138.996,85.416 138.973,79.921C131.558,78.001 122.145,77.419 115.759,76.151C114.102,75.831 110.47,70.667 108.984,68.177Z" /> + </group> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/activity_main.xml b/Corona-Warn-App/src/main/res/layout/activity_main.xml index 117a403341c4e759a2744fe6393a5b1ddca96f80..80cb4564b31c567999af52854d7ad7524dbaeb00 100644 --- a/Corona-Warn-App/src/main/res/layout/activity_main.xml +++ b/Corona-Warn-App/src/main/res/layout/activity_main.xml @@ -6,12 +6,33 @@ android:layout_height="match_parent" tools:context="de.rki.coronawarnapp.ui.main.MainActivity"> - <androidx.fragment.app.FragmentContainerView - android:id="@+id/nav_host_fragment" - android:name="androidx.navigation.fragment.NavHostFragment" + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" - android:layout_height="match_parent" - app:defaultNavHost="true" - app:navGraph="@navigation/nav_graph" /> + android:layout_height="match_parent"> + + <androidx.fragment.app.FragmentContainerView + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="0dp" + android:layout_height="0dp" + app:defaultNavHost="true" + app:layout_constraintBottom_toTopOf="@id/main_bottom_navigation" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navGraph="@navigation/nav_graph" /> + + <com.google.android.material.bottomnavigation.BottomNavigationView + android:id="@+id/main_bottom_navigation" + style="@style/Widget.MaterialComponents.BottomNavigationView" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:itemIconTint="@color/nav_item_color" + app:itemTextColor="@color/nav_item_color" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:menu="@menu/menu_bottom_nav" /> + </androidx.constraintlayout.widget.ConstraintLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_activity.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_activity.xml deleted file mode 100644 index 7d2465983bf4d603475485bbb0b49ca2da3bb1e9..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_activity.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?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:context="de.rki.coronawarnapp.contactdiary.ui.ContactDiaryActivity"> - - <androidx.fragment.app.FragmentContainerView - android:id="@+id/contact_diary_fragment_container" - android:name="androidx.navigation.fragment.NavHostFragment" - android:layout_width="match_parent" - android:layout_height="match_parent" - app:defaultNavHost="true" /> - -</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml index 17ede5ce20afbd092cd2cfa987e677027c906bdd..fffbd9bce0dce0a288281dd748e85cd6bed0bc05 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_onboarding_fragment.xml @@ -15,7 +15,6 @@ android:id="@+id/toolbar" android:layout_width="0dp" android:layout_height="wrap_content" - style="@style/CWAToolbar.Close" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml index d3182899cbebc34850e13b284c0c0ef9b8acb49d..e4daa3c93f1de28c48b6383433b6bad2783457bf 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_overview_fragment.xml @@ -13,26 +13,12 @@ android:id="@+id/toolbar" android:layout_width="0dp" android:layout_height="wrap_content" - style="@style/CWAToolbar.BackArrow" android:background="@color/colorBackground" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:popupTheme="@style/CWAToolbar.Menu" - app:title="@string/contact_diary_overview_header" /> - - <TextView - android:id="@+id/onboarding_headline" - style="@style/headline5Bold" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:focusable="true" - android:text="@string/contact_diary_overview_title" - app:layout_constraintEnd_toStartOf="@+id/guideline_end" - app:layout_constraintStart_toStartOf="@+id/guideline_start" - app:layout_constraintTop_toBottomOf="@+id/toolbar" - tools:text="Kontakt-Tagebuch" /> + app:title="@string/contact_diary_overview_title" /> <TextView android:id="@+id/contact_diary_overview_subtitle" @@ -44,7 +30,7 @@ android:text="@string/contact_diary_overview_subtitle" app:layout_constraintEnd_toEndOf="@id/guideline_end" app:layout_constraintStart_toStartOf="@id/guideline_start" - app:layout_constraintTop_toBottomOf="@+id/onboarding_headline" + app:layout_constraintTop_toBottomOf="@id/toolbar" tools:text="Tragen Sie ein, mit wem Sie sich getroffen haben und wo Sie gewesen sind." /> <androidx.recyclerview.widget.RecyclerView diff --git a/Corona-Warn-App/src/main/res/layout/fragment_information.xml b/Corona-Warn-App/src/main/res/layout/fragment_information.xml index 57c1456fb9d5b93f32fe186987ee76669f8355bd..b506237672f54d2ad6991c22004fec640dd7da96 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_information.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_information.xml @@ -12,19 +12,23 @@ <include android:id="@+id/information_header" layout="@layout/include_header" - android:layout_width="0dp" + android:layout_width="match_parent" android:layout_height="wrap_content" app:icon="@{@drawable/ic_close}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toTopOf="@id/scrollview" app:title="@{@string/information_title}" /> <ScrollView - android:layout_width="0dp" + android:id="@+id/scrollview" + android:layout_width="match_parent" android:layout_height="0dp" android:fillViewport="true" - app:layout_constraintBottom_toBottomOf="@+id/guideline_bottom" + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_normal" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/information_header"> @@ -32,7 +36,6 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_normal" android:orientation="vertical"> <include @@ -40,9 +43,6 @@ layout="@layout/include_row" android:layout_width="match_parent" android:layout_height="wrap_content" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" app:subtitle="@{@string/release_info_header}" /> <include @@ -50,9 +50,6 @@ layout="@layout/include_row" 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/information_release" app:subtitle="@{@string/information_about_title}" /> <include @@ -61,9 +58,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:icon="@{@drawable/ic_link}" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/information_about" app:subtitle="@{@string/information_help_title}" /> <include @@ -71,9 +65,6 @@ layout="@layout/include_row" 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/information_help" app:subtitle="@{@string/information_terms_title}" /> <include @@ -81,9 +72,6 @@ layout="@layout/include_row" 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/information_terms" app:subtitle="@{@string/information_privacy_title}" /> <include @@ -91,9 +79,6 @@ layout="@layout/include_row" 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/information_privacy" app:subtitle="@{@string/information_technical_title}" /> <include @@ -101,9 +86,6 @@ layout="@layout/include_row" 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/information_technical" app:subtitle="@{@string/information_contact_title}" /> <include @@ -111,9 +93,6 @@ layout="@layout/include_row" 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/information_contact" app:subtitle="@{@string/information_legal_title}" /> <include @@ -121,9 +100,6 @@ layout="@layout/include_row" 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/information_contact" app:subtitle="@{@string/debugging_debuglog_title}" /> <TextView @@ -150,18 +126,10 @@ tools:text="16000000" tools:visibility="visible" /> - </LinearLayout> </ScrollView> - <androidx.constraintlayout.widget.Guideline - android:id="@+id/guideline_bottom" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layout_constraintGuide_end="@dimen/guideline_bottom" /> - </androidx.constraintlayout.widget.ConstraintLayout> </layout> diff --git a/Corona-Warn-App/src/main/res/layout/fragment_statistics_explanation.xml b/Corona-Warn-App/src/main/res/layout/fragment_statistics_explanation.xml index 66bbd2becafe0aa1abc11b6ef4ed0a7b48a22dc3..3a77158151f358e1e8e26e462e67c80f7fdaef76 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_statistics_explanation.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_statistics_explanation.xml @@ -314,15 +314,15 @@ android:text="@string/statistics_explanation_trend_title" /> <TextView + android:id="@+id/statistics_explanation_trend_text" style="@style/body2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/guideline_start" android:layout_marginTop="14dp" android:layout_marginEnd="@dimen/guideline_end" - android:contentDescription="@string/statistics_explanation_trend_text" android:focusable="true" - android:text="@string/statistics_explanation_trend_text" /> + tools:text="@string/statistics_explanation_trend_text" /> <TextView style="@style/headline6" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml index 43d97e3dd4471011be8aa6b142568e6397087aeb..7247a9c38210db58c3981a3877224d46c35f659d 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_consent.xml @@ -50,7 +50,8 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - android:importantForAccessibility="no"/> + android:focusable="true" + android:contentDescription="@string/submission_consent_main_illustration_description" /> <include layout="@layout/include_submission_consent_intro" android:id="@+id/include_submission_consent_intro" diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml index a2d7596344f3432824a3544d750532496e69b356..6e860659fbb1bce3782112fba9fa38fca5fb707f 100644 --- a/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml +++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_symptom_intro.xml @@ -36,7 +36,6 @@ android:layout_marginStart="@dimen/spacing_normal" android:layout_marginEnd="@dimen/spacing_normal" android:fillViewport="true" - android:focusable="true" android:orientation="vertical"> <TextView @@ -62,6 +61,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_normal" + android:focusable="true" android:text="@string/submission_symptom_initial_explanation" /> diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml index de5b14693dc5b51ec89f6dcfa6cd3fcf200a8ee0..a2bd07c8f2264973bfb2a1e84db6985393cc5e08 100644 --- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml +++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_incidence_layout.xml @@ -9,6 +9,7 @@ android:layout_height="wrap_content" tools:layout_height="wrap_content" tools:layout_width="@dimen/statistics_card_width" + android:id="@+id/incidence_container" tools:showIn="@layout/home_statistics_cards_basecard_layout"> <androidx.appcompat.widget.AppCompatImageView @@ -16,6 +17,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:importantForAccessibility="no" + android:contentDescription="@null" android:src="@drawable/ic_statistics_incidence" android:paddingStart="0dp" android:paddingEnd="@dimen/spacing_small" @@ -47,6 +49,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" + android:focusable="true" tools:text="98,9" /> <TextView diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml index 5d227e91ffc571b3db20c4f1d8d9a3f3e2491ed8..f44904922ab4c86fe30b4e7141d40e5deb3897c9 100644 --- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml +++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_infections_layout.xml @@ -9,6 +9,7 @@ android:layout_height="wrap_content" tools:layout_height="wrap_content" tools:layout_width="@dimen/statistics_card_width" + android:id="@+id/infections_container" tools:showIn="@layout/home_statistics_cards_basecard_layout"> <androidx.appcompat.widget.AppCompatImageView @@ -16,6 +17,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:importantForAccessibility="no" + android:contentDescription="@null" android:src="@drawable/ic_main_illustration_infection" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/flow_layout" /> @@ -42,6 +44,7 @@ <TextView android:id="@+id/primary_value" style="@style/StatisticsCardPrimaryValue" + android:focusable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" @@ -58,6 +61,7 @@ <TextView android:id="@+id/secondary_value" style="@style/StatisticsCardSecondaryValue" + android:focusable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="11.981" /> @@ -81,6 +85,7 @@ <TextView android:id="@+id/tertiary_value" + android:focusable="true" style="@style/StatisticsCardSecondaryValue" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml index 44b537c3567bb9b5bafe1a3f09fda373c8b5a43b..781e99cdf4a48cc807509c0b6f227212d8d5a91d 100644 --- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml +++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_keysubmissions_layout.xml @@ -9,6 +9,7 @@ android:layout_height="wrap_content" tools:layout_height="wrap_content" tools:layout_width="@dimen/statistics_card_width" + android:id="@+id/keysubmissions_container" tools:showIn="@layout/home_statistics_cards_basecard_layout"> <androidx.appcompat.widget.AppCompatImageView @@ -16,6 +17,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:importantForAccessibility="no" + android:contentDescription="@null" android:src="@drawable/ic_main_illustration_warnende_personen" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@id/flow_layout" /> @@ -42,6 +44,7 @@ <TextView android:id="@+id/primary_value" style="@style/StatisticsCardPrimaryValue" + android:focusable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" @@ -58,6 +61,7 @@ <TextView android:id="@+id/secondary_value" style="@style/StatisticsCardSecondaryValue" + android:focusable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="1.812" /> @@ -83,6 +87,7 @@ android:id="@+id/tertiary_value" style="@style/StatisticsCardSecondaryValue" android:layout_width="wrap_content" + android:focusable="true" android:layout_height="wrap_content" tools:text="20.922" /> diff --git a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml index d32cc602176b1c3b7a17d1e94a940ba5d5ee933b..feca8a674d5c9f9583b3c1bd2a67794507d57128 100644 --- a/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml +++ b/Corona-Warn-App/src/main/res/layout/home_statistics_cards_sevendayrvalue_layout.xml @@ -9,6 +9,7 @@ android:layout_height="wrap_content" tools:layout_height="wrap_content" tools:layout_width="@dimen/statistics_card_width" + android:id="@+id/seven_day_r_value_container" tools:showIn="@layout/home_statistics_cards_basecard_layout"> <androidx.appcompat.widget.AppCompatImageView @@ -44,6 +45,7 @@ <TextView android:id="@+id/primary_value" style="@style/StatisticsCardPrimaryValue" + android:focusable="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:includeFontPadding="false" diff --git a/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml b/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml new file mode 100644 index 0000000000000000000000000000000000000000..c5a0c49a6aac2f350c82a45119862e36e49c3e0a --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/new_release_info_item.xml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout> + <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView + android:id="@+id/title" + style="@style/subtitleBoldSixteen" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_small" + android:layout_marginEnd="@dimen/spacing_large" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/bullet_point" + app:layout_constraintTop_toTopOf="parent" + tools:text="Title bullet point" /> + + <ImageView + android:id="@+id/bullet_point" + android:layout_width="@dimen/bullet_point_size" + android:layout_height="@dimen/bullet_point_size" + android:layout_marginStart="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_tiny" + android:importantForAccessibility="no" + android:src="@drawable/bullet_point" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@id/title" /> + + <TextView + android:id="@+id/body" + style="@style/subtitle" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:layout_marginEnd="@dimen/spacing_large" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/title" + app:layout_constraintTop_toBottomOf="@id/title" + tools:text="Body bullet point" /> + + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml b/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml index acb7b20de81093749675c51e818c3dfa7c7f97f3..a28280e788de0f35b133d912b211aefa4a4de60b 100644 --- a/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/new_release_info_screen_fragment.xml @@ -4,31 +4,30 @@ xmlns:tools="http://schemas.android.com/tools"> <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/new_release_info_screen_container" + android:id="@+id/container" android:contentDescription="@string/release_info_header" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.appcompat.widget.Toolbar - android:id="@+id/new_release_info_toolbar" + android:id="@+id/toolbar" + style="@style/CWAToolbar.Close" android:layout_width="0dp" android:layout_height="wrap_content" - style="@style/CWAToolbar.Close" android:background="@color/colorBackground" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:title="@string/release_info_header" /> - <ScrollView - android:id="@+id/new_release_info_scrollview" + <androidx.core.widget.NestedScrollView + android:id="@+id/scrollview" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginBottom="@dimen/spacing_normal" app:layout_constraintBottom_toTopOf="@+id/new_release_info_next_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/new_release_info_toolbar"> + app:layout_constraintTop_toBottomOf="@id/toolbar"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" @@ -42,19 +41,19 @@ android:focusable="true" android:layout_marginTop="@dimen/spacing_normal" android:src="@drawable/ic_new_release_info" + android:importantForAccessibility="no" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:src="@drawable/ic_new_release_info" /> <TextView - android:id="@+id/new_release_info_headline" style="@style/headline5" + android:id="@+id/headline" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_small" - android:layout_marginStart="@dimen/spacing_normal" - android:accessibilityHeading="true" + android:layout_marginHorizontal="@dimen/spacing_normal" + android:layout_marginTop="40dp" android:focusable="true" android:text="@string/release_info_version_title" app:layout_constraintStart_toStartOf="parent" @@ -62,8 +61,8 @@ tools:text="@string/release_info_version_title" /> <TextView - android:id="@+id/new_release_info_body" style="@style/subtitleMedium" + android:id="@+id/new_release_info_body" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_normal" @@ -72,69 +71,53 @@ android:focusable="true" android:text="@string/release_info_version_body" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/new_release_info_headline" + app:layout_constraintTop_toBottomOf="@id/headline" app:layout_constraintEnd_toEndOf="parent" tools:text="@string/release_info_version_body" /> - <TextView - android:id="@+id/new_release_info_encounter_history_title" - style="@style/subtitleBoldSixteen" - android:accessibilityHeading="true" + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recycler_view" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_medium" - android:layout_marginStart="@dimen/spacing_normal" - android:layout_marginEnd="@dimen/spacing_normal" - android:text="@string/release_info_1_12_encounter_history_title" - android:focusable="true" + android:layout_marginTop="12dp" + android:nestedScrollingEnabled="false" + android:orientation="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="@id/bullet_point_encounter" - app:layout_constraintTop_toBottomOf="@id/new_release_info_body" - tools:text="@string/release_info_1_12_encounter_history_title"/> - - <ImageView - android:id="@+id/bullet_point_encounter" - android:layout_width="@dimen/bullet_point_size" - android:layout_height="@dimen/bullet_point_size" - android:src="@drawable/bullet_point" - android:importantForAccessibility="no" - android:layout_marginTop="@dimen/spacing_mega_tiny" - android:layout_marginStart="@dimen/spacing_normal" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="@+id/new_release_info_encounter_history_title" - app:layout_constraintBottom_toBottomOf="@+id/new_release_info_encounter_history_title" /> + app:layout_constraintTop_toBottomOf="@id/new_release_info_body" + tools:listitem="@layout/new_release_info_item" /> <TextView - android:id="@+id/new_release_info_encounter_history_body" style="@style/subtitle" + android:id="@+id/new_release_info_bottom" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" - android:layout_marginEnd="@dimen/spacing_large" + android:layout_margin="@dimen/spacing_normal" android:focusable="true" - android:text="@string/release_info_1_12_encounter_history_body" - app:layout_constraintTop_toBottomOf="@+id/new_release_info_encounter_history_title" - app:layout_constraintStart_toStartOf="@+id/new_release_info_encounter_history_title" + android:text="@string/release_info_version_body" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - tools:text="@string/release_info_1_12_encounter_history_body"/> + app:layout_constraintTop_toBottomOf="@id/recycler_view" + tools:text="@string/new_release_bottom" /> </androidx.constraintlayout.widget.ConstraintLayout> - </ScrollView> + </androidx.core.widget.NestedScrollView> <android.widget.Button - android:id="@+id/new_release_info_next_button" style="@style/buttonPrimary" + android:id="@+id/new_release_info_next_button" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_normal" - android:layout_marginEnd="@dimen/spacing_normal" - android:layout_marginBottom="@dimen/spacing_normal" + android:layout_margin="@dimen/spacing_normal" android:text="@string/release_info_continue_button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/scrollview" tools:text="@string/release_info_continue_button" /> </androidx.constraintlayout.widget.ConstraintLayout> -</layout> \ No newline at end of file +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml b/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml new file mode 100644 index 0000000000000000000000000000000000000000..63965a046a806d02d0c828ec50a1510030d55caf --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/survey_consent_fragment.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout 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"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/content_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/contact_diary_onboarding_background" + android:focusable="true"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + style="@style/CWAToolbar.BackArrow" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:title="@string/risk_details_accessibility_title" /> + + <ScrollView + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginBottom="@dimen/spacing_small" + app:layout_constraintBottom_toTopOf="@id/survey_next_button" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/survey_top_illustration" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:contentDescription="@string/contact_diary_onboarding_image_content_description" + android:focusable="true" + android:src="@drawable/ic_illustration_datenspende" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@drawable/ic_illustration_datenspende" /> + + <TextView + android:id="@+id/survey_bottom_info_headline" + style="@style/headline4" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:accessibilityHeading="true" + android:focusable="true" + android:text="@string/datadonation_details_survey_consent_top_title" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/survey_top_illustration" + tools:text="@string/datadonation_details_survey_consent_top_title" /> + + <TextView + android:id="@+id/survey_bottom_info_body" + style="@style/subtitleMedium" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_normal" + android:focusable="true" + android:text="@string/datadonation_details_survey_consent_top_body" + app:layout_constraintEnd_toEndOf="@id/guideline_end" + app:layout_constraintStart_toStartOf="@id/guideline_start" + app:layout_constraintTop_toBottomOf="@id/survey_bottom_info_headline" + tools:text="@string/datadonation_details_survey_consent_top_body" /> + + + <include + android:id="@+id/survey_bottom_info_box" + layout="@layout/include_tracing_status_card" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_medium" + app:body="@{@string/datadonation_details_survey_consent_info_card_body}" + app:headline="@{@string/datadonation_details_survey_consent_info_card_title}" + app:layout_constraintEnd_toStartOf="@+id/guideline_card_end" + app:layout_constraintStart_toStartOf="@+id/guideline_card_start" + app:layout_constraintTop_toBottomOf="@+id/survey_bottom_info_body" /> + + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_begin="@dimen/guideline_start" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_end="@dimen/guideline_end" /> + + <include layout="@layout/merge_guidelines_card" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </ScrollView> + + <android.widget.Button + android:id="@+id/survey_next_button" + style="@style/buttonPrimary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacing_normal" + android:layout_marginEnd="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_small" + android:text="@string/datadonation_details_survey_consent_button_title" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + tools:text="@string/datadonation_details_survey_consent_button_title" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + +</layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml index e8c99db33ae40a54c64c6ae605183e29bb7350a4..794ebdd3aee08a39a15fce7808b8201c8c1bd9ad 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_content_increased_view.xml @@ -45,6 +45,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacing_small" + android:contentDescription="@{state.getRiskContactBodyDescription(context)}" android:icon="@drawable/ic_risk_card_contact_increased" android:text="@{state.getRiskContactBody(context)}" android:textColor="@color/colorTextPrimary1InvertedStable" @@ -66,7 +67,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/row_contact" - tools:text="@string/risk_card_high_risk_most_recent_body" /> + tools:text="@string/risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day" /> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/row_tracing_days" diff --git a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml index a73f7d0c409a4c83d3c84132ec949a0db3763b62..b84c232d43226203c0a7f9e9cc864bd66e709ad6 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_content_low_view.xml @@ -48,6 +48,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:contentDescription="@{state.getRiskContactBodyDescription(context)}" android:icon="@drawable/ic_risk_card_contact" android:text="@{state.getRiskContactBody(context)}" android:textColor="@color/colorTextPrimary1InvertedStable" @@ -57,13 +58,28 @@ app:layout_constraintTop_toBottomOf="@+id/headline" tools:text="@plurals/risk_card_low_risk_encounter_days_body" /> + <de.rki.coronawarnapp.ui.view.TracingCardInfoRow + android:id="@+id/row_contact_last" + gone="@{state.getRiskContactLast(context) == null}" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:icon="@drawable/ic_risk_card_calendar" + android:text="@{state.getRiskContactLast(context) ?? ``}" + android:textColor="@color/colorTextPrimary1InvertedStable" + app:compatIconTint="@color/colorStableLight" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/row_contact" + tools:text="@string/risk_card_low_risk_most_recent_body_encounters_on_more_than_one_day" /> + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/row_saved_days" + gone="@{state.getRiskContactLast(context) != null}" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/row_contact"> + app:layout_constraintTop_toBottomOf="@+id/row_contact_last"> <de.rki.coronawarnapp.ui.view.CircleProgress android:id="@+id/risk_card_row_saved_days_circle_progress" diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_access_survey_card.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_access_survey_card.xml new file mode 100644 index 0000000000000000000000000000000000000000..69635c8f19b5127277f2e49069fd13d12ef6c1e8 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/tracing_details_access_survey_card.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> +<layout 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"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/tracing_details_survey_card_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="true" + tools:showIn="@layout/home_card_container_layout"> + + <TextView + android:id="@+id/tracing_details_survey_card_title" + style="@style/headline5" + _toStartOf="parent" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/card_padding" + android:layout_marginTop="@dimen/card_padding" + android:layout_marginEnd="16dp" + android:accessibilityHeading="true" + android:focusable="false" + android:text="@string/datadonation_details_access_survey_card_title" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_goneMarginEnd="0dp" + tools:text="Befragung zur Corona-Warn-App" /> + + <TextView + android:id="@+id/tracing_details_survey_card_body" + style="@style/subtitleMedium" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/card_padding" + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginEnd="@dimen/spacing_small" + android:focusable="false" + android:text="@string/datadonation_details_access_survey_card_content" + app:layout_constraintEnd_toStartOf="@+id/tracing_details_survey_card_icon" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tracing_details_survey_card_title" /> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/tracing_details_survey_card_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/contact_diary_homescreen_card_image_content_description" + android:importantForAccessibility="no" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/tracing_details_survey_card_body" + app:srcCompat="@drawable/ic_tracing_survey_icon" /> + + <androidx.constraintlayout.widget.Barrier + android:id="@+id/tracing_details_survey_card_barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="bottom" + app:constraint_referenced_ids="tracing_details_survey_card_icon,tracing_details_survey_card_body" /> + + <Button + android:id="@+id/tracing_details_survey_card_button" + style="@style/buttonPrimary" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/card_padding" + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginEnd="@dimen/card_padding" + android:layout_marginBottom="@dimen/card_padding" + android:text="@string/datadonation_details_access_survey_card_button_text" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tracing_details_survey_card_barrier" /> + </androidx.constraintlayout.widget.ConstraintLayout> +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml b/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml index f26f124084a0c960d3c83c0665110af5faa7effc..8751e27e253b30db8154cd01d9c125f0198d2071 100644 --- a/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml +++ b/Corona-Warn-App/src/main/res/layout/tracing_details_item_behavior_increased_view.xml @@ -58,7 +58,7 @@ <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacing_tiny" + android:layout_margin="@dimen/spacing_tiny" android:focusable="true"> <ImageView @@ -142,6 +142,14 @@ app:layout_constraintVertical_bias="0.0" /> </androidx.constraintlayout.widget.ConstraintLayout> + <de.rki.coronawarnapp.tracing.ui.details.items.behavior.BehaviorInfoRow + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:backgroundTint="@color/colorSemanticHighRisk" + android:foregroundTint="@color/colorStableLight" + android:icon="@drawable/ic_faq_information" + android:text="@string/risk_details_increased_risk_faq_link_text" /> </LinearLayout> </layout> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml b/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml new file mode 100644 index 0000000000000000000000000000000000000000..3c598525006aed7ca597de2bd57c5cc3a1b55016 --- /dev/null +++ b/Corona-Warn-App/src/main/res/menu/menu_bottom_nav.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/mainFragment" + android:icon="@drawable/ic_nav_home" + android:title="@string/bottom_nav_home_title" /> + + <item + android:id="@+id/contact_diary_nav_graph" + android:icon="@drawable/ic_nav_diary" + android:title="@string/bottom_nav_diary_title" /> +</menu> \ No newline at end of file diff --git a/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml index 06e81152ec917051b4cd2c76330b5ea1afe75444..a11f6a23613373c2b2692c2a8450b60d447d676f 100644 --- a/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml @@ -83,6 +83,10 @@ app:destination="@id/contactDiaryOverviewFragment" app:popUpTo="@id/contact_diary_nav_graph" app:popUpToInclusive="true" /> + <argument + android:name="showBottomNav" + android:defaultValue="true" + app:argType="boolean" /> </fragment> <fragment android:id="@+id/contactDiaryInformationPrivacyFragment" diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml index ea016ac2088684b06064316f489a2b3b35acd78e..51b1c1ccea2dd399f42ec89992619945f05c4c65 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -5,7 +5,10 @@ android:id="@+id/nav_graph" app:startDestination="@id/mainFragment"> + <!-- Corona Test graph--> <include app:graph="@navigation/test_nav_graph" /> + <!-- Contact Diary graph--> + <include app:graph="@navigation/contact_diary_nav_graph" /> <!-- Main --> <fragment @@ -226,6 +229,9 @@ <action android:id="@+id/action_riskDetailsFragment_to_settingsTracingFragment" app:destination="@id/settingsTracingFragment" /> + <action + android:id="@+id/action_riskDetailsFragment_to_surveyConsentFragment" + app:destination="@id/surveyConsentFragment" /> </fragment> @@ -503,12 +509,21 @@ <!-- New Release --> <fragment android:id="@+id/newReleaseInfoFragment" - android:name="de.rki.coronawarnapp.ui.release.NewReleaseInfoFragment" + android:name="de.rki.coronawarnapp.release.NewReleaseInfoFragment" android:label="NewReleaseInfoFragment"> <argument android:name="comesFromInfoScreen" - app:argType="boolean" - android:defaultValue="false" /> + android:defaultValue="false" + app:argType="boolean" /> + </fragment> + <fragment + android:id="@+id/surveyConsentFragment" + android:name="de.rki.coronawarnapp.datadonation.survey.consent.SurveyConsentFragment" + android:label="survey_consent_fragment" + tools:layout="@layout/survey_consent_fragment"> + <argument + android:name="SurveyType" + app:argType="de.rki.coronawarnapp.datadonation.survey.Surveys$Type" /> </fragment> <fragment android:id="@+id/analyticsUserInputFragment" 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 177162a554f272cef015a48a0c8ef322a018ac2d..571fe920df2d859ae37377e5fd9e1187ee028ddf 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Ð’Ñе още нÑма въведени лица"</string> <string name="contact_diary_person_list_no_items_subtitle">"Създайте лице и го добавете към Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº на контактите."</string> <string name="contact_diary_person_bottom_sheet_title">"Лице"</string> - <string name="contact_diary_person_bottom_sheet_text_input_hint">"Име, фамилиÑ"</string> + <string name="contact_diary_person_bottom_sheet_text_input_hint">"Име"</string> <string name="contact_diary_person_bottom_sheet_save_button">"Запазване"</string> <string name="contact_diary_location_bottom_sheet_title">"МÑÑто"</string> <string name="contact_diary_location_bottom_sheet_text_input_hint">"ОпиÑание"</string> @@ -40,6 +40,8 @@ <string name="contact_diary_onboarding_functionality_fourth_section">"Можете да премахвате запиÑи за хора и меÑта от Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº по вÑÑко време. ЗапиÑите от дневника ще Ñе изтриват автоматично Ñлед 16 дни."</string> <!-- XTXT: Contact diary onboarding screen fifth functionality --> <string name="contact_diary_onboarding_functionality_fifth_section">"Ðко е необходимо, можете да екÑпортирате Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº на контактите във формат на обикновен текÑÑ‚ и Ñлед това да го разпечатате, редактирате или предоÑтавите на Ñлужбата за общеÑтвено здравеопазване."</string> + <!-- XTXT: Contact diary onboarding screen sixth functionality --> + <string name="contact_diary_onboarding_functionality_sixth_section">"Ðе е задължително поÑочените Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð´Ð° Ñа Ñвързани Ñ Ð¾Ñ‚Ð±ÐµÐ»Ñзаните от Ð’Ð°Ñ Ñ…Ð¾Ñ€Ð° и меÑта. МолÑ, не Ñи правете погрешни изводи."</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_title">"Дневник на контактите"</string> <!-- XTXT: Body for legal information of the contact diary onboarding screen --> @@ -61,6 +63,17 @@ <!-- XTXT: Header for contact diary overview screen --> <string name="contact_diary_overview_header">"Ðачална Ñтраница"</string> + + <!-- XTXT: Title for contact diary overview screen high risk information --> + <string name="contact_diary_high_risk_title">"Повишен риÑк"</string> + <!-- XTXT: Title for contact diary overview screen low risk information --> + <string name="contact_diary_low_risk_title">"ÐиÑък риÑк"</string> + <!-- XTXT: Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body">"въз оÑнова на оценените от приложението контакти."</string> + <!-- XTXT: Extended Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body_extended">"въз оÑнова на оценените от приложението контакти. Ðе е задължително те да Ñа Ñвързани Ñ Ð¾Ñ‚Ð±ÐµÐ»Ñзаните от Ð’Ð°Ñ Ñ…Ð¾Ñ€Ð° и меÑта."</string> + + <!-- XTXT: content description of contact journal image on home screen --> <string name="contact_diary_homescreen_card_image_content_description">"Затворена книга Ñ Ð¾Ñ‚Ð¼ÐµÑ‚ÐºÐ°"</string> <!-- XTXT: content description of contact journal header image on onboarding screen --> 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 new file mode 100644 index 0000000000000000000000000000000000000000..3650dfb4b24615fb4156b7f1e4464b7f7fc48a19 --- /dev/null +++ b/Corona-Warn-App/src/main/res/values-bg/release_info_strings.xml @@ -0,0 +1,15 @@ +<?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"> + <!-- #################################### + Release Info Screen 1.12 + ###################################### --> + + <!-- XHED: Title for the release info screen --> + <string name="release_info_header">"Ðови функции"</string> + <!-- XHED: Version title for the release info screen --> + <string name="release_info_version_title">"ВерÑÐ¸Ñ %1$s"</string> + <!-- XTXT: Description for the release info screen --> + <string name="release_info_version_body">"Ðаред Ñ ÐºÐ¾Ñ€ÐµÐºÑ†Ð¸Ð¸Ñ‚Ðµ на грешки наÑтоÑщата Ð°ÐºÑ‚ÑƒÐ°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ñъдържа Ñъщо и нови и разширени функции."</string> + <!-- XBUT: Continue button for the release info screen --> + <string name="release_info_continue_button">"Ðапред"</string> + +</resources> \ No newline at end of file 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 53c22b0c7624c98bf74cdee72f6849d981b7220b..2bad84480a808d63fd6388b35ba5715ad6935bb8 100644 --- a/Corona-Warn-App/src/main/res/values-bg/strings.xml +++ b/Corona-Warn-App/src/main/res/values-bg/strings.xml @@ -22,12 +22,6 @@ <!-- NOTR --> <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> <!-- NOTR --> - <string name="preference_background_job_allowed"><xliff:g id="preference">"preference_background_job_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_mobile_data_allowed"><xliff:g id="preference">"preference_mobile_data_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_registration_token"><xliff:g id="preference">"preference_registration_token"</xliff:g></string> - <!-- NOTR --> <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> <!-- NOTR --> <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> @@ -173,8 +167,8 @@ <item quantity="few">"Брой дни Ñ Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½ риÑк - %1$d"</item> <item quantity="many">"Брой дни Ñ Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ñ Ð¿Ð¾Ð²Ð¸ÑˆÐµÐ½ риÑк - %1$d"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"Ðай-близка дата - %1$s"</string> + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"Ðай-близка дата - %1$s"</string> <!-- #################################### Risk Card - Progress @@ -230,11 +224,11 @@ <!-- XHED: App overview subtitle for tracing explanation--> <string name="main_overview_subtitle_tracing">"РегиÑтриране на излаганиÑта на риÑк"</string> <!-- YTXT: App overview body text about tracing --> - <string name="main_overview_body_tracing">"РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк е една от трите оÑновни функции на приложението. Когато е активирана, вÑички оÑъщеÑтвени контакти между Ñмартфони Ñе запиÑват, без да е необходимо да правите друго."</string> + <string name="main_overview_body_tracing">"РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк е една от трите оÑновни функции на приложението. Когато е активирана, вÑички оÑъщеÑтвени контакти между уÑтройÑтва Ñе запиÑват автоматично."</string> <!-- XHED: App overview subtitle for risk explanation --> <string name="main_overview_subtitle_risk">"РиÑк от заразÑване"</string> <!-- YTXT: App overview body text about risk levels --> - <string name="main_overview_body_risk">"Ðко през поÑледните 14 дни Ñте имали контакт Ñ Ð»Ð¸Ñ†Ðµ, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ, приложението изчиÑлÑва какъв е ВашиÑÑ‚ риÑк от заразÑване, като измерва продължителноÑтта и близоÑтта на излагането."</string> + <string name="main_overview_body_risk">"Когато региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ðµ активно, приложението изчиÑлÑва какъв е ВашиÑÑ‚ риÑк от заразÑване, като измерва продължителноÑтта и близоÑтта на контактите Ви Ñ Ñ…Ð¾Ñ€Ð°, които Ñа били диагноÑтицирани Ñ COVID-19 през поÑледните 14 дни."</string> <!-- XHED: App overview subtitle for risk level list --> <string name="main_overview_subtitle_risk_levels">"Възможни Ñа Ñледните нива на риÑк:"</string> <!-- XTXT: App overview increased risk level --> @@ -246,7 +240,7 @@ <!-- XHED: App overview subtitle for test procedure explanation --> <string name="main_overview_headline_test">"УведомÑване на други потребители"</string> <!-- YTXT: App overview body text about rest procedure --> - <string name="main_overview_body_test">"Друга оÑновна Ñ„ÑƒÐ½ÐºÑ†Ð¸Ñ Ðµ региÑтрирането на теÑтове и извличането на резултати. Ðко имате поÑтавена диагноза \"коронавируÑ\", може да уведомите оÑтаналите потребители и да прекъÑнете веригата на заразÑване."</string> + <string name="main_overview_body_test">"Може да региÑтрирате теÑÑ‚ и да извлечете резултатите от него в приложението. Ðко имате поÑтавена диагноза „коронавируÑ“, може да уведомите оÑтаналите потребители и да прекъÑнете веригата на заразÑване."</string> <!-- XHED: App overview headline for glossary --> <string name="main_overview_headline_glossary">"Дефиниции на термини:"</string> <!-- XHED: App overview subtitle for glossary key storage --> @@ -256,7 +250,7 @@ <!-- XHED: App overview subtitle for glossary risk calculation --> <string name="main_overview_subtitle_glossary_calculation">"Проверка за излагане на риÑк"</string> <!-- YTXT: App overview body for glossary risk calculation --> - <string name="main_overview_body_glossary_calculation">"Данните от региÑтъра на излаганиÑта на риÑк Ñе извличат и Ñинхронизират Ñ Ñ€ÐµÐ³Ð¸Ñтрираните Ñлучаи на заразÑване на други потребители. Проверката за излагане на риÑк Ñе извършва автоматично нÑколко пъти на ден."</string> + <string name="main_overview_body_glossary_calculation">"Данните от региÑтъра на излаганиÑта на риÑк Ñе извличат и Ñинхронизират Ñ Ð¸Ð·Ð²ÐµÑтиÑта за риÑковете на други потребители. Проверката на Ð’Ð°ÑˆÐ¸Ñ Ñ€Ð¸Ñк Ñе извършва автоматично нÑколко пъти на ден."</string> <!-- XHED: App overview subtitle for glossary contact --> <string name="main_overview_subtitle_glossary_contact">"Излагане на риÑк"</string> <!-- YTXT: App overview body for glossary contact --> @@ -328,6 +322,8 @@ <string name="risk_details_information_body_low_risk">"Вашето ниво на риÑк от заразÑване е ниÑко, защото нÑмате региÑтрирани контакти Ñ Ð»Ð¸Ñ†Ð°, които впоÑледÑтвие Ñа били диагноÑтицирани Ñ COVID-19, или ако Ñте имали такива, те Ñа били краткотрайни и от по-голÑмо разÑтоÑние."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"РиÑкът от заразÑване Ñе изчиÑлÑва локално на Ñмартфона Ви въз оÑнова на региÑтрираните данни за излагане. ИзчиÑлението включва Ñъщо така разÑтоÑнието от и продължителноÑтта на вÑички контакти Ñ Ð»Ð¸Ñ†Ð°, диагноÑтицирани Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ, както и възможноÑтта им да заразÑÑ‚ околните. Ðикой оÑвен Ð’Ð°Ñ Ð½Ðµ може да види или да получи данни за Вашето ниво на риÑк."</string> + <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> + <string name="risk_details_information_body_increased_risk_date">"Ðивото на риÑка Ви от заразÑване е повишено, защото на %1$s Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ."</string> <!-- YTXT: risk details - increased risk explanation text with variable for day(s) since last contact --> <plurals name="risk_details_information_body_increased_risk"> <item quantity="one">"Ðивото на риÑка Ви от заразÑване е повишено, защото преди %1$s дни Ñте имали продължителен и близък контакт Ñ Ð¿Ð¾Ð½Ðµ едно лице, диагноÑтицирано Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑ."</item> @@ -352,9 +348,6 @@ <string name="risk_details_explanation_dialog_title">"Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно функционалноÑтта за региÑтриране на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк"</string> <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> <string name="risk_details_explanation_dialog_faq_body">"За повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð²Ð¸Ð¶Ñ‚Ðµ Ñтраницата „ЧЗВ“."</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> - <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"ВашиÑÑ‚ ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° риÑк"</string> <!-- YTXT: risk details - deadman notification text --> @@ -425,7 +418,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"За да уÑтановите дали за Ð’Ð°Ñ ÑъщеÑтвува риÑк от заразÑване, трÑбва да активирате функциÑта за региÑтриране на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк Ñе извършва Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰Ñ‚Ð° на Bluetooth връзка, при коÑто ВашиÑÑ‚ Ñмартфон Ñ Android получава криптираните Ñлучайни идентификационни кодове на други потребители и изпраща до техните уÑтройÑтва Вашите Ñлучайни ИД. ФункциÑта може да бъде дезактивирана по вÑÑко време. "</string> + <string name="onboarding_tracing_body">"РегиÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк Ñе извършва Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰Ñ‚Ð° на Bluetooth връзка, при коÑто ВашиÑÑ‚ Ñмартфон Ñ Android получава криптираните Ñлучайни ИД кодове на други потребители на приложението и изпраща до техните уÑтройÑтва Вашите Ñлучайни ИД кодове. ФункциÑта може да бъде дезактивирана по вÑÑко време."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"Криптираните Ñлучайни идентификатори предават Ñамо Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° дата, продължителноÑÑ‚ и близоÑÑ‚ на контакта (изчиÑлена от Ñилата на Ñигнала). СамоличноÑтта Ви не може да бъде уÑтановена по Ñлучайните ИД."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -475,9 +468,9 @@ <!-- XACT: Onboarding (test) page title --> <string name="onboarding_test_accessibility_title">"Въведение – Ñтраница 5 от 6: Ðко имате поÑтавена диагноза \"коронавируÑ\""</string> <!-- XHED: onboarding(test) - about positive tests --> - <string name="onboarding_test_headline">"Ðко имате поÑтавена диагноза \"коронавируÑ\",..."</string> + <string name="onboarding_test_headline">"Ðко имате поÑтавена диагноза „коронавируÑ“"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> - <string name="onboarding_test_subtitle">"... Ð¼Ð¾Ð»Ñ Ñъобщете за това в приложението Corona-Warn-App. СподелÑнето на резултатите от Вашите теÑтове е доброволно и безопаÑно. Ðаправете го в името на общото здраве."</string> + <string name="onboarding_test_subtitle">"Ðко получите положителен резултат от теÑÑ‚, Ð¼Ð¾Ð»Ñ Ñъобщете за това в приложението. СподелÑнето на резултатите от Вашите теÑтове е доброволно и безопаÑно. Ðаправете го в името на общото здраве."</string> <!-- YTXT: onboarding(test) - explain test --> <string name="onboarding_test_body">"Вашето извеÑтие Ñе криптира Ñ Ð²Ð¸Ñока Ñтепен на ÑигурноÑÑ‚ и Ñе обработва на защитен Ñървър. Лицата, чиито криптирани Ñлучайни ИД кодове Ñа запазени на Ð’Ð°ÑˆÐ¸Ñ Ñмартфон, ще получат предупреждение, както и Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ñ‚Ð½Ð¾Ñно това, което трÑбва да направÑÑ‚."</string> <!-- XACT: onboarding(test) - illustraction description, header image --> @@ -527,7 +520,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"УÑлугите за ÑподелÑне на меÑтоположението Ñа дезактивирани"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"За да може приложението да определи дали има вероÑтноÑÑ‚ да Ñте Ñе заразили, трÑбва да активирате функциÑта за региÑтриране на излаганиÑта на риÑк. Ð¢Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð¸ в различни държави, което означава, че Ñе вземат предвид и контактите Ви Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸ на други официални Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° борба Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑа.\n\nФункциÑта за региÑтриране на излаганиÑта получава чрез Bluetooth и запиÑва на Вашето уÑтройÑтво Ñ Android криптираните Ñлучайни идентификатори на оÑтаналите потребители в обхвата му и изпраща към техните уÑтройÑтва Вашите Ñлучайни идентификатори. Приложението вÑеки ден Ð¸Ð·Ñ‚ÐµÐ³Ð»Ñ ÑпиÑъци ÑÑŠÑ Ñлучайните ИД и евентуални данни за развитието на Ñимптомите на вÑички потребители Ñ Ð¿Ð¾Ð»Ð¾Ð¶Ð¸Ñ‚ÐµÐ»ÐµÐ½ теÑÑ‚ за коронавируÑ, които доброволно Ñа Ñподелили тази Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ (и по-конкретно Ñвоите Ñлучайните ИД) чрез приложението Ñи. ПоÑле ÑпиÑъците Ñе ÑравнÑват ÑÑŠÑ Ñлучайните ИД на потребителите, Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ Ñте Ñе Ñрещали, запиÑани на Вашето уÑтройÑтво Ñ Android, и така Ñе изчиÑлÑва вероÑтноÑтта да Ñте Ñе заразили. Ðко е необходимо, получавате предупреждение. Можете да активирате и дезактивирате региÑтрирането на излаганиÑта по вÑÑко време.\n\nПриложението никога не Ñъбира лични данни от типа на име, Ð°Ð´Ñ€ÐµÑ Ð¸ меÑтоположение и не ÑÐ¿Ð¾Ð´ÐµÐ»Ñ Ð¿Ð¾Ð´Ð¾Ð±Ð½Ð° Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ñ Ð´Ñ€ÑƒÐ³Ð¸ потребители. СамоличноÑтта Ви не може да бъде определена по Вашите Ñлучайни ИД."</string> + <string name="settings_tracing_body_text">"За да може приложението да определи дали ÑъщеÑтвува риÑк да Ñте Ñе заразили, трÑбва да активирате функциÑта за региÑтриране на излаганиÑта на риÑк. Ð¢Ñ Ñ€Ð°Ð±Ð¾Ñ‚Ð¸ на транÑнационален принцип, което означава, че Ñе вземат предвид и контактите Ви Ñ Ð¿Ð¾Ñ‚Ñ€ÐµÐ±Ð¸Ñ‚ÐµÐ»Ð¸ на други официални Ð¿Ñ€Ð¸Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð·Ð° борба Ñ ÐºÐ¾Ñ€Ð¾Ð½Ð°Ð²Ð¸Ñ€ÑƒÑа.\n\nФункциÑта за региÑтриране на излаганиÑта на риÑк работи по ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð½Ð°Ñ‡Ð¸Ð½: Чрез Bluetooth Ñе оÑъщеÑтвÑва обмен на криптираните Ñлучайно генерирани ИД кодове между Вашето Android уÑтройÑтво и тези на другите потребители на приложението. Ð’Ñеки ден приложението Ð¸Ð·Ñ‚ÐµÐ³Ð»Ñ ÑпиÑъци на Ñлучайните ИД кодове на вÑички потребители Ñ Ð¿Ð¾Ð»Ð¾Ð¶Ð¸Ñ‚ÐµÐ»ÐµÐ½ теÑÑ‚ за вируÑа, Ñподелен в приложението, както и доброволно предоÑтавените от Ñ‚ÑÑ… данни за проÑвените Ñимптоми, и най-вече техните Ñлучайно генерирани ИД кодове. След това ИД кодове от тези ÑпиÑъци Ñе ÑравнÑват Ñ Ð˜Ð” кодове на потребителите, запиÑани във Вашето Android уÑтройÑтво, за да Ñе изчиÑли вероÑтноÑтта да Ñте Ñе заразили и за да получите предупреждение, ако е необходимо.\n\nМожете да активирате и дезактивирате региÑтрирането на Ð¸Ð·Ð»Ð°Ð³Ð°Ð½Ð¸Ñ Ð½Ð° риÑк по вÑÑко време.\n\nПриложението никога не Ñъбира лични данни от типа на име, Ð°Ð´Ñ€ÐµÑ Ð¸ меÑтоположение и не ÑÐ¿Ð¾Ð´ÐµÐ»Ñ Ð¿Ð¾Ð´Ð¾Ð±Ð½Ð° Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ñ Ð´Ñ€ÑƒÐ³Ð¸ потребители. СамоличноÑтта Ви не може да бъде определена по Вашите Ñлучайни ИД."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Ðктивно"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -555,7 +548,7 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_connection_headline">"Ðеобходима е връзка Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if connection is disabled --> - <string name="settings_tracing_status_connection_body">"Ðеобходима е връзка Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚ за изчиÑлÑване на излаганиÑта на риÑк. МолÑ, Ñвържете Ñе Ñ Wi-Fi или мобилна мрежа за данни от наÑтройките на уÑтройÑтвото Ñи."</string> + <string name="settings_tracing_status_connection_body">"Ðеобходима е връзка Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð½ÐµÑ‚ за изчиÑлÑване на Ð’Ð°ÑˆÐ¸Ñ Ñ€Ð¸Ñк от заразÑване. МолÑ, Ñвържете Ñе Ñ Wi-Fi или мобилна мрежа за данни от наÑтройките на уÑтройÑтвото Ñи."</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card --> <string name="settings_tracing_status_connection_button">"Към наÑтройките на уÑтройÑтвото"</string> <!-- XTXT: settings(tracing) - explains the circle progress indicator to the right with the current value --> @@ -686,7 +679,7 @@ <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_headline">"Как можем да Ви помогнем?"</string> <!-- YTXT: Body text for technical contact and hotline information page --> - <string name="information_contact_body">"За техничеÑки въпроÑи отноÑно Corona-Warn-App Ñе обадете на горещата линиÑ.\n\nЛицата ÑÑŠÑ Ñлухови Ð·Ð°Ñ‚Ñ€ÑƒÐ´Ð½ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³Ð°Ñ‚ да използват релейните уÑлуги Tess (за превод между пиÑмен немÑки и жеÑтомимичен език), за да Ñе обаждат на горещата телефонна линиÑ. Може да изтеглите приложението от Google Play."</string> + <string name="information_contact_body">"МолÑ, ако имате техничеÑки въпроÑи отноÑно Corona-Warn-App, обадете на горещата национална линиÑ.\n\nЛицата ÑÑŠÑ Ñлухови Ð·Ð°Ñ‚Ñ€ÑƒÐ´Ð½ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³Ð°Ñ‚ да използват релейните уÑлуги Tess (за превод между пиÑмен немÑки и жеÑтомимичен език), за да Ñе обаждат на горещата телефонна линиÑ. Може да изтеглите приложението от Google Play."</string> <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_subtitle_phone">"ТехничеÑки въпроÑи:"</string> <!-- XLNK: Button / hyperlink to phone call for technical contact and hotline information page --> @@ -714,7 +707,7 @@ <!-- XHED: Headline for legal information page, publisher section --> <string name="information_legal_headline_publisher">"Издател"</string> <!-- YTXT: subtitle for legal information page, publisher section --> - <string name="information_legal_subtitle_publisher">"(отговорен ÑъглаÑно § 5, параграф 1 от TMG, § 55 параграф 1 от RStV, DS-GVO, BDSG"</string> + <string name="information_legal_subtitle_publisher">"(отговорен ÑъглаÑно член 5, параграф 1 от TMG и член 18, параграф 1 от МStV)"</string> <!-- YTXT: body for legal information page, publisher section --> <string name="information_legal_body_publisher">"Robert Koch Institute"<xliff:g id="line_break">"\n"</xliff:g>"Nordufer 20"<xliff:g id="line_break">"\n"</xliff:g>"13353 Berlin"<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"предÑтавлÑван от ÑÐ²Ð¾Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¾Ñ€"</string> <!-- XHED: Headline for legal information page, contact section --> @@ -739,7 +732,7 @@ <!-- YTXT: Description for the debug option to record log files --> <string name="debugging_debuglog_intro_explanation">"Тази Ð¾Ð¿Ñ†Ð¸Ñ Ð·Ð°Ð¿Ð¸Ñва дейÑтвиÑта на приложението в журнал. Ðко е активна и възникне грешка, ще можете да Ñподелите отчета Ñ Ñ€Ð°Ð·Ñ€Ð°Ð±Ð¾Ñ‚Ñ‡Ð¸Ñ†Ð¸Ñ‚Ðµ, за да им помогнете да решат проблема.\nÐко оÑтавите опциÑта активна, това може да доведе до запълване на проÑтранÑтвото за Ñъхранение. Ðко Ñ Ð´ÐµÐ·Ð°ÐºÑ‚Ð¸Ð²Ð¸Ñ€Ð°Ñ‚Ðµ, отчетите за грешки Ñе изтриват."</string> <!-- YTXT: Warning regarding downsides of recording a log file --> - <string name="debugging_debuglog_intro_warning">"МолÑ, имайте предвид, че отчетите за грешки може да Ñъдържат поверителни данни (например резултати от теÑтове или запиÑи в журнала на контактите) Ето защо не бива да ÑподелÑте отчетите за грешки публично."</string> + <string name="debugging_debuglog_intro_warning">"МолÑ, имайте предвид, че отчетите за грешки може да Ñъдържат поверителни данни (например резултати от теÑтове или запиÑи в дневника на контактите). Ето защо не бива да ÑподелÑте отчетите за грешки публично."</string> <!-- XBUT: Button text to start the log recording --> <string name="debugging_debuglog_action_start_recording">"Ðачало"</string> <!-- XBUT: Button text to stop the log recording --> @@ -768,7 +761,7 @@ <!-- XHED: Dialog title for generic web request error --> <string name="submission_error_dialog_web_generic_error_title">"Грешка"</string> <!-- XMSG: Dialog body for generic web request network error with status code --> - <string name="submission_error_dialog_web_generic_network_error_body">"Ðе беше уÑтановена връзка (%1$d). МолÑ, опитайте отново."</string> + <string name="submission_error_dialog_web_generic_network_error_body">"Възможно е да нÑмате доÑтъп до интернет. МолÑ, проверете дали имате връзка."</string> <!-- XMSG: Dialog body for generic web request error without status code --> <string name="submission_error_dialog_web_generic_error_body">"Ðе беше уÑтановена връзка. МолÑ, опитайте отново."</string> <!-- XBUT: Positive button for generic web request error --> @@ -902,7 +895,7 @@ <!-- XHED: Page headline for other warnings screen --> <string name="submission_test_result_positive_steps_warning_others_heading">"Предупредете другите"</string> <!-- YTXT: Body text for for other warnings screen--> - <string name="submission_test_result_positive_steps_warning_others_body">"Споделете Ñвоите Ñлучайни идентификатори и предупредете околните.\nПомогнете ни да определÑме риÑка от заразÑване за Ñ‚ÑÑ… по-точно, като Ñподелите и кога за пръв път Ñте забелÑзали Ñимптомите на коронавируÑната инфекциÑ."</string> + <string name="submission_test_result_positive_steps_warning_others_body">"МолÑ, Ñподелете Ñвоите Ñлучайни идентификатори и предупредете околните.\nПомогнете ни да определÑме риÑка от заразÑване за Ñ‚ÑÑ… по-точно, като Ñподелите и кога за пръв път Ñте забелÑзали Ñимптомите на коронавируÑната инфекциÑ."</string> <!-- XBUT: positive test result : continue button --> <string name="submission_test_result_positive_continue_button">"Ðапред"</string> <!-- XBUT: positive test result : continue button with symptoms--> @@ -947,7 +940,7 @@ <!-- XHED: Page title for TAN submission pge --> <string name="submission_tan_title">"Въведете ТÐРкод"</string> <!-- YTXT: Body text for the tan submission page --> - <string name="submission_tan_body">"МолÑ, въведете предоÑÑ‚Ð°Ð²ÐµÐ½Ð¸Ñ Ð’Ð¸ 10-цифрен ТÐРкод."</string> + <string name="submission_tan_body">"Въведете предоÑÑ‚Ð°Ð²ÐµÐ½Ð¸Ñ Ð’Ð¸ 10-цифрен ТÐРкод."</string> <!-- XBUT: Submit TAN button --> <string name="submission_tan_button_text">"Ðапред"</string> <!-- XACT: Submission Tan page title --> @@ -960,6 +953,9 @@ <!-- Submission Intro --> <!-- XBUT: Submission introduction next button--> <string name="submission_intro_button_next">"Ðапред"</string> + <!-- YTXT: Description for illustration in submission onboarding--> + <string name="submission_intro_illustration_description">"КриптираниÑÑ‚ положителен резултат от теÑÑ‚ Ñе изпраща в ÑиÑтемата, за да бъдат предупредени оÑтаналите потребители."</string> + <!-- Dispatcher --> <!-- XHED: Page headline for dispatcher menu --> @@ -1047,10 +1043,10 @@ <string name="submission_done_further_info_title">"Още информациÑ:"</string> <!-- YTXT: submission done further info bullet points --> <string-array name="submission_done_further_info_bullet_points"> - <item>"Периодът, в който трÑбва да Ñте под карантина, е 14 дни. Ðаблюдавайте Ñвоите Ñимптоми и Ñ‚Ñхното развитие."</item> - <item>"Службата за общеÑтвено здравеопазване ще изиÑка от Ð’Ð°Ñ Ð´Ð° ÑÑŠÑтавите ÑпиÑък на хората, Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ Ñте били в близък контакт (на разÑтоÑние под 2 метра или при разговор лице в лице) в продължение на повече от 15 минути през поÑледните два дни, преди да развиете Ñимптоми. Може да използвате Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº на контактите – проÑто екÑпортирайте запиÑите и Ñлед това ги разпечатайте или ги изпратете по имейл."</item> + <item>"Периодът, в който трÑбва да Ñте под карантина, обикновено е 14 дни. Ðаблюдавайте внимателно развитието на Вашите Ñимптоми."</item> + <item>"Службата за общеÑтвено здравеопазване ще изиÑка от Ð’Ð°Ñ Ð´Ð° ÑÑŠÑтавите ÑпиÑък на хората, Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ Ñте били в близък контакт (на разÑтоÑние под 2 метра или при разговор лице в лице) в продължение на повече от 15 минути през поÑледните два дни, преди да Ñе разболеете. Може да използвате Ð’Ð°ÑˆÐ¸Ñ Ð´Ð½ÐµÐ²Ð½Ð¸Ðº на контактите – проÑто екÑпортирайте запиÑите и Ñлед това ги разпечатайте или ги изпратете по имейл."</item> <item>"МолÑ, обърнете оÑобено внимание на хората, които нÑма да бъдат уведомени директно чрез приложението, тъй като нÑмат Ñмартфон или не Ñа инÑталирали приложението."</item> - <item>"Може вÑе още да Ñте ноÑител на заразата дори ако вече не проÑвÑвате Ñимптоми и отново Ñе чувÑтвате добре."</item> + <item>"Може вÑе още да Ñте ноÑител на заразата дори ако вече не проÑвÑвате Ñимптоми и отново Ñе чувÑтвате добре. По тази причина, молÑ, не нарушавайте ÑƒÐºÐ°Ð·Ð°Ð½Ð¸Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´ на карантина."</item> </string-array> <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Готово"</string> @@ -1113,22 +1109,22 @@ <!-- XBUT: submission contact enter tan button --> <string name="submission_contact_button_enter">"Въведете ТÐРкод"</string> <!-- YTXT: Body text for step 1 of contact page --> - <string name="submission_contact_step_1_body">"Обадете Ñе на горещата Ð»Ð¸Ð½Ð¸Ñ Ð¸ поиÑкайте ТÐРкод:"</string> + <string name="submission_contact_step_1_body">"Обадете Ñе на националната гореща Ð»Ð¸Ð½Ð¸Ñ Ð¸ поиÑкайте ТÐРкод:"</string> <!-- XLNK: Button / hyperlink to phone call for TAN contact page --> <string name="submission_contact_number_display">"0800 7540002"</string> <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"РегиÑтрирайте теÑта, като въведете ТÐРкода в приложението."</string> + <string name="submission_contact_step_2_body">"РегиÑтрирайте теÑта Ñи, като въведете ТÐРкода в приложението."</string> <!-- YTXT: Body text for operating hours in contact page--> - <string name="submission_contact_operating_hours_body">"Езици: \nанглийÑки, немÑки, турÑки\n\nРаботно време:\nот понеделник до неделÑ: 24 чаÑа\n\nОбажданиÑта Ñа безплатни."</string> + <string name="submission_contact_operating_hours_body">"Езици: \nанглийÑки, немÑки, турÑки\n\nРаботно време:\n24/7\n\nОбажданиÑта Ñа безплатни."</string> <!-- YTXT: Body text for technical contact and hotline information page --> <string name="submission_contact_body_other">"Ðко имате въпроÑи, Ñвързани ÑÑŠÑ Ð·Ð´Ñ€Ð°Ð²ÐµÑ‚Ð¾, Ñе обадете на Ð»Ð¸Ñ‡Ð½Ð¸Ñ Ñи лекар или на горещата Ð»Ð¸Ð½Ð¸Ñ Ð½Ð° Ñлужбата за медицинÑка помощ на телефон 116 117."</string> <!-- XACT: Submission contact page title --> <string name="submission_contact_accessibility_title">"Обадете Ñе на горещата Ð»Ð¸Ð½Ð¸Ñ Ð¸ поиÑкайте ТÐРкод"</string> <!-- XACT: Content Description for submission contact step 1, number has to sync with the display number --> - <string name="submission_contact_step_1_content">"Ð’ първата Ñтъпка Ñе обаждате на горещата линиÑ, за да заÑвите ТÐРкод. ТелефонниÑÑ‚ номер е 0800 7540002. Работното време е от 8:00 до 22:00 ч. от понеделник до петък и от 10:00 до 22:00 ч в Ñъбота и Ð½ÐµÐ´ÐµÐ»Ñ . Обаждането е безплатно."</string> + <string name="submission_contact_step_1_content">"Ð’ първата Ñтъпка Ñе обаждате на горещата национална линиÑ, за да заÑвите ТÐРкод. ТелефонниÑÑ‚ номер е 0800 7540002. Работното време е от 8:00 до 22:00 ч. от понеделник до петък и от 10:00 до 22:00 ч в Ñъбота и неделÑ. Обаждането е безплатно."</string> <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"Във втората Ñтъпка региÑтрирате теÑта Ñи, като въвеждате ТÐРкода в приложението."</string> @@ -1164,7 +1160,7 @@ <!-- XHED: Page title for the various submission status fetch ready --> <string name="submission_status_card_title_ready">"Резултатът от теÑта Ви е готов"</string> <!-- XHED: Subtitle for the submission status card: invalid --> - <string name="submission_status_card_subtitle_invalid">"Ðевалиден теÑÑ‚"</string> + <string name="submission_status_card_subtitle_invalid">"Ðе може да бъде определен резултат от теÑта Ви"</string> <!-- XHED: Subtitle for the submission status card: negative --> <string name="submission_status_card_subtitle_negative">"Отрицателен резултат"</string> <!-- XHED: Subtitle for the submission status card: ready --> @@ -1295,7 +1291,7 @@ <!-- XHED: Title for submission statistics card --> <string name="statistics_card_submission_title">"Лица, изпратили предупреждениÑ"</string> <!-- XTXT: Text displayed in the bottom of submission statistics card--> - <string name="statistics_card_submission_bottom_text">"чрез приложението Corona-Warn-App"</string> + <string name="statistics_card_submission_bottom_text">"ОтноÑно приложението Corona-Warn-App"</string> <!-- XTXT: Timestamp refers to today's date in submission and infections cards --> <string name="statistics_primary_value_today">"ДнеÑ"</string> @@ -1333,8 +1329,8 @@ <!-- XHED: Explanation screen seven day r-value title --> <string name="statistics_explanation_seven_day_r_value_title">"7-дневно репродуктивно чиÑло R"</string> <!-- XTXT: Explanation screen seven day r-value text --> - <string name="statistics_explanation_seven_day_r_value_text">"Репродуктивното чиÑло R указва Ñредно колко души заразÑва едно заразено лице. Текущата ÑтойноÑÑ‚ взема предвид данните от поÑледните 5 дни. \n\nЗа повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð²Ð¸Ð¶Ñ‚Ðµ\nЧЗВ отноÑно ÑтатиÑтиката."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> + <string name="statistics_explanation_seven_day_r_value_text">"Репродуктивното чиÑло R указва Ñредно колко души заразÑва едно заразено лице. Текущата ÑтойноÑÑ‚ взема предвид данните от поÑледните 5 дни. \n\nПовече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в раздел ЧЗВ.\nЧЗВ отноÑно ÑтатиÑтиката."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> <string name="statistics_explanation_seven_day_r_link_label">"ЧЗВ отноÑно ÑтатиÑтиката."</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Легенда"</string> @@ -1355,7 +1351,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"ТенденциÑ"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"ПоÑоката на Ñтрелката указва дали тенденциÑта е към нараÑтване, намалÑване или Ñтабилизиране, Ñ‚.е. отклонението е по-малко от 1\% ÑпрÑмо Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð´ÐµÐ½ или от 5\% ÑпрÑмо предишната Ñедмица. Цветът указва тенденциÑта като положителна (зелено), отрицателна (червено) или неутрална (Ñиво). За определÑне на Ñ‚eнденциÑта ÑтойноÑтта от Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð´ÐµÐ½ Ñе ÑравнÑва Ñ Ñ‚Ð°Ð·Ð¸ от преди два дни, а при 7-дневната Ñ‚ÐµÐ½Ð´ÐµÐ½Ñ†Ð¸Ñ Ñредната ÑтойноÑÑ‚ за поÑледните 7 дни Ñе ÑравнÑва ÑÑŠÑ Ñъответната ÑтойноÑÑ‚ за Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ 7-дневен период."</string> + <string name="statistics_explanation_trend_text">"ПоÑоката на Ñтрелката указва дали тенденциÑта е към нараÑтване, намалÑване или Ñтабилизиране, Ñ‚.е. отклонението е по-малко от 1%% ÑпрÑмо Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð´ÐµÐ½ или от 5%% ÑпрÑмо предишната Ñедмица. Цветът указва тенденциÑта като положителна (зелено), отрицателна (червено) или неутрална (Ñиво). За определÑне на тенденциÑта ÑтойноÑтта от Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð´ÐµÐ½ Ñе ÑравнÑва Ñ Ñ‚Ð°Ð·Ð¸ от преди два дни, а при 7-дневната Ñ‚ÐµÐ½Ð´ÐµÐ½Ñ†Ð¸Ñ Ñредната ÑтойноÑÑ‚ за поÑледните 7 дни Ñе ÑравнÑва ÑÑŠÑ Ñъответната ÑтойноÑÑ‚ за Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ 7-дневен период."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"Възможни Ñа Ñледните тенденции:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1371,6 +1367,11 @@ <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"ÐбÑтрактно изображение на Ñмартфон Ñ 4 контейнера за данни"</string> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"Плочка “СтатиÑтикаâ€"</string> + <!-- XTXT: Statistics Card Navigation Announcement --> + <string name="accessibility_statistics_card_navigation_information">"Плъзнете хоризонтално между плочките, за да покажете още ÑтатиÑтичеÑки данни"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1406,6 +1407,9 @@ <string name="errors_risk_detection_limit_reached_title">"Лимитът вече е доÑтигнат"</string> <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_description">"Ðе може да правите повече проверки за излагане на риÑк до ÐºÑ€Ð°Ñ Ð½Ð° денÑ, тъй като Ñте доÑтигнали макÑÐ¸Ð¼Ð°Ð»Ð½Ð¸Ñ Ð±Ñ€Ð¾Ð¹ проверки, определен от вашата операционна ÑиÑтема. МолÑ, проверете ÑтатуÑа Ñи на риÑк утре."</string> + <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> + <string name="errors_ssl_certificate_deactivated">"МолÑ, активирайте на вашето уÑтройÑтво Ñертификата за ÑигурноÑÑ‚ T-TeleSec GlobalRoot Class 2, издаден от T-Systems Enterprise Services GmbH. Повече Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да намерите в ЧЗВ на Ð°Ð´Ñ€ÐµÑ https://coronawarn.app/en/ under “CAUSE: 2001â€."</string> + <!-- #################################### Generic Error Messages ###################################### --> diff --git a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml index c62e2ff8a0d64ebd1d89f58747a2f4b9b4eccfb6..384424dbeb13935a2d6caf09ad6c169f2a8ea59f 100644 --- a/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/contact_diary_strings.xml @@ -23,9 +23,9 @@ <!-- XTXT: Body for the contact diary card displayed in the homescreen --> <string name="contact_diary_homescreen_card_body">"Merken Sie sich, wo Sie waren und wen Sie getroffen haben."</string> <!-- XBUT: Button text for the contact diary card button in the homescreen --> - <string name="contact_diary_homescreen_card_button_text">"Tagebuch Führen"</string> + <string name="contact_diary_homescreen_card_button_text">"Tagebuch öffnen"</string> <!-- XBUT: Button text for the contact diary card button in the homescreen --> - <string name="contact_diary_onboarding_button_text">"Tagebuch Führen"</string> + <string name="contact_diary_onboarding_button_text">"Tagebuch öffnen"</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_information_card_title">"Behalten Sie den Überblick."</string> @@ -133,4 +133,5 @@ <string name="accessibility_action_deselect">"Auswahl aufheben"</string> <!-- XTXT: Edit (description for screen readers) --> <string name="accessibility_edit">"Bearbeiten"</string> + </resources> diff --git a/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml b/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml index 4588a8b27166fa0e89831c8a66cb874a8e7821fa..6737e65e8a9ab3634b993bf2b1a59d10c2a8a38e 100644 --- a/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/release_info_strings.xml @@ -5,20 +5,34 @@ ###################################### --> <!-- XHED: Title for the release info screen --> - <string name="release_info_header">"Neue Funktionen"</string> + <string name="release_info_header">Neue Funktionen</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">"Mit diesem Update stellen wir Ihnen neben Fehlerbehebungen auch neue und erweiterte Funktionen zur Verfügung."</string> - <!-- XTXT: Encounter history title for the release info screen --> - <string name="release_info_1_12_encounter_history_title">"Begegnungshistorie"</string> - <!-- XTXT: Encounter history body for the release info screen --> - <string name="release_info_1_12_encounter_history_body">"Sie können in Ihrem Kontakt-Tagebuch sehen, an welchen Tagen Sie ein niedriges oder erhöhtes Risiko aufgrund der von der App ausgewerteten Begegnungen hatten. Sie können damit Personen warnen, die Sie an diesen Tagen begleitet haben, wenn diese die Corona-Warn-App nicht nutzen. Diese Personen waren möglicherweise einem vergleichbaren Risiko ausgesetzt. Die Personen und Orte in Ihrem Kontakt-Tagebuch müssen nicht in Zusammenhang mit den angezeigten Risiko-Begegnungen stehen."</string> - <!-- XTXT: Statistics title for the release info screen --> - <string name="release_info_1_12_statistics_title">"Statistiken"</string> - <!-- XTXT: Statistics body for the release info screen --> - <string name="release_info_1_12_statistics_body">"Sie bekommen einen Überblick mit aktuellen Kennzahlen zum Infektionsgeschehen in Deutschland."</string> + <string name="release_info_version_body">Mit diesem Update stellen wir Ihnen neben Fehlerbehebungen auch neue und erweiterte Funktionen zur Verfügung.</string> <!-- XBUT: Continue button for the release info screen --> - <string name="release_info_continue_button">"Weiter"</string> + <string name="release_info_continue_button">Weiter</string> + <!-- XTXT: New release info footer --> + <string name="new_release_bottom">Änderungen zum Release finden Sie in den App-Einstellungen unter dem Menüpunkt „Neue Funktionenâ€.</string> + + <!-- XHED: Titles for the release info screen bullet points --> + <string-array name="new_release_title"> + <item>Link auf RKI-Umfrage</item> + <item>Einwilligung zur Datenspende (optional)</item> + <item>Anpassungen der Risiko-Karten</item> + <item>Fortführung der Risiko-Ermittlung nach Teilung der Zufalls-IDs</item> + <item>Einführung von Registerkarten</item> + <item>Mehr Informationen zum Testablauf</item> + </string-array> + + <!-- XTXT: Text bodies for the release info screen bullet points --> + <string-array name="new_release_body"> + <item>Wenn Sie ein erhöhtes Risiko haben, können Sie aus der App heraus eine Umfrage des Robert-Koch-Instituts aufrufen und daran teilnehmen.</item> + <item>Sie haben nun die Möglichkeit, Ihre Nutzungsdaten zur Verfügung zu stellen und uns somit bei der Verbesserung der App zu unterstützen.</item> + <item>Die Texte auf den Risiko-Karten wurden leicht angepasst.</item> + <item>Nachdem Sie Ihre verschlüsselten Zufalls-IDs geteilt haben, können Sie entscheiden, ob und wann Sie die Risiko-Ermittlung wieder einschalten.</item> + <item>Es gibt nun eine eigene Registerkarte für das Kontakt-Tagebuch. Somit können Sie schneller auf Ihr Tagebuch zugreifen und vom Kontakt-Tagebuch wieder zurück auf die Startseite der App wechseln.</item> + <item>Die App enthält nun detaillierte Informationen zum Testablauf.</item> + </string-array> </resources> diff --git a/Corona-Warn-App/src/main/res/values-de/strings.xml b/Corona-Warn-App/src/main/res/values-de/strings.xml index 79ddeb355cc5799d637e81458c7502a786c5be7e..27b291fefa2c74ff0f5ba33a1dc6a2cf94cc358b 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -151,16 +151,38 @@ <string name="risk_card_low_risk_no_encounters_body">Keine Risiko-Begegnungen</string> <!-- XTXT: risk card - Low risk state - Days with low risk encounters --> <plurals name="risk_card_low_risk_encounter_days_body"> + <item quantity="one">"Begegnungen mit niedrigem Risiko an %1$d Tag"</item> + <item quantity="other">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> + <item quantity="zero">"Keine Risiko-Begegnungen"</item> + <item quantity="two">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> + <item quantity="few">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> + <item quantity="many">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> + </plurals> + <!-- XACT: risk card - Low risk state - Days with low risk encounters description --> + <plurals name="risk_card_low_risk_encounter_days_body_description"> <item quantity="one">"Begegnungen mit niedrigem Risiko an einem Tag"</item> <item quantity="other">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> - <item quantity="zero">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> + <item quantity="zero">"Keine Risiko-Begegnungen"</item> <item quantity="two">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> <item quantity="few">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> <item quantity="many">"Begegnungen mit niedrigem Risiko an %1$d Tagen"</item> </plurals> + <!-- XTXT: risk card - Low risk state - Most recent date with low risk and single day of encounters --> + <string name="risk_card_low_risk_most_recent_body_encounter_on_single_day">"Am %1$s"</string> + <!-- XTXT: risk card - Low risk state - Most recent date with low risk and more than one day of encounters --> + <string name="risk_card_low_risk_most_recent_body_encounters_on_more_than_one_day">"Zuletzt am %1$s"</string> <!-- XTXT: risk card - High risk state - Days with high risk encounters --> <plurals name="risk_card_high_risk_encounter_days_body"> + <item quantity="one">"Begegnungen an %1$d Tag mit erhöhtem Risiko"</item> + <item quantity="other">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> + <item quantity="zero">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> + <item quantity="two">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> + <item quantity="few">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> + <item quantity="many">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> + </plurals> + <!-- XACT: risk card - High risk state - Days with high risk encounters description --> + <plurals name="risk_card_high_risk_encounter_days_body_description"> <item quantity="one">"Begegnungen an einem Tag mit erhöhtem Risiko"</item> <item quantity="other">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> <item quantity="zero">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> @@ -168,8 +190,12 @@ <item quantity="few">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> <item quantity="many">"Begegnungen an %1$d Tagen mit erhöhtem Risiko"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"Zuletzt am %1$s"</string> + + <!-- XTXT: risk card - High risk state - Most recent date with high risk and single day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounter_on_single_day">"Am %1$s"</string> + + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"Zuletzt am %1$s"</string> <!-- #################################### Risk Card - Progress @@ -295,6 +321,14 @@ <string name="risk_details_behavior_body_wear_mask">"Tragen Sie einen Mund-Nasen-Schutz bei Begegnungen mit anderen Personen."</string> <!-- XMSG: risk details - stay 1,5 away, something like a bullet point --> <string name="risk_details_behavior_body_stay_away">"Halten Sie mindestens 1,5 Meter Abstand zu anderen Personen."</string> + + <!-- XMSG: risk details - link to faq, something like a bullet point --> + <string name="risk_details_increased_risk_faq_link_text">"Falls Sie sich testen lassen, finden Sie weitere Informationen in der FAQ zum Testablauf."</string> + <!-- XTXT: Explanation screen increased risk level link label --> + <string name="risk_details_increased_risk_faq_link_label">"FAQ zum Testablauf"</string> + <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="risk_details_increased_risk_faq_url">"https://www.coronawarn.app/de/faq/#red_card_how_to_test"</string> + <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Niesen oder husten Sie in die Armbeuge oder in ein Taschentuch."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -869,6 +903,8 @@ <string name="submission_consent_main_third_point">"Ihre Identität bleibt geheim. Andere Nutzer erfahren nicht, wer sein Testergebnis geteilt hat."</string> <!-- YTXT: Body for consent main section fourth point --> <string name="submission_consent_main_fourth_point">"Sie können Ihr Einverständnis abgeben, wenn Sie mindestens 16 Jahre alt sind."</string> + <!-- YTXT: Content Description for the illustration --> + <string name="submission_consent_main_illustration_description">"Eine Person hält ein Smartphone. Ein QR-Code auf einem Test symbolisiert den zu scannenden Code."</string> <!-- Submission Test Result --> <!-- XHED: Page headline for test result --> @@ -1331,8 +1367,8 @@ <string name="statistics_explanation_seven_day_r_value_title">"7-Tage-R-Wert"</string> <!-- XTXT: Explanation screen seven day r-value text --> <string name="statistics_explanation_seven_day_r_value_text">"Die Reproduktionszahl R gibt an, wie viele Personen im Durchschnitt von einer infizierten Person angesteckt wurden. Der aktuelle Wert berücksichtigt Daten bis vor 5 Tagen.\n\nWeitere Informationen finden Sie in den FAQ:\nFAQ zu Statistiken."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> - <string name="statistics_explanation_seven_day_r_link_label">"FAQ zu den Statistiken."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> + <string name="statistics_explanation_seven_day_r_link_label">"FAQ zu Statistiken"</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Legende"</string> <!-- XHED: Explanation screen period title --> @@ -1352,7 +1388,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"Trend"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"Die Pfeilrichtung zeigt an, ob der Trend nach oben oder nach unten geht oder relativ stabil ist, d.h. eine Abweichung von weniger als 1\% im Vortagesvergleich bzw. 5\% im Vorwochenvergleich aufweist. Die Farbe bewertet diesen Trend als positiv (grün), negativ (rot) oder neutral (grau). Der Trend vergleicht den Wert vom Vortag mit dem Wert von vor zwei Tagen bzw. für die 7-Tage-Trends den Mittelwert der letzten 7 Tage mit dem der vorausgegangenen 7 Tage."</string> + <string name="statistics_explanation_trend_text">"Die Pfeilrichtung zeigt an, ob der Trend nach oben oder nach unten geht oder relativ stabil ist, d.h. eine Abweichung von weniger als 1%% im Vortagesvergleich bzw. 5%% im Vorwochenvergleich aufweist. Die Farbe bewertet diesen Trend als positiv (grün), negativ (rot) oder neutral (grau). Der Trend vergleicht den Wert vom Vortag mit dem Wert von vor zwei Tagen bzw. für die 7-Tage-Trends den Mittelwert der letzten 7 Tage mit dem der vorausgegangenen 7 Tage."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"Folgende Trends können angezeigt werden:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1368,6 +1404,11 @@ <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"Abstrakte Darstellung eines Smartphones mit vier Platzhaltern für Informationen"</string> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"Statistikkarte"</string> + <!-- XTXT: Statistics Card Navigation Announcement --> + <string name="accessibility_statistics_card_navigation_information">"Wische horizontal zwischen den Karten für weitere Statistiken"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1610,10 +1651,35 @@ <!-- XTXT: Statistics trend stable (Accessibilty) --> <string name="statistics_trend_stable">Trend stabil</string> + <!-- XHED: Title for BottomNav main screen title --> + <string name="bottom_nav_home_title">Startseite</string> + <!-- XHED: Title for BottomNav diary screen title --> + <string name="bottom_nav_diary_title">Tagebuch</string> + <!-- #################################### Data donation ###################################### --> + <!-- XHED: Title for the access survey card displayed in the risk details screen --> + <string name="datadonation_details_access_survey_card_title">"Befragung zur Corona-Warn-App"</string> + <!-- XHED: Text for the access survey card displayed in the risk details screen --> + <string name="datadonation_details_access_survey_card_content">"Helfen Sie uns, die App zu verbessern, indem Sie einige einfache Fragen beantworten."</string> + <!-- XHED: Text for the access survey card displayed in the risk details screen --> + <string name="datadonation_details_access_survey_card_button_text">"Zur Befragung"</string> + + <!-- XHED: Title for the access survey button displayed at the bottom of the screen --> + <string name="datadonation_details_survey_consent_button_title">"Weiter"</string> + <!-- XHED: Text for the access survey title displayed at the top of survey consent screen --> + <string name="datadonation_details_survey_consent_top_title">"Befragung zur Verbesserung der Corona-Warn-App"</string> + <!-- XHED: Text for the access survey body displayed under the title of survey consent screen --> + <string name="datadonation_details_survey_consent_top_body">"Mit Ihrer Teilnahme helfen Sie uns zu verstehen, wie sich Warnungen über die App auf das Verhalten von Personen mit erhöhtem Risiko auswirken."</string> + <!-- XHED: Text for the consent part title displayed over the button in the survey consent screen --> + <string name="datadonation_details_survey_consent_info_card_title">"Ihr Einverständnis"</string> + <!-- XHED: Text for the consent part body displayed over the button in the survey consent screen --> + <string name="datadonation_details_survey_consent_info_card_body">"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut t amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."</string> + <!-- #################################### + Analytics + ###################################### --> <!-- XHED: Analytics voluntary user input, age group toolbar title --> <string name="analytics_userinput_agegroup_title">Ihr Alter</string> <!-- XTXT: Analytics voluntary user input, age group: AGE_GROUP_UNSPECIFIED --> 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 94c4c3a7620bc0f25a6e5b79d422560146c240c7..1d8a69496707bc2875e8317db803052e40e16d74 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"No people defined yet"</string> <string name="contact_diary_person_list_no_items_subtitle">"Create a person and add them to your contact journal."</string> <string name="contact_diary_person_bottom_sheet_title">"Person"</string> - <string name="contact_diary_person_bottom_sheet_text_input_hint">"First name, last name"</string> + <string name="contact_diary_person_bottom_sheet_text_input_hint">"Name"</string> <string name="contact_diary_person_bottom_sheet_save_button">"Save"</string> <string name="contact_diary_location_bottom_sheet_title">"Place"</string> <string name="contact_diary_location_bottom_sheet_text_input_hint">"Description"</string> @@ -40,6 +40,8 @@ <string name="contact_diary_onboarding_functionality_fourth_section">"You can remove entries for people and places from your journal at any time. Journal entries will be deleted automatically after 16 days."</string> <!-- XTXT: Contact diary onboarding screen fifth functionality --> <string name="contact_diary_onboarding_functionality_fifth_section">"You can export your contact journal in plain text format and then print out the entries, edit them, or provide them to your public health authority as needed."</string> + <!-- XTXT: Contact diary onboarding screen sixth functionality --> + <string name="contact_diary_onboarding_functionality_sixth_section">"The indicated exposures are not necessarily related to the people and places you have recorded. Please don’t draw any wrong conclusions."</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_title">"Contact Journal"</string> <!-- XTXT: Body for legal information of the contact diary onboarding screen --> @@ -61,6 +63,17 @@ <!-- XTXT: Header for contact diary overview screen --> <string name="contact_diary_overview_header">"Start Page"</string> + + <!-- XTXT: Title for contact diary overview screen high risk information --> + <string name="contact_diary_high_risk_title">"Increased Risk"</string> + <!-- XTXT: Title for contact diary overview screen low risk information --> + <string name="contact_diary_low_risk_title">"Low Risk"</string> + <!-- XTXT: Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body">"based on the encounters evaluated by the app."</string> + <!-- XTXT: Extended Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body_extended">"based on the encounters evaluated by the app. They are not necessarily related to the people and places you have recorded."</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> <!-- XTXT: content description of contact journal header image on onboarding screen --> 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 new file mode 100644 index 0000000000000000000000000000000000000000..c045c2a4da86ace5ca14d9d1b87527e1c299e3d4 --- /dev/null +++ b/Corona-Warn-App/src/main/res/values-en/release_info_strings.xml @@ -0,0 +1,15 @@ +<?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"> + <!-- #################################### + Release Info Screen 1.12 + ###################################### --> + + <!-- 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> + <!-- 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 --> + <string name="release_info_continue_button">"Next"</string> + +</resources> \ No newline at end of file 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 c539e7baf3819b019d1a7548079907753897ece0..84889ee4ef575c32210bde85d835116d72f6e8c1 100644 --- a/Corona-Warn-App/src/main/res/values-en/strings.xml +++ b/Corona-Warn-App/src/main/res/values-en/strings.xml @@ -22,12 +22,6 @@ <!-- NOTR --> <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> <!-- NOTR --> - <string name="preference_background_job_allowed"><xliff:g id="preference">"preference_background_job_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_mobile_data_allowed"><xliff:g id="preference">"preference_mobile_data_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_registration_token"><xliff:g id="preference">"preference_registration_token"</xliff:g></string> - <!-- NOTR --> <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> <!-- NOTR --> <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> @@ -173,8 +167,9 @@ <item quantity="few">"Exposures on %1$d days with increased risk"</item> <item quantity="many">"Exposures on %1$d days with increased risk"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"Most recently on %1$s"</string> + + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"Most recently on %1$s"</string> <!-- #################################### Risk Card - Progress @@ -230,11 +225,11 @@ <!-- XHED: App overview subtitle for tracing explanation--> <string name="main_overview_subtitle_tracing">"Exposure Logging"</string> <!-- YTXT: App overview body text about tracing --> - <string name="main_overview_body_tracing">"Exposure logging is one of the three central features of the app. When you activate it, encounters with people’s smartphones are logged. You don’t have to do anything else."</string> + <string name="main_overview_body_tracing">"Exposure logging is one of the three central features of the app. When you activate it, encounters with people’s smartphones are logged automatically."</string> <!-- XHED: App overview subtitle for risk explanation --> <string name="main_overview_subtitle_risk">"Risk of Infection"</string> <!-- YTXT: App overview body text about risk levels --> - <string name="main_overview_body_risk">"If you have had contact within the last 14 days with a person who was diagnosed with coronavirus, the app calculates your personal risk of infection. It does this by measuring duration and proximity of the exposure."</string> + <string name="main_overview_body_risk">"When exposure logging is active, the app calculates your personal risk of infection. It does this by measuring duration and proximity of your exposure to people who have been diagnosed with coronavirus over the last 14 days."</string> <!-- XHED: App overview subtitle for risk level list --> <string name="main_overview_subtitle_risk_levels">"The following risk status can be shown:"</string> <!-- XTXT: App overview increased risk level --> @@ -246,7 +241,7 @@ <!-- XHED: App overview subtitle for test procedure explanation --> <string name="main_overview_headline_test">"Notifying Other Users"</string> <!-- YTXT: App overview body text about rest procedure --> - <string name="main_overview_body_test">"Another central feature is registering your test and retrieving the result. If you are diagnosed with coronavirus, you can notify others and break the chain of infection."</string> + <string name="main_overview_body_test">"You can register your test and retrieve your test result in the app. If you are diagnosed with coronavirus, you can notify others and break the chain of infection."</string> <!-- XHED: App overview headline for glossary --> <string name="main_overview_headline_glossary">"Definition of Terms:"</string> <!-- XHED: App overview subtitle for glossary key storage --> @@ -256,7 +251,7 @@ <!-- XHED: App overview subtitle for glossary risk calculation --> <string name="main_overview_subtitle_glossary_calculation">"Exposure Check"</string> <!-- YTXT: App overview body for glossary risk calculation --> - <string name="main_overview_body_glossary_calculation">"Exposure log data is retrieved and synchronized with reported infections of other users. Your risk is checked automatically several times per day."</string> + <string name="main_overview_body_glossary_calculation">"Exposure log data is retrieved and synchronized with risk notifications from other users. Your risk is checked automatically several times per day."</string> <!-- XHED: App overview subtitle for glossary contact --> <string name="main_overview_subtitle_glossary_contact">"Exposure Risk"</string> <!-- YTXT: App overview body for glossary contact --> @@ -328,6 +323,8 @@ <string name="risk_details_information_body_low_risk">"You have a low risk of infection because no exposure to people later diagnosed with COVID-19 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> + <!-- 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 last exposed on %1$s 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 --> <plurals name="risk_details_information_body_increased_risk"> <item quantity="one">"You have an increased risk of infection because you were last exposed %1$s days ago over a longer period of time and at close proximity to at least one person diagnosed with coronavirus."</item> @@ -352,9 +349,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> - <!-- 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> - <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Your Risk Status"</string> <!-- YTXT: risk details - deadman notification text --> @@ -425,7 +419,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"To identify whether you are at risk of infection, you must activate exposure logging."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"Exposure logging works by your Android smartphone receiving, via Bluetooth, encrypted random IDs of other users and passing your own random IDs to their smartphones. Exposure logging can be deactivated at any time. "</string> + <string name="onboarding_tracing_body">"Exposure logging works by your Android smartphone receiving, via Bluetooth, encrypted random IDs of other app users and passing your own random IDs to their smartphones. Exposure logging can be deactivated at any time."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"The encrypted random IDs only pass information about date, duration, and proximity (calculated from signal strength) to other people. Individuals cannot be identified based on the random IDs."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -475,9 +469,9 @@ <!-- XACT: Onboarding (test) page title --> <string name="onboarding_test_accessibility_title">"Onboarding page 5 of 6: If You Are Diagnosed with Coronavirus"</string> <!-- XHED: onboarding(test) - about positive tests --> - <string name="onboarding_test_headline">"If you are diagnosed with coronavirus…"</string> + <string name="onboarding_test_headline">"If you are diagnosed with coronavirus"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> - <string name="onboarding_test_subtitle">"… please report this in the Corona-Warn-App. Sharing your test results is voluntary and secure. Please do this for the sake of everyone’s health."</string> + <string name="onboarding_test_subtitle">"If you receive a positive test result, please report this in the app. Sharing your test results is voluntary and secure. Please do this for the sake of everyone’s health."</string> <!-- YTXT: onboarding(test) - explain test --> <string name="onboarding_test_body">"Your notification is encrypted securely and processed on a secure server. People whose encrypted random IDs your smartphone has collected will now receive a warning along with information about what they should now do."</string> <!-- XACT: onboarding(test) - illustraction description, header image --> @@ -527,7 +521,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"Location services deactivated"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"You need to enable the exposure logging feature so that the app can determine whether you are at risk of infection. The exposure logging feature works transnationally, meaning any possible exposure involving users is also detected by other official coronavirus apps.\n\nThe exposure logging feature works by your Android smartphone receiving encrypted random IDs from other users via Bluetooth and passing your own random IDs to their smartphones. Every day, the app downloads lists containing the random IDs – along with any voluntary information about the onset of symptoms – of all users who have tested positive for the virus and voluntarily shared this information (specifically: their random IDs) via their app. This list is then compared with the random IDs of other users you have encountered that have been recorded by your Android smartphone, in order to calculate the likelihood that you have also been infected and to warn you if necessary.\n\nYou can use the toggle switch to disable exposure logging at any time.\n\nThe app never collects personal data such as your name, address or location, nor is this information passed on to other users. It is not possible to use random IDs to draw conclusions about individual persons."</string> + <string name="settings_tracing_body_text">"You need to enable the exposure logging feature so that the app can determine whether you are at risk of infection. The exposure logging feature works transnationally, meaning any possible exposure involving users is also detected by other official coronavirus apps.\n\nThe exposure logging feature works by your Android smartphone receiving encrypted random IDs from other app users via Bluetooth and passing your own random IDs to their smartphones. Every day, the app downloads lists containing the random IDs – along with any voluntary information about the onset of symptoms – of all users who have tested positive for the virus and voluntarily shared this information (specifically: their random IDs) via their app. This list is then compared with the random IDs of other users you have encountered that have been recorded by your Android smartphone, in order to calculate the likelihood that you have also been infected and to warn you if necessary.\n\nYou can use the toggle switch to disable exposure logging at any time.\n\nThe app never collects personal data such as your name, address or location, nor is this information passed on to other users. It is not possible to use random IDs to draw conclusions about individual persons."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Active"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -555,7 +549,7 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <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 exposures. Please turn on WIFI or mobile data in your device settings."</string> + <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> <!-- 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 --> @@ -714,7 +708,7 @@ <!-- XHED: Headline for legal information page, publisher section --> <string name="information_legal_headline_publisher">"Published by"</string> <!-- YTXT: subtitle for legal information page, publisher section --> - <string name="information_legal_subtitle_publisher">"(responsible in accordance with § 5, para. 1 TMG, § 55 para. 1 RStV, DS-GVO, BDSG)"</string> + <string name="information_legal_subtitle_publisher">"(responsible in accordance with § 5 (1) TMG, § 18 (1) MStV)"</string> <!-- YTXT: body for legal information page, publisher section --> <string name="information_legal_body_publisher">"Robert Koch Institute"<xliff:g id="line_break">"\n"</xliff:g>"Nordufer 20"<xliff:g id="line_break">"\n"</xliff:g>"13353 Berlin"<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"represented by its president"</string> <!-- XHED: Headline for legal information page, contact section --> @@ -768,7 +762,7 @@ <!-- XHED: Dialog title for generic web request error --> <string name="submission_error_dialog_web_generic_error_title">"Error"</string> <!-- XMSG: Dialog body for generic web request network error with status code --> - <string name="submission_error_dialog_web_generic_network_error_body">"Connection could not be established (%1$d). Please try again."</string> + <string name="submission_error_dialog_web_generic_network_error_body">"Your Internet connection may have been lost. Please ensure that you are connected to the Internet."</string> <!-- XMSG: Dialog body for generic web request error without status code --> <string name="submission_error_dialog_web_generic_error_body">"Connection could not be established. Please try again."</string> <!-- XBUT: Positive button for generic web request error --> @@ -902,7 +896,7 @@ <!-- XHED: Page headline for other warnings screen --> <string name="submission_test_result_positive_steps_warning_others_heading">"Warn Others"</string> <!-- YTXT: Body text for for other warnings screen--> - <string name="submission_test_result_positive_steps_warning_others_body">"Share your random IDs and warn others.\nHelp determine the risk of infection for others more accurately by also indicating when you first noticed any coronavirus symptoms."</string> + <string name="submission_test_result_positive_steps_warning_others_body">"Please share your random IDs and warn others.\nHelp determine the risk of infection for others more accurately by also indicating when you first noticed any coronavirus symptoms."</string> <!-- XBUT: positive test result : continue button --> <string name="submission_test_result_positive_continue_button">"Next"</string> <!-- XBUT: positive test result : continue button with symptoms--> @@ -947,7 +941,7 @@ <!-- XHED: Page title for TAN submission pge --> <string name="submission_tan_title">"Enter TAN"</string> <!-- YTXT: Body text for the tan submission page --> - <string name="submission_tan_body">"Please enter the 10-digit TAN that you were given."</string> + <string name="submission_tan_body">"Enter the 10-digit TAN that you were given."</string> <!-- XBUT: Submit TAN button --> <string name="submission_tan_button_text">"Next"</string> <!-- XACT: Submission Tan page title --> @@ -960,6 +954,9 @@ <!-- Submission Intro --> <!-- XBUT: Submission introduction next button--> <string name="submission_intro_button_next">"Next"</string> + <!-- 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 --> @@ -1047,10 +1044,10 @@ <string name="submission_done_further_info_title">"Other information:"</string> <!-- YTXT: submission done further info bullet points --> <string-array name="submission_done_further_info_bullet_points"> - <item>"Your quarantine period is usually 14 days. Please observe your symptoms and monitor how they develop."</item> - <item>"You will be asked by the public health authority to create a list of people you have had contact with. This should include all people with whom you have had close contact with (less than 2 meters, face-to-face conversation) for over 15 minutes in the two days before you developed symptoms. You can use your contact journal for this: simply export the entries and then print them out or send them via e-mail."</item> + <item>"Your quarantine period is usually 14 days. Please monitor how your symptoms develop closely."</item> + <item>"You will be asked by your public health authority to create a list of people you have had contact with. This should include all people with whom you have had close contact with (less than 2 meters, face-to-face conversation) for over 15 minutes in the two days before you became ill. You can use your contact journal for this: simply export the entries and then print them out or send them via e-mail."</item> <item>"Please particularly consider people who will not be notified directly by the app since they don’t own a smartphone, or haven’t installed the app."</item> - <item>"Even when you no longer have any symptoms and you feel well again, you could still be infectious."</item> + <item>"Even when you no longer have any symptoms and you feel well again, you could still be infectious, so please follow the designated quarantine period."</item> </string-array> <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Done"</string> @@ -1119,16 +1116,16 @@ <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"Register the test by entering the TAN in the app."</string> + <string name="submission_contact_step_2_body">"Register your test by entering the TAN in the app."</string> <!-- YTXT: Body text for operating hours in contact page--> - <string name="submission_contact_operating_hours_body">"Languages: \nEnglish, German, Turkish\n\nBusiness hours:\nMonday to Sunday: 24 hours\n\nThe call is free of charge."</string> + <string name="submission_contact_operating_hours_body">"Languages: \nEnglish, German, Turkish\n\nBusiness hours:\n24/7\n\nThe call is free of charge."</string> <!-- YTXT: Body text for technical contact and hotline information page --> <string name="submission_contact_body_other">"If you have any health-related questions, please contact your general practitioner or the medical emergency service hotline, telephone: 116 117."</string> <!-- XACT: Submission contact page title --> <string name="submission_contact_accessibility_title">"Call the hotline and request a TAN"</string> <!-- XACT: Content Description for submission contact step 1, number has to sync with the display number --> - <string name="submission_contact_step_1_content">"In the first step, you call the nationwide hotline and request your TAN. You can reach the nationwide hotline on 0800 7540002. The business hours are Monday to Friday from 8 am to 10 pm and Saturday and Sunday from 10 am to 10 pm. The call is free of charge."</string> + <string name="submission_contact_step_1_content">"In the first step, you call the nationwide hotline and request your TAN. You can reach the hotline on 0800 7540002. The business hours are Monday to Friday from 8 am to 10 pm and Saturday and Sunday from 10 am to 10 pm. The call is free of charge."</string> <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"In the second step, you register your test with your TAN in the app."</string> @@ -1164,7 +1161,7 @@ <!-- XHED: Page title for the various submission status fetch ready --> <string name="submission_status_card_title_ready">"Your Test Result Is Available"</string> <!-- XHED: Subtitle for the submission status card: invalid --> - <string name="submission_status_card_subtitle_invalid">"Invalid test"</string> + <string name="submission_status_card_subtitle_invalid">"Your test cannot be evaluated"</string> <!-- XHED: Subtitle for the submission status card: negative --> <string name="submission_status_card_subtitle_negative">"Negative Diagnosis"</string> <!-- XHED: Subtitle for the submission status card: ready --> @@ -1295,7 +1292,7 @@ <!-- XHED: Title for submission statistics card --> <string name="statistics_card_submission_title">"Warnings by App Users"</string> <!-- XTXT: Text displayed in the bottom of submission statistics card--> - <string name="statistics_card_submission_bottom_text">"through the Corona-Warn-App"</string> + <string name="statistics_card_submission_bottom_text">"About Corona-Warn-App"</string> <!-- XTXT: Timestamp refers to today's date in submission and infections cards --> <string name="statistics_primary_value_today">"Today"</string> @@ -1333,8 +1330,8 @@ <!-- XHED: Explanation screen seven day r-value title --> <string name="statistics_explanation_seven_day_r_value_title">"7-Day R Value"</string> <!-- XTXT: Explanation screen seven day r-value text --> - <string name="statistics_explanation_seven_day_r_value_text">"The reproduction figure, R, indicates how many people an average infected person has infected further. The current value takes data up to the last 5 days into account.\n\nFor more information, refer to the\nFAQ for the statistics."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> + <string name="statistics_explanation_seven_day_r_value_text">"The reproduction figure, R, indicates how many people an average infected person has infected further. The current value takes data up to the last 5 days into account.\n\nFor more information, refer to the FAQ:\nFAQ for the statistics."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> <string name="statistics_explanation_seven_day_r_link_label">"FAQ for the statistics."</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Legend"</string> @@ -1355,7 +1352,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"Trend"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"The arrow direction indicates whether the trend is increasing, decreasing, or remaining steady – that is, demonstrates a deviation of less than 1\% compared to the previous day or 5\% compared to the previous week. The color indicates this trend as positive (green), negative (red), or neutral (gray). The trend compares the value from the previous day with the value from two days ago or, for the 7-day trends, the average value from the last 7 days with the average value from the 7 days prior to that."</string> + <string name="statistics_explanation_trend_text">"The arrow direction indicates whether the trend is increasing, decreasing, or remaining steady – that is, demonstrates a deviation of less than 1%% compared to the previous day or 5%% compared to the previous week. The color indicates this trend as positive (green), negative (red), or neutral (gray). The trend compares the value from the previous day with the value from two days ago or, for the 7-day trends, the average value from the last 7 days with the average value from the 7 days prior to that."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"The following trends can be shown:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1371,6 +1368,11 @@ <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"Abstract picture of a smartphone with four placeholders for information"</string> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"Statistics tile"</string> + <!-- 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 ###################################### --> @@ -1406,6 +1408,9 @@ <string name="errors_risk_detection_limit_reached_title">"Limit already reached"</string> <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_description">"No more exposure checks possible today, as you have reached the maximum number of checks per day defined by your operating system. Please check your risk status again tomorrow."</string> + <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> + <string name="errors_ssl_certificate_deactivated">"Please activate the SYSTEM security certificate T-Systems Enterprise Services GmbH, T-TeleSec GlobalRoot Class 2 on your smartphone. For more information, refer to the FAQs on https://coronawarn.app/en/ under “CAUSE: 2001â€."</string> + <!-- #################################### Generic Error Messages ###################################### --> 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 91371b37d2c78106bb39d1635abc64f2828344a6..7df96b0c925ecee27ba0adbe6c37621ac2dda3b3 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Nie zdefiniowano jeszcze żadnych osób"</string> <string name="contact_diary_person_list_no_items_subtitle">"Utwórz osobÄ™ i dodaj jÄ… do dziennika kontaktów."</string> <string name="contact_diary_person_bottom_sheet_title">"Osoba"</string> - <string name="contact_diary_person_bottom_sheet_text_input_hint">"ImiÄ™, nazwisko"</string> + <string name="contact_diary_person_bottom_sheet_text_input_hint">"ImiÄ™ i nazwisko"</string> <string name="contact_diary_person_bottom_sheet_save_button">"Zapisz"</string> <string name="contact_diary_location_bottom_sheet_title">"Miejsce"</string> <string name="contact_diary_location_bottom_sheet_text_input_hint">"Opis"</string> @@ -40,6 +40,8 @@ <string name="contact_diary_onboarding_functionality_fourth_section">"Możesz w dowolnym momencie usunąć wpisy dotyczÄ…ce osób i miejsc ze swojego dziennika. Wpisy w dzienniku bÄ™dÄ… automatycznie usuwane po 16 dniach."</string> <!-- XTXT: Contact diary onboarding screen fifth functionality --> <string name="contact_diary_onboarding_functionality_fifth_section">"Możesz wyeksportować swój dziennik kontaktów w formacie zwykÅ‚ego tekstu, a nastÄ™pnie wydrukować wpisy, edytować je lub w razie potrzeby przekazać do organu ds. zdrowia publicznego."</string> + <!-- XTXT: Contact diary onboarding screen sixth functionality --> + <string name="contact_diary_onboarding_functionality_sixth_section">"Wskazane narażenia niekoniecznie sÄ… zwiÄ…zane z zarejestrowanymi przez Ciebie osobami i miejscami. Nie wyciÄ…gaj błędnych wniosków."</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_title">"Dziennik kontaktów"</string> <!-- XTXT: Body for legal information of the contact diary onboarding screen --> @@ -61,6 +63,17 @@ <!-- XTXT: Header for contact diary overview screen --> <string name="contact_diary_overview_header">"Strona startowa"</string> + + <!-- XTXT: Title for contact diary overview screen high risk information --> + <string name="contact_diary_high_risk_title">"Podwyższone ryzyko"</string> + <!-- XTXT: Title for contact diary overview screen low risk information --> + <string name="contact_diary_low_risk_title">"Niskie ryzyko"</string> + <!-- XTXT: Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body">"na podstawie kontaktów ocenionych przez aplikacjÄ™."</string> + <!-- XTXT: Extended Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body_extended">"na podstawie kontaktów ocenionych przez aplikacjÄ™. Niekoniecznie sÄ… one zwiÄ…zane z zarejestrowanymi przez Ciebie osobami i miejscami."</string> + + <!-- XTXT: content description of contact journal image on home screen --> <string name="contact_diary_homescreen_card_image_content_description">"ZamkniÄ™ta książka z zakÅ‚adkÄ…"</string> <!-- XTXT: content description of contact journal header image on onboarding screen --> 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 new file mode 100644 index 0000000000000000000000000000000000000000..38fa6509bae67a794f6d0683f17bda05aa386406 --- /dev/null +++ b/Corona-Warn-App/src/main/res/values-pl/release_info_strings.xml @@ -0,0 +1,15 @@ +<?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"> + <!-- #################################### + Release Info Screen 1.12 + ###################################### --> + + <!-- XHED: Title for the release info screen --> + <string name="release_info_header">"Nowe funkcje"</string> + <!-- XHED: Version title for the release info screen --> + <string name="release_info_version_title">"Wersja %1$s"</string> + <!-- XTXT: Description for the release info screen --> + <string name="release_info_version_body">"Oprócz poprawek błędów ta aktualizacja zawiera również nowe i ulepszone funkcje."</string> + <!-- XBUT: Continue button for the release info screen --> + <string name="release_info_continue_button">"Dalej"</string> + +</resources> \ No newline at end of file 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 e13e39f3865423caf48867d41d7b85df47abc1d6..8e9bbff42bad7fd1045a442ddc76c726dd489d2d 100644 --- a/Corona-Warn-App/src/main/res/values-pl/strings.xml +++ b/Corona-Warn-App/src/main/res/values-pl/strings.xml @@ -22,12 +22,6 @@ <!-- NOTR --> <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> <!-- NOTR --> - <string name="preference_background_job_allowed"><xliff:g id="preference">"preference_background_job_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_mobile_data_allowed"><xliff:g id="preference">"preference_mobile_data_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_registration_token"><xliff:g id="preference">"preference_registration_token"</xliff:g></string> - <!-- NOTR --> <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> <!-- NOTR --> <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> @@ -173,8 +167,8 @@ <item quantity="few">"Narażenia w ciÄ…gu %1$d dni z podwyższonym ryzykiem"</item> <item quantity="many">"Narażenia w ciÄ…gu %1$d dni z podwyższonym ryzykiem"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"Ostatnio dnia %1$s"</string> + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"Ostatnio dnia %1$s"</string> <!-- #################################### Risk Card - Progress @@ -230,11 +224,11 @@ <!-- XHED: App overview subtitle for tracing explanation--> <string name="main_overview_subtitle_tracing">"Rejestrowanie narażenia"</string> <!-- YTXT: App overview body text about tracing --> - <string name="main_overview_body_tracing">"Rejestrowanie narażenia jest jednÄ… z trzech głównych funkcji aplikacji. Po jej aktywacji rejestrowane sÄ… kontakty ze smartfonami innych osób. Nie musisz robić nic wiÄ™cej."</string> + <string name="main_overview_body_tracing">"Rejestrowanie narażenia jest jednÄ… z trzech głównych funkcji aplikacji. Po jej aktywacji automatycznie rejestrowane sÄ… kontakty ze smartfonami innych osób."</string> <!-- XHED: App overview subtitle for risk explanation --> <string name="main_overview_subtitle_risk">"Ryzyko zakażenia"</string> <!-- YTXT: App overview body text about risk levels --> - <string name="main_overview_body_risk">"JeÅ›li w ciÄ…gu ostatnich 14 dni miaÅ‚eÅ›(-aÅ›) kontakt z osobÄ…, u której zdiagnozowano koronawirusa, aplikacja oblicza Twoje osobiste ryzyko zakażenia poprzez pomiar czasu trwania i bliskoÅ›ci kontaktu."</string> + <string name="main_overview_body_risk">"JeÅ›li rejestrowanie narażenia jest aktywne, aplikacja oblicza Twoje osobiste ryzyko zakażenia poprzez pomiar czasu trwania i bliskoÅ›ci Twojego kontaktu z osobami, u których zdiagnozowano koronawirusa w ciÄ…gu ostatnich 14 dni."</string> <!-- XHED: App overview subtitle for risk level list --> <string name="main_overview_subtitle_risk_levels">"Może wyÅ›wietlić siÄ™ nastÄ™pujÄ…cy status ryzyka:"</string> <!-- XTXT: App overview increased risk level --> @@ -246,7 +240,7 @@ <!-- XHED: App overview subtitle for test procedure explanation --> <string name="main_overview_headline_test">"Powiadamianie innych użytkowników"</string> <!-- YTXT: App overview body text about rest procedure --> - <string name="main_overview_body_test">"InnÄ… kluczowÄ… funkcjÄ… jest rejestracja testu i pobranie wyniku. W przypadku zdiagnozowania u Ciebie koronawirusa bÄ™dziesz mieć możliwość powiadomienia innych i przerwania Å‚aÅ„cucha zakażeÅ„."</string> + <string name="main_overview_body_test">"Możesz zarejestrować swój test i pobrać jego wynik w aplikacji. W przypadku zdiagnozowania u Ciebie koronawirusa bÄ™dziesz mieć możliwość powiadomienia innych i przerwania Å‚aÅ„cucha zakażeÅ„."</string> <!-- XHED: App overview headline for glossary --> <string name="main_overview_headline_glossary">"Definicja terminów:"</string> <!-- XHED: App overview subtitle for glossary key storage --> @@ -256,7 +250,7 @@ <!-- XHED: App overview subtitle for glossary risk calculation --> <string name="main_overview_subtitle_glossary_calculation">"Sprawdzanie narażenia"</string> <!-- YTXT: App overview body for glossary risk calculation --> - <string name="main_overview_body_glossary_calculation">"Odczytanie danych dziennika narażeÅ„ i porównanie ze zgÅ‚oszonymi zakażeniami innych użytkowników. Ryzyko jest sprawdzane automatycznie kilka razy dziennie."</string> + <string name="main_overview_body_glossary_calculation">"Odczytanie danych dziennika narażeÅ„ i porównanie z powiadomieniami o ryzyku otrzymanymi od innych użytkowników. Ryzyko jest sprawdzane automatycznie kilka razy dziennie."</string> <!-- XHED: App overview subtitle for glossary contact --> <string name="main_overview_subtitle_glossary_contact">"Ryzyko narażenia"</string> <!-- YTXT: App overview body for glossary contact --> @@ -328,6 +322,8 @@ <string name="risk_details_information_body_low_risk">"Masz niskie ryzyko zakażenia, ponieważ nie zarejestrowano narażenia na kontakt z osobami, u których później zdiagnozowano COVID-19, lub ponieważ Twoje kontakty trwaÅ‚y krótko przy zachowaniu odpowiednio dużej odlegÅ‚oÅ›ci."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Ryzyko zakażenia jest obliczane lokalnie na Twoim smartfonie na podstawie danych rejestrowania narażenia. Ta kalkulacja uwzglÄ™dnia również dystans i czas trwania narażenia na kontakt z osobami, u których zdiagnozowano koronawirusa, a także ich potencjalnÄ… zakaźność. Twoje ryzyko zakażenia nie jest widoczne dla nikogo ani nikomu przekazywane."</string> + <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> + <string name="risk_details_information_body_increased_risk_date">"Masz podwyższone ryzyko zakażenia, ponieważ w dniu %1$s byÅ‚eÅ›(-aÅ›) narażony(-a) na dÅ‚uższy, bliski kontakt z co najmniej jednÄ… osobÄ…, u której zdiagnozowano koronawirusa."</string> <!-- YTXT: risk details - increased risk explanation text with variable for day(s) since last contact --> <plurals name="risk_details_information_body_increased_risk"> <item quantity="one">"Masz podwyższone ryzyko zakażenia, ponieważ %1$s dzieÅ„ temu byÅ‚eÅ›(-aÅ›) narażony(-a) na dÅ‚uższy, bliski kontakt z co najmniej jednÄ… osobÄ…, u której zdiagnozowano koronawirusa."</item> @@ -352,9 +348,6 @@ <string name="risk_details_explanation_dialog_title">"Informacje o funkcjonalnoÅ›ci rejestrowania narażenia"</string> <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> <string name="risk_details_explanation_dialog_faq_body">"WiÄ™cej informacji znajduje siÄ™ na naszej stronie „CzÄ™sto zadawane pytaniaâ€."</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> - <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Twój status ryzyka"</string> <!-- YTXT: risk details - deadman notification text --> @@ -425,7 +418,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"Aby ustalić wystÄ™powanie ryzyka zakażenia, musisz aktywować funkcjÄ™ rejestrowania narażenia."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"DziaÅ‚anie funkcji rejestrowania narażenia polega na odbieraniu przez Twój smartfon z systemem Android za pomocÄ… Bluetooth zaszyfrowanych, losowych identyfikatorów innych użytkowników i przekazywaniu Twoich wÅ‚asnych, losowych identyfikatorów do ich smartfonów. Rejestrowanie narażenia można wyłączyć w dowolnym momencie. "</string> + <string name="onboarding_tracing_body">"DziaÅ‚anie funkcji rejestrowania narażenia polega na odbieraniu przez Twój smartfon z systemem Android za pomocÄ… Bluetooth zaszyfrowanych, losowych identyfikatorów innych użytkowników aplikacji i przekazywaniu Twoich wÅ‚asnych, losowych identyfikatorów do ich smartfonów. Rejestrowanie narażenia można wyłączyć w dowolnym momencie."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"Zaszyfrowane losowe identyfikatory przekazujÄ… innym osobom jedynie informacje o dacie kontaktu, czasie trwania i odlegÅ‚oÅ›ci (obliczonej na podstawie mocy sygnaÅ‚u) od innych użytkowników aplikacji. Ustalenie tożsamoÅ›ci osób na podstawie losowych identyfikatorów nie jest możliwe."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -475,9 +468,9 @@ <!-- XACT: Onboarding (test) page title --> <string name="onboarding_test_accessibility_title">"Strona wprowadzenia 5 z 6: JeÅ›li zdiagnozowano u Ciebie koronawirusa"</string> <!-- XHED: onboarding(test) - about positive tests --> - <string name="onboarding_test_headline">"JeÅ›li zdiagnozowano u Ciebie koronawirusa..."</string> + <string name="onboarding_test_headline">"JeÅ›li zdiagnozowano u Ciebie koronawirusa"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> - <string name="onboarding_test_subtitle">"… zgÅ‚oÅ› ten fakt w Corona-Warn-App. UdostÄ™pnianie wyników testu jest dobrowolne i bezpieczne. Zrób to ze wzglÄ™du na zdrowie innych osób."</string> + <string name="onboarding_test_subtitle">"Jeżeli otrzymasz pozytywny wynik testu, zgÅ‚oÅ› ten fakt w aplikacji. UdostÄ™pnianie wyników testu jest dobrowolne i bezpieczne. Zrób to ze wzglÄ™du na zdrowie innych osób."</string> <!-- YTXT: onboarding(test) - explain test --> <string name="onboarding_test_body">"Twoje powiadomienie jest szyfrowane w bezpieczny sposób i przetwarzane na bezpiecznym serwerze. Osoby, których zaszyfrowane losowe identyfikatory zostaÅ‚y zgromadzone przez Twój smartfon, otrzymajÄ… wtedy ostrzeżenie wraz z informacjÄ… na temat dalszych kroków postÄ™powania."</string> <!-- XACT: onboarding(test) - illustraction description, header image --> @@ -527,7 +520,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"UsÅ‚ugi lokalizacji dezaktywowane"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"Musisz włączyć funkcjÄ™ rejestrowania narażenia, aby aplikacja mogÅ‚a ustalić, czy dotyczy CiÄ™ ryzyko zakażenia. Funkcja rejestrowania narażenia dziaÅ‚a w skali miÄ™dzynarodowej, co oznacza, że potencjalne narażenie użytkowników jest wykrywane również przez inne oficjalne aplikacje koronawirusowe.\n\nDziaÅ‚anie funkcji rejestrowania narażenia polega na odbieraniu przez Twój smartfon z systemem Android za pomocÄ… Bluetooth zaszyfrowanych, losowych identyfikatorów innych użytkowników i przekazywaniu Twoich wÅ‚asnych, losowych identyfikatorów do ich smartfonów. Codziennie aplikacja pobiera listy losowych identyfikatorów – wraz z wszelkimi opcjonalnie podawanymi informacjami o wystÄ…pieniu objawów – wszystkich użytkowników, którzy mieli pozytywny wynik testu na obecność koronawirusa i dobrowolnie udostÄ™pnili tÄ™ informacjÄ™ (a dokÅ‚adnie swoje losowe identyfikatory) poprzez swojÄ… aplikacjÄ™. Lista ta jest nastÄ™pnie porównywana z zarejestrowanymi przez Twój smartfon z systemem Android losowymi identyfikatorami innych użytkowników, z którymi miaÅ‚eÅ›(-aÅ›) kontakt, w celu obliczenia prawdopodobieÅ„stwa Twojego zakażenia i ostrzeżenia CiÄ™ w razie potrzeby.\n\nFunkcjÄ™ rejestrowania narażenia można wyłączyć w dowolnym momencie.\n\nAplikacja nigdy nie gromadzi danych osobowych, takich jak imiÄ™ i nazwisko, adres czy lokalizacja. Takie informacje nie sÄ… też przekazywane innym użytkownikom. Nie jest możliwe wykorzystanie losowych identyfikatorów do ustalenia tożsamoÅ›ci poszczególnych osób."</string> + <string name="settings_tracing_body_text">"Musisz włączyć funkcjÄ™ rejestrowania narażenia, aby aplikacja mogÅ‚a ustalić, czy dotyczy CiÄ™ ryzyko zakażenia. Funkcja rejestrowania narażenia dziaÅ‚a w skali miÄ™dzynarodowej, co oznacza, że potencjalne narażenie użytkowników jest wykrywane również przez inne oficjalne aplikacje koronawirusowe.\n\nDziaÅ‚anie funkcji rejestrowania narażenia polega na odbieraniu przez Twój smartfon z systemem Android za pomocÄ… Bluetooth zaszyfrowanych, losowych identyfikatorów innych użytkowników aplikacji i przekazywaniu Twoich wÅ‚asnych, losowych identyfikatorów do ich smartfonów. Codziennie aplikacja pobiera listy losowych identyfikatorów – wraz z wszelkimi opcjonalnie podawanymi informacjami o wystÄ…pieniu objawów – wszystkich użytkowników, którzy mieli pozytywny wynik testu na obecność koronawirusa i dobrowolnie udostÄ™pnili tÄ™ informacjÄ™ (a dokÅ‚adnie swoje losowe identyfikatory) poprzez swojÄ… aplikacjÄ™. Lista ta jest nastÄ™pnie porównywana z zarejestrowanymi przez Twój smartfon z systemem Android losowymi identyfikatorami innych użytkowników, z którymi miaÅ‚eÅ›(-aÅ›) kontakt, w celu obliczenia prawdopodobieÅ„stwa Twojego zakażenia i ostrzeżenia CiÄ™ w razie potrzeby.\n\nFunkcjÄ™ rejestrowania narażenia można wyłączyć w dowolnym momencie.\n\nAplikacja nigdy nie gromadzi danych osobowych, takich jak imiÄ™ i nazwisko, adres czy lokalizacja. Takie informacje nie sÄ… też przekazywane innym użytkownikom. Nie jest możliwe wykorzystanie losowych identyfikatorów do ustalenia tożsamoÅ›ci poszczególnych osób."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Aktywne"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -555,7 +548,7 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_connection_headline">"Otwórz połączenie z Internetem"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if connection is disabled --> - <string name="settings_tracing_status_connection_body">"Rejestrowanie narażenia wymaga połączenia z Internetem w celu obliczenia narażeÅ„. Włącz WIFI lub dane komórkowe w ustawieniach swojego urzÄ…dzenia."</string> + <string name="settings_tracing_status_connection_body">"Rejestrowanie narażenia wymaga połączenia z Internetem w celu obliczenia ryzyka zakażenia. Włącz WI-FI lub dane komórkowe w ustawieniach swojego urzÄ…dzenia."</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card --> <string name="settings_tracing_status_connection_button">"Otwórz ustawienia urzÄ…dzenia"</string> <!-- XTXT: settings(tracing) - explains the circle progress indicator to the right with the current value --> @@ -686,7 +679,7 @@ <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_headline">"Jak możemy Ci pomóc?"</string> <!-- YTXT: Body text for technical contact and hotline information page --> - <string name="information_contact_body">"W przypadku pytaÅ„ technicznych dotyczÄ…cych aplikacji Corona-Warn-App skontaktuj siÄ™ z naszÄ… infoliniÄ….\n\nOsoby niedosÅ‚yszÄ…ce mogÄ… skorzystać z usÅ‚ug Tess Relay (tÅ‚umaczenie z pisanego jÄ™zyka niemieckiego na jÄ™zyk migowy i odwrotnie) w celu skontaktowania siÄ™ z infoliniÄ…. To oprogramowanie można pobrać ze sklepu Google Play."</string> + <string name="information_contact_body">"W przypadku pytaÅ„ technicznych dotyczÄ…cych aplikacji Corona-Warn-App skontaktuj siÄ™ z naszÄ… ogólnokrajowÄ… infoliniÄ….\n\nOsoby niedosÅ‚yszÄ…ce mogÄ… skorzystać z usÅ‚ug Tess Relay (tÅ‚umaczenie z pisanego jÄ™zyka niemieckiego na jÄ™zyk migowy i odwrotnie) w celu skontaktowania siÄ™ z infoliniÄ…. To oprogramowanie można pobrać ze sklepu Google Play."</string> <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_subtitle_phone">"Infolinia techniczna:"</string> <!-- XLNK: Button / hyperlink to phone call for technical contact and hotline information page --> @@ -714,7 +707,7 @@ <!-- XHED: Headline for legal information page, publisher section --> <string name="information_legal_headline_publisher">"Opublikowane przez"</string> <!-- YTXT: subtitle for legal information page, publisher section --> - <string name="information_legal_subtitle_publisher">"(odpowiedzialny zgodnie z § 5, par. 1 TMG, § 55 par. 1 RStV, DS-GVO, BDSG)"</string> + <string name="information_legal_subtitle_publisher">"(odpowiedzialny zgodnie z § 5 (1) TMG, § 18 (1) MStV)"</string> <!-- YTXT: body for legal information page, publisher section --> <string name="information_legal_body_publisher">"Robert Koch Institute"<xliff:g id="line_break">"\n"</xliff:g>"Nordufer 20"<xliff:g id="line_break">"\n"</xliff:g>"13353 Berlin"<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"reprezentowany przez prezesa"</string> <!-- XHED: Headline for legal information page, contact section --> @@ -768,7 +761,7 @@ <!-- XHED: Dialog title for generic web request error --> <string name="submission_error_dialog_web_generic_error_title">"Błąd"</string> <!-- XMSG: Dialog body for generic web request network error with status code --> - <string name="submission_error_dialog_web_generic_network_error_body">"Nie udaÅ‚o siÄ™ nawiÄ…zać połączenia (%1$d). Spróbuj ponownie."</string> + <string name="submission_error_dialog_web_generic_network_error_body">"Twoje połączenie z Internetem mogÅ‚o zostać utracone. Zadbaj o jego przywrócenie."</string> <!-- XMSG: Dialog body for generic web request error without status code --> <string name="submission_error_dialog_web_generic_error_body">"Nie udaÅ‚o siÄ™ nawiÄ…zać połączenia. Spróbuj ponownie."</string> <!-- XBUT: Positive button for generic web request error --> @@ -960,6 +953,9 @@ <!-- Submission Intro --> <!-- XBUT: Submission introduction next button--> <string name="submission_intro_button_next">"Dalej"</string> + <!-- YTXT: Description for illustration in submission onboarding--> + <string name="submission_intro_illustration_description">"Zaszyfrowana diagnoza zakażenia jest przesyÅ‚ana do systemu, który bÄ™dzie teraz ostrzegaÅ‚ innych użytkowników."</string> + <!-- Dispatcher --> <!-- XHED: Page headline for dispatcher menu --> @@ -1048,9 +1044,9 @@ <!-- YTXT: submission done further info bullet points --> <string-array name="submission_done_further_info_bullet_points"> <item>"Okres kwarantanny wynosi zazwyczaj 14 dni. Obserwuj swoje objawy i monitoruj ich rozwój."</item> - <item>"Zostaniesz poproszony(-a) przez organ ds. zdrowia publicznego o stworzenie listy osób, z którymi miaÅ‚eÅ›(-aÅ›) kontakt. Powinna ona obejmować wszystkie osoby, z którymi miaÅ‚eÅ›(-aÅ›) bliski kontakt (w odlegÅ‚oÅ›ci mniejszej niż 2 metry, rozmowa twarzÄ… w twarz) przez ponad 15 minut w ciÄ…gu dwóch dni przed wystÄ…pieniem objawów. Możesz do tego celu wykorzystać swój dziennik kontaktów: po prostu wyeksportuj wpisy, a nastÄ™pnie wydrukuj je lub wyÅ›lij pocztÄ… e-mail."</item> + <item>"Zostaniesz poproszony(-a) przez organ ds. zdrowia publicznego o stworzenie listy osób, z którymi miaÅ‚eÅ›(-aÅ›) kontakt. Powinna ona obejmować wszystkie osoby, z którymi miaÅ‚eÅ›(-aÅ›) bliski kontakt (w odlegÅ‚oÅ›ci mniejszej niż 2 metry, rozmowa twarzÄ… w twarz) przez ponad 15 minut w ciÄ…gu dwóch dni poprzedzajÄ…cych zachorowanie. Możesz do tego celu wykorzystać swój dziennik kontaktów: po prostu wyeksportuj wpisy, a nastÄ™pnie wydrukuj je lub wyÅ›lij pocztÄ… e-mail."</item> <item>"Zwróć szczególnÄ… uwagÄ™ na osoby, które nie zostanÄ… powiadomione bezpoÅ›rednio przez aplikacjÄ™, ponieważ nie posiadajÄ… smartfonów lub nie zainstalowaÅ‚y aplikacji."</item> - <item>"Nawet jeÅ›li nie masz już żadnych objawów i znów czujesz siÄ™ dobrze, możesz nadal zarażać."</item> + <item>"Nawet jeÅ›li nie masz już żadnych objawów i znów czujesz siÄ™ dobrze, możesz nadal zarażać, dlatego prosimy o przestrzeganie wyznaczonego okresu kwarantanny."</item> </string-array> <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Gotowe"</string> @@ -1113,22 +1109,22 @@ <!-- XBUT: submission contact enter tan button --> <string name="submission_contact_button_enter">"Wpisz TAN"</string> <!-- YTXT: Body text for step 1 of contact page --> - <string name="submission_contact_step_1_body">"ZadzwoÅ„ na infoliniÄ™ i poproÅ› o numer TAN:"</string> + <string name="submission_contact_step_1_body">"ZadzwoÅ„ na ogólnokrajowÄ… infoliniÄ™ i poproÅ› o numer TAN:"</string> <!-- XLNK: Button / hyperlink to phone call for TAN contact page --> <string name="submission_contact_number_display">"0800 7540002"</string> <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"Zarejestruj test poprzez wpisanie numeru TAN w aplikacji."</string> + <string name="submission_contact_step_2_body">"Zarejestruj swój test poprzez wpisanie numeru TAN w aplikacji."</string> <!-- YTXT: Body text for operating hours in contact page--> - <string name="submission_contact_operating_hours_body">"JÄ™zyki: \nangielski, niemiecki, turecki\n\nGodziny pracy:\nod poniedziaÅ‚ku do niedzieli: caÅ‚odobowo\n\nPołączenie jest bezpÅ‚atne."</string> + <string name="submission_contact_operating_hours_body">"JÄ™zyki: \nangielski, niemiecki, turecki\n\nGodziny pracy:\ncaÅ‚odobowo\n\nPołączenie jest bezpÅ‚atne."</string> <!-- YTXT: Body text for technical contact and hotline information page --> <string name="submission_contact_body_other">"W razie jakichkolwiek pytaÅ„ zwiÄ…zanych ze zdrowiem skontaktuj siÄ™ z lekarzem rodzinnym lub lekarzem dyżurnym pod numerem: 116 117."</string> <!-- XACT: Submission contact page title --> <string name="submission_contact_accessibility_title">"ZadzwoÅ„ na infoliniÄ™ i poproÅ› o numer TAN"</string> <!-- XACT: Content Description for submission contact step 1, number has to sync with the display number --> - <string name="submission_contact_step_1_content">"Najpierw zadzwoÅ„ na infoliniÄ™ i poproÅ› o numer TAN. Numer infolinii to 0800 7540002. Godziny pracy: od poniedziaÅ‚ku do piÄ…tku od 8:00 do 22:00 oraz w sobotÄ™ i niedzielÄ™ od 10:00 do 22:00. Połączenie jest bezpÅ‚atne."</string> + <string name="submission_contact_step_1_content">"Najpierw zadzwoÅ„ na ogólnokrajowÄ… infoliniÄ™ i poproÅ› o numer TAN. Numer infolinii to 0800 7540002. Godziny pracy: od poniedziaÅ‚ku do piÄ…tku od 8:00 do 22:00 oraz w sobotÄ™ i niedzielÄ™ od 10:00 do 22:00. Połączenie jest bezpÅ‚atne."</string> <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"NastÄ™pnie zarejestruj test przy użyciu numeru TAN w aplikacji."</string> @@ -1164,7 +1160,7 @@ <!-- XHED: Page title for the various submission status fetch ready --> <string name="submission_status_card_title_ready">"Twój wynik testu jest dostÄ™pny"</string> <!-- XHED: Subtitle for the submission status card: invalid --> - <string name="submission_status_card_subtitle_invalid">"NieprawidÅ‚owy test"</string> + <string name="submission_status_card_subtitle_invalid">"Ustalenie wyniku Twojego testu jest niemożliwe"</string> <!-- XHED: Subtitle for the submission status card: negative --> <string name="submission_status_card_subtitle_negative">"Diagnoza: brak zakażenia"</string> <!-- XHED: Subtitle for the submission status card: ready --> @@ -1295,7 +1291,7 @@ <!-- XHED: Title for submission statistics card --> <string name="statistics_card_submission_title">"Ostrzeżenia od użytkowników aplikacji"</string> <!-- XTXT: Text displayed in the bottom of submission statistics card--> - <string name="statistics_card_submission_bottom_text">"za poÅ›rednictwem Corona-Warn-App"</string> + <string name="statistics_card_submission_bottom_text">"Informacje o aplikacji Corona-Warn-App"</string> <!-- XTXT: Timestamp refers to today's date in submission and infections cards --> <string name="statistics_primary_value_today">"Dzisiaj"</string> @@ -1333,8 +1329,8 @@ <!-- XHED: Explanation screen seven day r-value title --> <string name="statistics_explanation_seven_day_r_value_title">"7-dniowa wartość R"</string> <!-- XTXT: Explanation screen seven day r-value text --> - <string name="statistics_explanation_seven_day_r_value_text">"Współczynnik reprodukcji R wskazuje liczbÄ™ osób, którÄ… zaraża przeciÄ™tna osoba zakażona. Bieżąca wartość uwzglÄ™dnia dane z ostatnich 5 dni.\n\nWiÄ™cej informacji znajdziesz w\nodpowiedziach na czÄ™sto zadawane pytania dotyczÄ…ce statystyk."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> + <string name="statistics_explanation_seven_day_r_value_text">"Współczynnik reprodukcji R wskazuje liczbÄ™ osób, którÄ… zaraża przeciÄ™tna osoba zakażona. Bieżąca wartość uwzglÄ™dnia dane z ostatnich 5 dni.\n\nWiÄ™cej informacji znajdziesz w odpowiedziach na czÄ™sto zadawane pytania:\nCzÄ™sto zadawane pytania dotyczÄ…ce statystyk."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> <string name="statistics_explanation_seven_day_r_link_label">"CzÄ™sto zadawane pytania dotyczÄ…ce statystyk."</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Legenda"</string> @@ -1355,7 +1351,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"Trend"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"Kierunek strzaÅ‚ki wskazuje, czy trend roÅ›nie, maleje, czy też pozostaje stabilny, czyli wykazuje odchylenie mniejsze niż 1\% w porównaniu z poprzednim dniem lub 5\% w porównaniu z poprzednim tygodniem. Kolor wskazuje, że trend jest pozytywny (zielony), negatywny (czerwony) lub neutralny (szary). Trend ustala siÄ™ poprzez porównanie wartoÅ›ci z poprzedniego dnia z wartoÅ›ciÄ… sprzed dwóch dni. W przypadku trendów 7-dniowych porównywana jest Å›rednia wartość z ostatnich 7 dni ze Å›redniÄ… wartoÅ›ciÄ… z 7 dni je poprzedzajÄ…cych."</string> + <string name="statistics_explanation_trend_text">"Kierunek strzaÅ‚ki wskazuje, czy trend roÅ›nie, maleje, czy też pozostaje stabilny, czyli wykazuje odchylenie mniejsze niż 1%% w porównaniu z poprzednim dniem lub 5%% w porównaniu z poprzednim tygodniem. Kolor wskazuje, że trend jest pozytywny (zielony), negatywny (czerwony) lub neutralny (szary). Trend ustala siÄ™ poprzez porównanie wartoÅ›ci z poprzedniego dnia z wartoÅ›ciÄ… sprzed dwóch dni. W przypadku trendów 7-dniowych porównywana jest Å›rednia wartość z ostatnich 7 dni ze Å›redniÄ… wartoÅ›ciÄ… z 7 dni je poprzedzajÄ…cych."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"Można przedstawić nastÄ™pujÄ…ce trendy:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1371,6 +1367,11 @@ <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"Abstrakcyjne zdjÄ™cie smartfona z czterema symbolami zastÄ™pczymi do celów informacyjnych"</string> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"Kafelek statystyk"</string> + <!-- XTXT: Statistics Card Navigation Announcement --> + <string name="accessibility_statistics_card_navigation_information">"Aby wyÅ›wietlić wiÄ™cej statystyk, przesuÅ„ palcem poziomo miÄ™dzy kafelkami."</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1406,6 +1407,9 @@ <string name="errors_risk_detection_limit_reached_title">"Limit zostaÅ‚ już osiÄ…gniÄ™ty"</string> <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_description">"Sprawdzanie narażeÅ„ nie jest już dzisiaj możliwe, ponieważ osiÄ…gniÄ™to maksymalnÄ… liczbÄ™ takich kontroli na dzieÅ„ okreÅ›lonÄ… przez system operacyjny. Sprawdź ponownie swój status ryzyka jutro."</string> + <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> + <string name="errors_ssl_certificate_deactivated">"Aktywuj certyfikat bezpieczeÅ„stwa SYSTEMU T-Systems Enterprise Services GmbH, T-TeleSec GlobalRoot Class 2 na swoim smartfonie. Aby uzyskać wiÄ™cej informacji, zapoznaj siÄ™ z czÄ™sto zadawanymi pytaniami na stronie https://coronawarn.app/en/ w sekcji „CAUSE: 2001â€."</string> + <!-- #################################### Generic Error Messages ###################################### --> @@ -1579,7 +1583,7 @@ <!-- YDES: Title for the interoperability onboarding if country download fails for Risk Details --> <string name="interoperability_onboarding_list_title_riskdetection_no_network">"Nie można teraz wyÅ›wietlić krajów"</string> <!-- YMSW: Subtitle for the interoperability onboarding if country download fails --> - <string name="interoperability_onboarding_list_subtitle_failrequest_no_network">"Twoje połączenie z Internetem mogÅ‚o zostać utracone. Upewnij siÄ™, że masz połączenie z Internetem."</string> + <string name="interoperability_onboarding_list_subtitle_failrequest_no_network">"Twoje połączenie z Internetem mogÅ‚o zostać utracone. Zadbaj o jego przywrócenie."</string> <!-- XBUT: Title for the interoperability onboarding Settings-Button if no network is available --> <string name="interoperability_onboarding_list_button_title_no_network">"Otwórz ustawienia urzÄ…dzenia"</string> 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 ba3dd2f70a3710a70e785e7620a6cf3d3895a238..7aa904c09eb51d0df2758387f07f8fb65353fb8e 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Nicio persoană definită deocamdată"</string> <string name="contact_diary_person_list_no_items_subtitle">"CreaÈ›i o persoană È™i adăugaÈ›i-o la jurnalul dvs. de contacte."</string> <string name="contact_diary_person_bottom_sheet_title">"Persoană"</string> - <string name="contact_diary_person_bottom_sheet_text_input_hint">"Prenume, nume"</string> + <string name="contact_diary_person_bottom_sheet_text_input_hint">"Nume"</string> <string name="contact_diary_person_bottom_sheet_save_button">"Salvare"</string> <string name="contact_diary_location_bottom_sheet_title">"Loc"</string> <string name="contact_diary_location_bottom_sheet_text_input_hint">"Descriere"</string> @@ -40,6 +40,8 @@ <string name="contact_diary_onboarding_functionality_fourth_section">"PuteÈ›i oricând elimina intrări de persoane È™i locuri din jurnal. Intrările din jurnal vor fi È™terse automat după 16 zile."</string> <!-- XTXT: Contact diary onboarding screen fifth functionality --> <string name="contact_diary_onboarding_functionality_fifth_section">"PuteÈ›i exporta jurnalul de contacte în format de text simplu È™i apoi puteÈ›i tipări sau edita intrările, sau le puteÈ›i pune la dispoziÈ›ia autorității de sănătate publică, după cum este necesar."</string> + <!-- XTXT: Contact diary onboarding screen sixth functionality --> + <string name="contact_diary_onboarding_functionality_sixth_section">"Expunerile indicate nu sunt legate neapărat de persoanele È™i locurile pe care le-aÈ›i înregistrat. Vă rugăm să nu trageÈ›i concluzii greÈ™ite."</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_title">"Jurnal de contacte"</string> <!-- XTXT: Body for legal information of the contact diary onboarding screen --> @@ -61,6 +63,17 @@ <!-- XTXT: Header for contact diary overview screen --> <string name="contact_diary_overview_header">"Pagina iniÈ›ială"</string> + + <!-- XTXT: Title for contact diary overview screen high risk information --> + <string name="contact_diary_high_risk_title">"Risc crescut"</string> + <!-- XTXT: Title for contact diary overview screen low risk information --> + <string name="contact_diary_low_risk_title">"Risc redus"</string> + <!-- XTXT: Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body">"pe baza întâlnirilor evaluate de aplicaÈ›ie."</string> + <!-- XTXT: Extended Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body_extended">"pe baza întâlnirilor evaluate de aplicaÈ›ie. Acestea nu sunt legate neapărat de persoanele È™i locurile pe care le-aÈ›i înregistrat."</string> + + <!-- XTXT: content description of contact journal image on home screen --> <string name="contact_diary_homescreen_card_image_content_description">"O carte închisă cu un semn de citire"</string> <!-- XTXT: content description of contact journal header image on onboarding screen --> 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 new file mode 100644 index 0000000000000000000000000000000000000000..bfa4639f34a2faa7512426f0c54e2ff23ee581eb --- /dev/null +++ b/Corona-Warn-App/src/main/res/values-ro/release_info_strings.xml @@ -0,0 +1,15 @@ +<?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"> + <!-- #################################### + Release Info Screen 1.12 + ###################################### --> + + <!-- XHED: Title for the release info screen --> + <string name="release_info_header">"Caracteristici noi"</string> + <!-- XHED: Version title for the release info screen --> + <string name="release_info_version_title">"Versiunea %1$s"</string> + <!-- XTXT: Description for the release info screen --> + <string name="release_info_version_body">"Pe lângă remedierile de erori, această actualizare include È™i caracteristici noi È™i îmbunătățiri."</string> + <!-- XBUT: Continue button for the release info screen --> + <string name="release_info_continue_button">"ÃŽnainte"</string> + +</resources> \ No newline at end of file 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 8041dc5266537199795aa2e6c2d3bcc138bffbf0..06c5fdf6f5b11a500f226a98b428e0f5329526ce 100644 --- a/Corona-Warn-App/src/main/res/values-ro/strings.xml +++ b/Corona-Warn-App/src/main/res/values-ro/strings.xml @@ -22,12 +22,6 @@ <!-- NOTR --> <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> <!-- NOTR --> - <string name="preference_background_job_allowed"><xliff:g id="preference">"preference_background_job_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_mobile_data_allowed"><xliff:g id="preference">"preference_mobile_data_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_registration_token"><xliff:g id="preference">"preference_registration_token"</xliff:g></string> - <!-- NOTR --> <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> <!-- NOTR --> <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> @@ -173,8 +167,8 @@ <item quantity="few">"Expuneri cu risc crescut în %1$d zile"</item> <item quantity="many">"Expuneri cu risc crescut în %1$d de zile"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"Cel mai recent pe %1$s"</string> + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"Cel mai recent pe %1$s"</string> <!-- #################################### Risk Card - Progress @@ -230,11 +224,11 @@ <!-- XHED: App overview subtitle for tracing explanation--> <string name="main_overview_subtitle_tracing">"ÃŽnregistrarea în jurnal a expunerilor"</string> <!-- YTXT: App overview body text about tracing --> - <string name="main_overview_body_tracing">"ÃŽnregistrarea în jurnal a expunerilor este una dintre cele trei caracteristici centrale ale aplicaÈ›iei. Când o activaÈ›i, sunt înregistrate întâlnirile cu smartphone-urile altor persoane. Nu trebuie să faceÈ›i nimic altceva."</string> + <string name="main_overview_body_tracing">"ÃŽnregistrarea în jurnal a expunerilor este una dintre cele trei caracteristici centrale ale aplicaÈ›iei. Când o activaÈ›i, sunt înregistrate automat întâlnirile cu smartphone-urile altor persoane."</string> <!-- XHED: App overview subtitle for risk explanation --> <string name="main_overview_subtitle_risk">"Risc de infectare"</string> <!-- YTXT: App overview body text about risk levels --> - <string name="main_overview_body_risk">"Dacă în ultimele 14 zile aÈ›i intrat în contact cu o persoană care a fost diagnosticată cu coronavirus, aplicaÈ›ia calculează riscul dvs. personal de infectare. Face aceasta măsurând durata È™i proximitatea expunerii."</string> + <string name="main_overview_body_risk">"Când este activă înregistrarea în jurnal a expunerilor, aplicaÈ›ia calculează riscul dvs. personal de infectare. Efectuează acest lucru prin măsurarea duratei È™i a proximității expunerii la persoanele care au fost diagnosticate cu coronavirus în ultimele 14 zile."</string> <!-- XHED: App overview subtitle for risk level list --> <string name="main_overview_subtitle_risk_levels">"Poate fi afiÈ™ată următoarea stare a riscului:"</string> <!-- XTXT: App overview increased risk level --> @@ -246,7 +240,7 @@ <!-- XHED: App overview subtitle for test procedure explanation --> <string name="main_overview_headline_test">"Notificarea altor utilizatori"</string> <!-- YTXT: App overview body text about rest procedure --> - <string name="main_overview_body_test">"O altă caracteristică centrală este înregistrarea testului È™i primirea rezultatului. Dacă sunteÈ›i diagnosticat cu coronavirus, îi puteÈ›i notifica pe ceilalÈ›i È™i puteÈ›i întrerupe lanÈ›ul de infectare."</string> + <string name="main_overview_body_test">"Vă puteÈ›i înregistra testul È™i primi rezultatul testului în aplicaÈ›ie. Dacă sunteÈ›i diagnosticat cu coronavirus, îi puteÈ›i notifica pe ceilalÈ›i È™i puteÈ›i întrerupe lanÈ›ul de infectare."</string> <!-- XHED: App overview headline for glossary --> <string name="main_overview_headline_glossary">"DefiniÈ›ii ale termenilor:"</string> <!-- XHED: App overview subtitle for glossary key storage --> @@ -256,7 +250,7 @@ <!-- XHED: App overview subtitle for glossary risk calculation --> <string name="main_overview_subtitle_glossary_calculation">"Verificarea expunerii"</string> <!-- YTXT: App overview body for glossary risk calculation --> - <string name="main_overview_body_glossary_calculation">"Datele jurnalului de expuneri sunt citite È™i sincronizate cu infectările raportate de alÈ›i utilizatori. Riscul dvs. este verificat automat de mai multe ori pe zi."</string> + <string name="main_overview_body_glossary_calculation">"Datele jurnalului de expuneri sunt citite È™i sincronizate cu notificările de risc de la alÈ›i utilizatori. Riscul dvs. este verificat automat de mai multe ori pe zi."</string> <!-- XHED: App overview subtitle for glossary contact --> <string name="main_overview_subtitle_glossary_contact">"Risc de expunere"</string> <!-- YTXT: App overview body for glossary contact --> @@ -328,6 +322,8 @@ <string name="risk_details_information_body_low_risk">"AveÈ›i un risc redus de infectare deoarece nu a fost înregistrată nicio expunere la persoane diagnosticate ulterior cu COVID-19 sau întâlnirile dvs. au fost limitate la o perioadă scurtă È™i la o distanță mai mare."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Riscul de infectare este calculat local pe smartphone-ul dvs., utilizând datele de înregistrare în jurnal a expunerilor. Calculul poate È›ine cont È™i de distanÈ›a È™i durata expunerii la persoane diagnosticate cu coronavirus, precum È™i de potenÈ›iala contagiozitate a acestora. Riscul dvs. de infectare nu poate fi observat sau transmis mai departe niciunei alte persoane."</string> + <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> + <string name="risk_details_information_body_increased_risk_date">"AveÈ›i un risc crescut de infectare deoarece aÈ›i fost expus ultima dată pe %1$s pe o perioadă mai lungă de timp È™i în strânsă proximitate cu cel puÈ›in o persoană diagnosticată cu coronavirus."</string> <!-- YTXT: risk details - increased risk explanation text with variable for day(s) since last contact --> <plurals name="risk_details_information_body_increased_risk"> <item quantity="one">"AveÈ›i un risc crescut de infectare deoarece aÈ›i fost expus ultima dată acum %1$s zi pe o perioadă mai lungă de timp È™i în strânsă proximitate cu cel puÈ›in o persoană diagnosticată cu coronavirus."</item> @@ -352,9 +348,6 @@ <string name="risk_details_explanation_dialog_title">"InformaÈ›ii despre funcÈ›ionalitatea de înregistrare în jurnal a expunerilor"</string> <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> <string name="risk_details_explanation_dialog_faq_body">"Pentru mai multe informaÈ›ii, consultaÈ›i pagina noastră de întrebări frecvente."</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> - <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Starea riscului dvs."</string> <!-- YTXT: risk details - deadman notification text --> @@ -425,7 +418,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"Pentru a identifica dacă sunteÈ›i supus riscului de infectare, trebuie să activaÈ›i înregistrarea în jurnal a expunerilor."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"ÃŽnregistrarea în jurnal a expunerilor funcÈ›ionează astfel: smartphone-ul dvs. Android primeÈ™te prin Bluetooth ID-uri aleatorii criptate ale altor utilizatori È™i transmite propriile dvs. ID-uri aleatorii către smartphone-urile acestora. ÃŽnregistrarea în jurnal a expunerilor poate fi oricând dezactivată. "</string> + <string name="onboarding_tracing_body">"ÃŽnregistrarea în jurnal a expunerilor funcÈ›ionează astfel: smartphone-ul dvs. Android primeÈ™te prin Bluetooth ID-uri aleatorii criptate ale altor utilizatori ai aplicaÈ›iei È™i transmite propriile dvs. ID-uri aleatorii către smartphone-urile acestora. ÃŽnregistrarea în jurnal a expunerilor poate fi oricând dezactivată."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"ID-urile aleatorii criptate transmit informaÈ›ii doar despre dată, durată È™i proximitatea față de alte persoane (calculată prin intensitatea semnalului). Nu se pot identifica persoane individuale pe baza ID-urilor aleatorii."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -475,9 +468,9 @@ <!-- XACT: Onboarding (test) page title --> <string name="onboarding_test_accessibility_title">"Pagina de înregistrare 5 din 6: Dacă sunteÈ›i diagnosticat cu coronavirus"</string> <!-- XHED: onboarding(test) - about positive tests --> - <string name="onboarding_test_headline">"Dacă sunteÈ›i diagnosticat cu coronavirus…"</string> + <string name="onboarding_test_headline">"Dacă sunteÈ›i diagnosticat cu coronavirus"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> - <string name="onboarding_test_subtitle">"… vă rugăm să raportaÈ›i acest lucru în Corona-Warn-App. ÃŽmpărtășirea rezultatului testului este voluntară È™i securizată. Vă rugăm să faceÈ›i acest lucru pentru binele celorlalÈ›i."</string> + <string name="onboarding_test_subtitle">"Dacă primiÈ›i un rezultat pozitiv al testului, vă rugăm să raportaÈ›i acest lucru în aplicaÈ›ie. ÃŽmpărtășirea rezultatului testului este voluntară È™i sigură. Vă rugăm să faceÈ›i acest lucru pentru binele celorlalÈ›i."</string> <!-- YTXT: onboarding(test) - explain test --> <string name="onboarding_test_body">"Notificarea dvs. este criptată în mod securizat È™i este prelucrată pe un server sigur. Persoanele ale căror ID-uri aleatorii criptate au fost colectate de smartphone-ul dvs. vor primi acum o avertizare împreună cu informaÈ›ii despre paÈ™ii de urmat."</string> <!-- XACT: onboarding(test) - illustraction description, header image --> @@ -527,7 +520,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"Serviciile de localizare au fost dezactivate"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"Trebuie să activaÈ›i caracteristica de înregistrare în jurnal a expunerilor, pentru ca aplicaÈ›ia să poată determina dacă aveÈ›i risc de infectare. Caracteristica de înregistrare în jurnal a expunerilor funcÈ›ionează la nivel transnaÈ›ional, ceea ce înseamnă că orice expunere posibilă care îi implică pe utilizatori este detectată È™i de alte aplicaÈ›ii oficiale împotriva coronavirusului.\n\nCaracteristica de înregistrare în jurnal a expunerilor funcÈ›ionează astfel: smartphone-ul dvs. Android primeÈ™te prin Bluetooth ID-uri aleatorii criptate ale altor utilizatori È™i transmite propriul dvs. ID aleatoriu către smartphone-urile acestora. ÃŽn fiecare zi, aplicaÈ›ia descarcă liste ce conÈ›in ID-uri aleatorii – împreună cu orice alte informaÈ›ii voluntare despre debutul simptomelor – ale tuturor utilizatorilor testaÈ›i pozitiv la virus È™i care au împărtășit voluntar aceste informaÈ›ii (mai exact: ID-urile lor aleatorii) prin intermediul aplicaÈ›iei lor. Apoi, lista este comparată cu ID-urile aleatorii ale altor utilizatori cu care v-aÈ›i întâlnit care au fost înregistrate de smartphone-ul dvs. Android, pentru a calcula probabilitatea ca È™i dvs. să fi fost infectat È™i să vă avertizeze dacă este necesar. PuteÈ›i utiliza comutatorul pentru a dezactiva în orice moment înregistrarea în jurnal a expunerilor.\n\nAplicaÈ›ia nu colectează niciodată date personale, precum numele, adresa sau locaÈ›ia dvs., iar aceste informaÈ›ii nu sunt transmise niciodată altor utilizatori. Nu se pot utiliza ID-urile aleatorii pentru a trage concluzii despre persoane individuale."</string> + <string name="settings_tracing_body_text">"Trebuie să activaÈ›i caracteristica de înregistrare în jurnal a expunerilor, pentru ca aplicaÈ›ia să poată determina dacă aveÈ›i risc de infectare. Caracteristica de înregistrare în jurnal a expunerilor funcÈ›ionează la nivel transnaÈ›ional, ceea ce înseamnă că orice expunere posibilă care îi implică pe utilizatori este detectată È™i de alte aplicaÈ›ii oficiale împotriva coronavirusului.\n\nCaracteristica de înregistrare în jurnal a expunerilor funcÈ›ionează astfel: smartphone-ul dvs. Android primeÈ™te prin Bluetooth ID-uri aleatorii criptate ale altor utilizatori ai aplicaÈ›iei È™i transmite propriul dvs. ID aleatoriu către smartphone-urile acestora. ÃŽn fiecare zi, aplicaÈ›ia descarcă liste ce conÈ›in ID-uri aleatorii – împreună cu orice alte informaÈ›ii voluntare despre debutul simptomelor – ale tuturor utilizatorilor testaÈ›i pozitiv la virus È™i care au împărtășit voluntar aceste informaÈ›ii (mai exact: ID-urile lor aleatorii) prin intermediul aplicaÈ›iei lor. Apoi, lista este comparată cu ID-urile aleatorii ale altor utilizatori cu care v-aÈ›i întâlnit, care au fost înregistrate de smartphone-ul dvs. Android, pentru a calcula probabilitatea ca È™i dvs. să fi fost infectat È™i să vă avertizeze dacă este necesar.\n\nPuteÈ›i utiliza comutatorul pentru a dezactiva în orice moment înregistrarea în jurnal a expunerilor.\n\nAplicaÈ›ia nu colectează niciodată date personale, precum numele, adresa sau locaÈ›ia dvs., iar aceste informaÈ›ii nu sunt transmise niciodată altor utilizatori. Nu se pot utiliza ID-urile aleatorii pentru a trage concluzii despre persoane individuale."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Activ"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -555,7 +548,7 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_connection_headline">"DeschideÈ›i conexiunea la internet"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if connection is disabled --> - <string name="settings_tracing_status_connection_body">"ÃŽnregistrarea în jurnal a expunerilor necesită conexiunea la internet pentru a calcula expunerile. PorniÈ›i reÈ›eaua WIFI sau datele celulare din setările dispozitivului dvs."</string> + <string name="settings_tracing_status_connection_body">"ÃŽnregistrarea în jurnal a expunerilor necesită conexiune la internet pentru a calcula riscul dvs. de infectare. PorniÈ›i reÈ›eaua Wi-Fi sau datele mobile din setările dispozitivului dvs."</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card --> <string name="settings_tracing_status_connection_button">"DeschideÈ›i configurările dispozitivului"</string> <!-- XTXT: settings(tracing) - explains the circle progress indicator to the right with the current value --> @@ -686,7 +679,7 @@ <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_headline">"Cum vă putem ajuta?"</string> <!-- YTXT: Body text for technical contact and hotline information page --> - <string name="information_contact_body">"Pentru întrebări tehnice despre aplicaÈ›ia Corona-Warn, contactaÈ›i hotline-ul.\n\nPersoanele cu deficienÈ›e de auz pot utiliza serviciile Tess Relay (interpretare între limba germană scrisă È™i limbajul semnelor) pentru a contacta hotline-ul telefonic. PuteÈ›i descărca software-ul din Google Play."</string> + <string name="information_contact_body">"Pentru întrebări tehnice despre aplicaÈ›ia Corona-Warn, contactaÈ›i hotline-ul nostru la nivel naÈ›ional.\n\nPersoanele cu deficienÈ›e de auz pot utiliza serviciile Tess Relay (interpretare între limba germană scrisă È™i limbajul semnelor) pentru a contacta hotline-ul telefonic. PuteÈ›i descărca software-ul din Google Play."</string> <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_subtitle_phone">"Hotline tehnic:"</string> <!-- XLNK: Button / hyperlink to phone call for technical contact and hotline information page --> @@ -714,7 +707,7 @@ <!-- XHED: Headline for legal information page, publisher section --> <string name="information_legal_headline_publisher">"Publicată de"</string> <!-- YTXT: subtitle for legal information page, publisher section --> - <string name="information_legal_subtitle_publisher">"(responsabilă în conformitate cu § 5, alin. 1 TMG, § 55 alin. 1 RStV, DS-GVO, BDSG)"</string> + <string name="information_legal_subtitle_publisher">"(responsabilă în conformitate cu § 5 (1) TMG, § 18 (1) MStV)"</string> <!-- YTXT: body for legal information page, publisher section --> <string name="information_legal_body_publisher">"Robert Koch Institute"<xliff:g id="line_break">"\n"</xliff:g>"Nordufer 20"<xliff:g id="line_break">"\n"</xliff:g>"13353 Berlin"<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"reprezentată de preÈ™edintele său"</string> <!-- XHED: Headline for legal information page, contact section --> @@ -768,7 +761,7 @@ <!-- XHED: Dialog title for generic web request error --> <string name="submission_error_dialog_web_generic_error_title">"Eroare"</string> <!-- XMSG: Dialog body for generic web request network error with status code --> - <string name="submission_error_dialog_web_generic_network_error_body">"Conexiunea nu a putut fi stabilită (%1$d). ReîncercaÈ›i."</string> + <string name="submission_error_dialog_web_generic_network_error_body">"Se poate să fi pierdut conexiunea la internet. AsiguraÈ›i-vă că sunteÈ›i conectat la internet."</string> <!-- XMSG: Dialog body for generic web request error without status code --> <string name="submission_error_dialog_web_generic_error_body">"Conexiunea nu a putut fi stabilită. ReîncercaÈ›i."</string> <!-- XBUT: Positive button for generic web request error --> @@ -902,7 +895,7 @@ <!-- XHED: Page headline for other warnings screen --> <string name="submission_test_result_positive_steps_warning_others_heading">"AvertizaÈ›i-i pe ceilalÈ›i"</string> <!-- YTXT: Body text for for other warnings screen--> - <string name="submission_test_result_positive_steps_warning_others_body">"PartajaÈ›i ID-urile dvs. aleatorii È™i avertizaÈ›i-i pe ceilalÈ›i.\nAjutaÈ›i la stabilirea riscului de infectare pentru ceilalÈ›i cu mai multă acurateÈ›e, indicând momentul în care aÈ›i observat prima dată simptomele de coronavirus."</string> + <string name="submission_test_result_positive_steps_warning_others_body">"Vă rugăm să partajaÈ›i ID-urile dvs. aleatorii È™i să-i avertizaÈ›i pe ceilalÈ›i.\nAjutaÈ›i la stabilirea riscului de infectare pentru ceilalÈ›i cu mai multă acurateÈ›e, indicând momentul în care aÈ›i observat prima dată simptomele de coronavirus."</string> <!-- XBUT: positive test result : continue button --> <string name="submission_test_result_positive_continue_button">"ÃŽnainte"</string> <!-- XBUT: positive test result : continue button with symptoms--> @@ -960,6 +953,9 @@ <!-- Submission Intro --> <!-- XBUT: Submission introduction next button--> <string name="submission_intro_button_next">"ÃŽnainte"</string> + <!-- YTXT: Description for illustration in submission onboarding--> + <string name="submission_intro_illustration_description">"Un diagnostic de test pozitiv criptat este transmis la sistem, iar acesta va avertiza apoi ceilalÈ›i utilizatori."</string> + <!-- Dispatcher --> <!-- XHED: Page headline for dispatcher menu --> @@ -1047,10 +1043,10 @@ <string name="submission_done_further_info_title">"Alte informaÈ›ii:"</string> <!-- YTXT: submission done further info bullet points --> <string-array name="submission_done_further_info_bullet_points"> - <item>"Perioada de carantină este de obicei de 14 zile. ObservaÈ›i ce simptome aveÈ›i È™i monitorizaÈ›i dezvoltarea acestora."</item> - <item>"Autoritatea de sănătate publică vă va solicita să întocmiÈ›i o listă a persoanelor cu care aÈ›i intrat în contact. Aceasta trebuie să cuprindă toate persoanele cu care aÈ›i avut contact strâns (mai puÈ›in de 2 metri, conversaÈ›ii față în față) timp de peste 15 minute începând cu două zile înainte de a prezenta simptome. PuteÈ›i utiliza jurnalul de contacte în acest scop: trebuie doar să exportaÈ›i intrările È™i apoi să le tipăriÈ›i sau să le expediaÈ›i pe e-mail."</item> + <item>"Perioada de carantină este de obicei de 14 zile. MonitorizaÈ›i cu atenÈ›ie modul în care evoluează simptomele dvs."</item> + <item>"Autoritatea dvs. de sănătate publică vă va solicita să întocmiÈ›i o listă a persoanelor cu care aÈ›i intrat în contact. Aceasta trebuie să cuprindă toate persoanele cu care aÈ›i avut contact strâns (mai puÈ›in de 2 metri, conversaÈ›ii față în față) timp de peste 15 minute începând cu două zile înainte de a vă îmbolnăvi. PuteÈ›i utiliza jurnalul de contacte în acest scop: trebuie doar să exportaÈ›i intrările È™i apoi să le tipăriÈ›i sau să le expediaÈ›i pe e-mail."</item> <item>"LuaÈ›i în considerare în special persoanele care nu vor fi notificate direct de aplicaÈ›ie dacă nu au un smartphone sau dacă nu È™i-au instalat aplicaÈ›ia."</item> - <item>"Chiar È™i când nu mai aveÈ›i simptome È™i vă simÈ›iÈ›i din nou bine, tot puteÈ›i să fiÈ›i contagios."</item> + <item>"Chiar È™i când nu mai aveÈ›i simptome È™i vă simÈ›iÈ›i din nou bine, tot puteÈ›i să fiÈ›i contagios. Vă rugăm să respectaÈ›i perioada de carantină indicată."</item> </string-array> <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Gata"</string> @@ -1113,22 +1109,22 @@ <!-- XBUT: submission contact enter tan button --> <string name="submission_contact_button_enter">"IntroduceÈ›i TAN"</string> <!-- YTXT: Body text for step 1 of contact page --> - <string name="submission_contact_step_1_body">"SunaÈ›i la hotline È™i solicitaÈ›i un TAN:"</string> + <string name="submission_contact_step_1_body">"SunaÈ›i la hotline-ul naÈ›ional È™i solicitaÈ›i un TAN:"</string> <!-- XLNK: Button / hyperlink to phone call for TAN contact page --> <string name="submission_contact_number_display">"0800 7540002"</string> <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"ÃŽnregistraÈ›i testul introducând codul TAN în aplicaÈ›ie."</string> + <string name="submission_contact_step_2_body">"ÃŽnregistraÈ›i testul dvs. introducând codul TAN în aplicaÈ›ie."</string> <!-- YTXT: Body text for operating hours in contact page--> - <string name="submission_contact_operating_hours_body">"Limbi: \nengleză, germană, turcă\n\nProgram de lucru:\nluni - duminică: non-stop\n\nApelul este gratuit."</string> + <string name="submission_contact_operating_hours_body">"Limbi: \nengleză, germană, turcă\n\nProgram de lucru:\n24/7\n\nApelul este gratuit."</string> <!-- YTXT: Body text for technical contact and hotline information page --> <string name="submission_contact_body_other">"Dacă aveÈ›i întrebări legate de starea de sănătate, vă rugăm să contactaÈ›i medicul de familie sau hotline-ul pentru servicii medicale de urgență, la numărul de telefon: 116 117 (Germania) sau 112 (România)."</string> <!-- XACT: Submission contact page title --> <string name="submission_contact_accessibility_title">"SunaÈ›i la hotline È™i solicitaÈ›i un TAN"</string> <!-- XACT: Content Description for submission contact step 1, number has to sync with the display number --> - <string name="submission_contact_step_1_content">"Primul pas: sunaÈ›i la hotline È™i solicitaÈ›i codul dvs. TAN. PuteÈ›i contacta hotline-ul la numărul de telefon 0800 7540002. Programul de lucru este: luni - vineri, 08:00 - 22.00 È™i sâmbătă - duminică, 10:00 - 22:00. Apelul este gratuit."</string> + <string name="submission_contact_step_1_content">"Primul pas: sunaÈ›i la hotline-ul naÈ›ional È™i solicitaÈ›i codul dvs. TAN. PuteÈ›i contacta hotline-ul la numărul de telefon 0800 7540002. Programul de lucru este: luni - vineri, 08:00 - 22.00 È™i sâmbătă - duminică, 10:00 - 22:00. Apelul este gratuit."</string> <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"Al doilea pas: înregistraÈ›i-vă testul cu codul dvs. TAN în aplicaÈ›ie."</string> @@ -1164,7 +1160,7 @@ <!-- XHED: Page title for the various submission status fetch ready --> <string name="submission_status_card_title_ready">"Rezultatul testului dvs. este disponibil"</string> <!-- XHED: Subtitle for the submission status card: invalid --> - <string name="submission_status_card_subtitle_invalid">"Test nevalid"</string> + <string name="submission_status_card_subtitle_invalid">"Testul dvs. nu poate fi evaluat"</string> <!-- XHED: Subtitle for the submission status card: negative --> <string name="submission_status_card_subtitle_negative">"Diagnostic negativ"</string> <!-- XHED: Subtitle for the submission status card: ready --> @@ -1295,7 +1291,7 @@ <!-- XHED: Title for submission statistics card --> <string name="statistics_card_submission_title">"Avertizări de la utilizatorii aplicaÈ›iei"</string> <!-- XTXT: Text displayed in the bottom of submission statistics card--> - <string name="statistics_card_submission_bottom_text">"prin aplicaÈ›ia Corona-Warn"</string> + <string name="statistics_card_submission_bottom_text">"Despre aplicaÈ›ia Corona-Warn"</string> <!-- XTXT: Timestamp refers to today's date in submission and infections cards --> <string name="statistics_primary_value_today">"Astăzi"</string> @@ -1333,8 +1329,8 @@ <!-- XHED: Explanation screen seven day r-value title --> <string name="statistics_explanation_seven_day_r_value_title">"Valoarea R pe 7 zile"</string> <!-- XTXT: Explanation screen seven day r-value text --> - <string name="statistics_explanation_seven_day_r_value_text">"Numărul de reproducÈ›ie R indică câte persoane a infectat mai departe, în medie, o persoană infectată. Valoarea curentă ia în considerare cel mult ultimele 5 zile.\n\nPentru alte informaÈ›ii, consultaÈ›i\nîntrebările frecvente despre statistică."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> + <string name="statistics_explanation_seven_day_r_value_text">"Numărul de reproducÈ›ie R indică câte persoane a infectat mai departe, în medie, o persoană infectată. Valoarea curentă ia în considerare cel mult ultimele 5 zile.\n\nPentru alte informaÈ›ii, consultaÈ›i întrebările frecvente:\nÃŽntrebări frecvente despre statistică."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> <string name="statistics_explanation_seven_day_r_link_label">"ÃŽntrebări frecvente despre statistică."</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Legendă"</string> @@ -1355,7 +1351,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"Tendință"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"DirecÈ›ia săgeÈ›ii indică dacă tendinÈ›a este în creÈ™tere, în scădere sau dacă rămâne constantă – mai exact, demonstrează o abatere de sub 1\% în comparaÈ›ie cu ziua precedentă sau de 5\% în comparaÈ›ie cu săptămâna precedentă. Culoarea indică faptul că tendinÈ›a este pozitivă (verde), negativă (roÈ™u) sau neutră (gri). TendinÈ›a compară valoarea din ziua precedentă cu valoarea de acum două zile sau, pentru tendinÈ›ele pe 7 zile, valoarea medie din ultimele 7 zile cu valoarea medie din ultimele 7 zile anterioare acesteia."</string> + <string name="statistics_explanation_trend_text">"DirecÈ›ia săgeÈ›ii indică dacă tendinÈ›a este în creÈ™tere, în scădere sau dacă rămâne constantă – mai exact, demonstrează o abatere de sub 1%% în comparaÈ›ie cu ziua precedentă sau de 5%% în comparaÈ›ie cu săptămâna precedentă. Culoarea indică faptul că tendinÈ›a este pozitivă (verde), negativă (roÈ™u) sau neutră (gri). TendinÈ›a compară valoarea din ziua precedentă cu valoarea de acum două zile sau, pentru tendinÈ›ele pe 7 zile, valoarea medie din ultimele 7 zile cu valoarea medie din ultimele 7 zile anterioare acesteia."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"Pot fi afiÈ™ate următoarele tendinÈ›e:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1371,6 +1367,11 @@ <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"O imagine abstractă a unui smartphone cu patru substituenÈ›i pentru informaÈ›ii"</string> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"Mozaic statistică"</string> + <!-- XTXT: Statistics Card Navigation Announcement --> + <string name="accessibility_statistics_card_navigation_information">"GlisaÈ›i orizontal între mozaicuri pentru a afiÈ™a mai multe statistici"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1406,6 +1407,9 @@ <string name="errors_risk_detection_limit_reached_title">"Limita a fost deja atinsă"</string> <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_description">"Astăzi nu mai este posibilă verificarea expunerii, deoarece aÈ›i atins numărul maxim de verificări pe zi definit de sistemul dvs. de operare. VerificaÈ›i din nou mâine starea riscului dvs."</string> + <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> + <string name="errors_ssl_certificate_deactivated">"Vă rugăm să activaÈ›i certificatul de securitate a sistemelor de la T-Systems Enterprise Services GmbH, T-TeleSec GlobalRoot Class 2 pe smartphone-ul dvs. Pentru mai multe informaÈ›ii, consultaÈ›i întrebările frecvente de la https://coronawarn.app/en/ secÈ›iunea „CAUSE: 2001â€."</string> + <!-- #################################### Generic Error Messages ###################################### --> 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 2dc0545f0cfd9733b84ec131e7f66f2524a6a3a4..b39e54257776736f6b8eeb25b6b428eaca83979c 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 @@ -11,7 +11,7 @@ <string name="contact_diary_person_list_no_items_title">"Henüz hiçbir kiÅŸi tanımlanmadı"</string> <string name="contact_diary_person_list_no_items_subtitle">"Bir kiÅŸi oluÅŸturun ve temas güncenize ekleyin."</string> <string name="contact_diary_person_bottom_sheet_title">"KiÅŸi"</string> - <string name="contact_diary_person_bottom_sheet_text_input_hint">"Ad, soyadı"</string> + <string name="contact_diary_person_bottom_sheet_text_input_hint">"Ad"</string> <string name="contact_diary_person_bottom_sheet_save_button">"Kaydet"</string> <string name="contact_diary_location_bottom_sheet_title">"Yer"</string> <string name="contact_diary_location_bottom_sheet_text_input_hint">"Tanım"</string> @@ -40,6 +40,8 @@ <string name="contact_diary_onboarding_functionality_fourth_section">"DilediÄŸiniz zaman güncenizdeki kiÅŸi ve yer giriÅŸlerini kaldırabilirsiniz. Günce giriÅŸleri 16 günün ardından otomatik olarak silinecektir."</string> <!-- XTXT: Contact diary onboarding screen fifth functionality --> <string name="contact_diary_onboarding_functionality_fifth_section">"Temas güncenizi düz metin biçiminde dışa aktarabilir ve ardından giriÅŸleri yazdırabilir, düzenleyebilir ya da gerekli olduÄŸunda kamu saÄŸlığı yetkilinize sunabilirsiniz."</string> + <!-- XTXT: Contact diary onboarding screen sixth functionality --> + <string name="contact_diary_onboarding_functionality_sixth_section">"Belirtilen maruz kalmaların kaydettiÄŸiniz kiÅŸilerle ve yerlerle ilgili olması ÅŸart deÄŸildir. Lütfen herhangi bir yanlış çıkarımda bulunmayın."</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_title">"Temas Güncesi"</string> <!-- XTXT: Body for legal information of the contact diary onboarding screen --> @@ -61,6 +63,17 @@ <!-- XTXT: Header for contact diary overview screen --> <string name="contact_diary_overview_header">"BaÅŸlangıç Sayfası"</string> + + <!-- XTXT: Title for contact diary overview screen high risk information --> + <string name="contact_diary_high_risk_title">"Daha Yüksek Risk"</string> + <!-- XTXT: Title for contact diary overview screen low risk information --> + <string name="contact_diary_low_risk_title">"Düşük Risk"</string> + <!-- XTXT: Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body">"uygulama tarafından deÄŸerlendirilen karşılaÅŸmalar temel alınır."</string> + <!-- XTXT: Extended Body for contact diary overview screen risk information --> + <string name="contact_diary_risk_body_extended">"uygulama tarafından deÄŸerlendirilen karşılaÅŸmalar temel alınır. KaydettiÄŸiniz kiÅŸilerle ve yerlerle ilgili olmaları ÅŸart deÄŸildir."</string> + + <!-- XTXT: content description of contact journal image on home screen --> <string name="contact_diary_homescreen_card_image_content_description">"Yer iÅŸareti bulunan kapalı bir kitap"</string> <!-- XTXT: content description of contact journal header image on onboarding screen --> 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 new file mode 100644 index 0000000000000000000000000000000000000000..860dae78281978aa757cbb869a877d1bb20be730 --- /dev/null +++ b/Corona-Warn-App/src/main/res/values-tr/release_info_strings.xml @@ -0,0 +1,15 @@ +<?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"> + <!-- #################################### + Release Info Screen 1.12 + ###################################### --> + + <!-- XHED: Title for the release info screen --> + <string name="release_info_header">"Yeni Özellikler"</string> + <!-- XHED: Version title for the release info screen --> + <string name="release_info_version_title">"Sürüm %1$s"</string> + <!-- XTXT: Description for the release info screen --> + <string name="release_info_version_body">"Hata düzeltmelerinin yanında bu güncelleme ile birlikte yeni ve geliÅŸmiÅŸ özellikler sunulmaktadır."</string> + <!-- XBUT: Continue button for the release info screen --> + <string name="release_info_continue_button">"Sonraki"</string> + +</resources> \ No newline at end of file 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 4e298ed5e780c709138023c05666c70e32ff0fc4..38f72d512203222bdcdf48920b8d7ee18ab64545 100644 --- a/Corona-Warn-App/src/main/res/values-tr/strings.xml +++ b/Corona-Warn-App/src/main/res/values-tr/strings.xml @@ -22,12 +22,6 @@ <!-- NOTR --> <string name="preference_timestamp_manual_diagnosis_keys_retrieval"><xliff:g id="preference">"preference_timestamp_manual_diagnosis_keys_retrieval"</xliff:g></string> <!-- NOTR --> - <string name="preference_background_job_allowed"><xliff:g id="preference">"preference_background_job_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_mobile_data_allowed"><xliff:g id="preference">"preference_mobile_data_enabled"</xliff:g></string> - <!-- NOTR --> - <string name="preference_registration_token"><xliff:g id="preference">"preference_registration_token"</xliff:g></string> - <!-- NOTR --> <string name="preference_device_pairing_successful_time"><xliff:g id="preference">"preference_device_pairing_successful_time"</xliff:g></string> <!-- NOTR --> <string name="preference_initial_tracing_activation_time"><xliff:g id="preference">"preference_initial_tracing_activation_time"</xliff:g></string> @@ -173,8 +167,8 @@ <item quantity="few">"%1$d gün için artmış riskli maruz kalmalar"</item> <item quantity="many">"%1$d gün için artmış riskli maruz kalmalar"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"En son %1$s"</string> + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"En son %1$s"</string> <!-- #################################### Risk Card - Progress @@ -230,11 +224,11 @@ <!-- XHED: App overview subtitle for tracing explanation--> <string name="main_overview_subtitle_tracing">"Maruz Kalma Günlüğü"</string> <!-- YTXT: App overview body text about tracing --> - <string name="main_overview_body_tracing">"Maruz kalma günlüğü, uygulamanın üç temel özelliÄŸinden biridir. Bu özelliÄŸi etkinleÅŸtirdiÄŸinizde, diÄŸer kiÅŸilerin akıllı telefonlarıyla karşılaÅŸmalarınız günlüğe kaydedilir. BaÅŸka bir iÅŸlem yapmanız gerekmez."</string> + <string name="main_overview_body_tracing">"Maruz kalma günlüğü, uygulamanın üç temel özelliÄŸinden biridir. Bu özelliÄŸi etkinleÅŸtirdiÄŸinizde, diÄŸer kiÅŸilerin akıllı telefonlarıyla karşılaÅŸmalarınız otomatik olarak günlüğe kaydedilir."</string> <!-- XHED: App overview subtitle for risk explanation --> <string name="main_overview_subtitle_risk">"Enfeksiyon Riski"</string> <!-- YTXT: App overview body text about risk levels --> - <string name="main_overview_body_risk">"Son 14 gün içerisinde koronavirüs tanısı konan biriyle temasınız olduysa uygulama kiÅŸisel enfeksiyon riskinizi hesaplar. Bunu, maruz kalma süresini ve yakınlığını ölçerek gerçekleÅŸtirir."</string> + <string name="main_overview_body_risk">"Maruz kalma günlüğü etkin olduÄŸunda uygulama kiÅŸisel enfeksiyon riskinizi hesaplar. Bunu, son 14 günde koronavirüs tanısı almış kiÅŸilere maruz kalma sürenizi ve yakınlığınızı ölçerek gerçekleÅŸtirir."</string> <!-- XHED: App overview subtitle for risk level list --> <string name="main_overview_subtitle_risk_levels">"Åžu risk durumu gösterilebilir:"</string> <!-- XTXT: App overview increased risk level --> @@ -246,7 +240,7 @@ <!-- XHED: App overview subtitle for test procedure explanation --> <string name="main_overview_headline_test">"DiÄŸer Kullanıcıları Bilgilendirme"</string> <!-- YTXT: App overview body text about rest procedure --> - <string name="main_overview_body_test">"DiÄŸer bir temel özellik, testinizi kaydetme ve sonucunu alma özelliÄŸidir. Koronavirüs tanısı alırsanız diÄŸer kiÅŸileri bilgilendirerek enfeksiyon zincirini kırabilirsiniz."</string> + <string name="main_overview_body_test">"Testinizi kaydedebilir ve test sonucunuzu uygulamada alabilirsiniz. Koronavirüs tanısı alırsanız diÄŸer kiÅŸileri bilgilendirerek enfeksiyon zincirini kırabilirsiniz."</string> <!-- XHED: App overview headline for glossary --> <string name="main_overview_headline_glossary">"Terimlerin Tanımı:"</string> <!-- XHED: App overview subtitle for glossary key storage --> @@ -256,7 +250,7 @@ <!-- XHED: App overview subtitle for glossary risk calculation --> <string name="main_overview_subtitle_glossary_calculation">"Maruz Kalma Denetimi"</string> <!-- YTXT: App overview body for glossary risk calculation --> - <string name="main_overview_body_glossary_calculation">"Maruz kalma günlüğü verileri alınır ve diÄŸer kullanıcıların bildirilen enfeksiyonları ile senkronize edilir. Risk durumunuz günde birkaç kez otomatik olarak kontrol edilir."</string> + <string name="main_overview_body_glossary_calculation">"Maruz kalma günlüğü verileri alınır ve diÄŸer kullanıcıların risk bildirimleri ile senkronize edilir. Risk durumunuz günde birkaç kez otomatik olarak kontrol edilir."</string> <!-- XHED: App overview subtitle for glossary contact --> <string name="main_overview_subtitle_glossary_contact">"Maruz Kalma Riski"</string> <!-- YTXT: App overview body for glossary contact --> @@ -328,6 +322,8 @@ <string name="risk_details_information_body_low_risk">"Daha sonra COVID-19 tanısı konan kiÅŸilere maruz kaldığınıza dair bir günlük kaydı oluÅŸturulmadığı veya bu kiÅŸilerle yalnızca kısa süreyle ve uzak mesafeden karşılaÅŸtığınız için enfeksiyon riskiniz düşüktür."</string> <!-- YTXT: risk details - low risk explanation text with encounter with low risk --> <string name="risk_details_information_body_low_risk_with_encounter">"Enfeksiyon riskiniz, maruz kalma günlüğünün verileri kullanılarak yerel olarak akıllı telefonunuzda hesaplanır. Hesaplamada koronavirüs tanısı konan kiÅŸilere maruz kalma mesafesi ve süresinin yanında potansiyel enfeksiyon bulaÅŸtırma durumu da göz önünde bulundurulur. Enfeksiyon riskiniz bir baÅŸkası tarafından görüntülenemez ya da bir baÅŸkasına aktarılamaz."</string> + <!-- YTXT: risk details - increased risk explanation text with variable date since last contact --> + <string name="risk_details_information_body_increased_risk_date">"En son %1$s tarihinde, koronavirüs tanısı konan en az bir kiÅŸiyle daha uzun süreyle ve yakın mesafeden maruz kalma yaÅŸadığınız için enfeksiyon riskiniz daha yüksektir."</string> <!-- YTXT: risk details - increased risk explanation text with variable for day(s) since last contact --> <plurals name="risk_details_information_body_increased_risk"> <item quantity="one">"En son %1$s gün önce, koronavirüs tanısı konan en az bir kiÅŸiyle daha uzun süreyle ve yakın mesafeden maruz kalma yaÅŸadığınız için enfeksiyon riskiniz daha yüksektir."</item> @@ -352,9 +348,6 @@ <string name="risk_details_explanation_dialog_title">"Maruz kalma günlüğü iÅŸlevi hakkında bilgi"</string> <!-- YTXT: one time risk explanation dialog - pointing to the faq page for more information--> <string name="risk_details_explanation_dialog_faq_body">"Daha fazla bilgi için lütfen SSS sayfamıza bakın."</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> - <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Risk Durumunuz"</string> <!-- YTXT: risk details - deadman notification text --> @@ -425,7 +418,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"Enfeksiyon riski taşıyıp taşımadığınızı belirlemek için maruz kalma günlüğünü etkinleÅŸtirmeniz gerekir."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"Maruz kalma günlüğü, Android akıllı telefonunuzun Bluetooth üzerinden diÄŸer kullanıcıların ÅŸifrelenmiÅŸ rastgele kimliklerini alması ve size ait rastgele kimlikleri diÄŸer kullanıcıların akıllı telefonlarına aktarmasıyla çalışır. Maruz kalma günlüğü, dilediÄŸiniz zaman devre dışı bırakılabilir. "</string> + <string name="onboarding_tracing_body">"Maruz kalma günlüğü, Android akıllı telefonunuzun Bluetooth üzerinden diÄŸer uygulama kullanıcılarının ÅŸifrelenmiÅŸ rastgele kimliklerini alması ve size ait rastgele kimlikleri diÄŸer kullanıcıların akıllı telefonlarına aktarmasıyla çalışır. Maruz kalma günlüğü, dilediÄŸiniz zaman devre dışı bırakılabilir."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"ÅžifrelenmiÅŸ rastgele kimlikler diÄŸer kullanıcılara yalnızca tarih, süre ve yakınlık (sinyal gücü ile hesaplanır) hakkındaki bilgileri aktarır. KiÅŸilerin kimlikleri rastgele kimliklere göre belirlenemez."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -475,9 +468,9 @@ <!-- XACT: Onboarding (test) page title --> <string name="onboarding_test_accessibility_title">"EtkinleÅŸtirme sayfası 5/6: Koronavirüs Tanısı Aldıysanız"</string> <!-- XHED: onboarding(test) - about positive tests --> - <string name="onboarding_test_headline">"Koronavirüs tanısı aldıysanız..."</string> + <string name="onboarding_test_headline">"Koronavirüs tanısı aldıysanız"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> - <string name="onboarding_test_subtitle">"… lütfen bunu Corona-Warn-App\'te belirtin. Test sonuçlarınızı paylaÅŸmanız gönüllülük esasına dayalıdır ve güvenlidir. Lütfen herkesin saÄŸlığı için bunu yapın."</string> + <string name="onboarding_test_subtitle">"Pozitif test sonucu alırsanız lütfen bunu uygulamada belirtin. Test sonuçlarınızı paylaÅŸmanız gönüllülük esasına dayalıdır ve güvenlidir. Lütfen herkesin saÄŸlığı için bunu yapın."</string> <!-- YTXT: onboarding(test) - explain test --> <string name="onboarding_test_body">"Bildiriminiz güvenli olarak ÅŸifrelenir ve güvenli bir sunucuda iÅŸlenir. Akıllı telefonunuza ÅŸifrelenmiÅŸ rastgele kimlikleri toplanan kiÅŸilere bundan sonra atmaları gereken adımlarla birlikte bir uyarı gönderilir."</string> <!-- XACT: onboarding(test) - illustraction description, header image --> @@ -527,7 +520,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"Konum hizmetleri devre dışı bırakıldı"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"Uygulamanın, enfeksiyon riski altında olup olmadığınızı belirleyebilmesi için maruz kalma günlüğü özelliÄŸini etkinleÅŸtirmeniz gerekir. Maruz kalma günlüğü özelliÄŸi uluslararası olarak çalışır; yani, kullanıcılara iliÅŸkin tüm olası maruz kalmalar da diÄŸer resmi koronavirüs uygulamalarıyla tespit edilir.\n\nMaruz kalma günlüğü özelliÄŸi, Android akıllı telefonunuzun Bluetooth üzerinden diÄŸer kullanıcıların ÅŸifrelenmiÅŸ rastgele kimliklerini alması ve size ait rastgele kimlikleri diÄŸer kullanıcıların akıllı telefonlarına aktarmasıyla çalışır. Uygulama her gün, virüs testi pozitif olan ve uygulamaları aracılığıyla bu bilgileri (özellikle rastgele kimliklerini) gönüllü olarak paylaÅŸan tüm kullanıcıların rastgele kimliklerini içeren listeler ile birlikte bu kullanıcıların belirtilerinin baÅŸlangıcına iliÅŸkin gönüllü olarak verilen tüm bilgileri indirir. Ardından bu liste, sizin de enfekte olma olasılığınızı hesaplamak ve gereken durumlarda sizi uyarmak için Android akıllı telefonunuzun kaydetttiÄŸi ve karşılaÅŸtığınız diÄŸer kullanıcıların rastgele kimliklerle karşılaÅŸtırılır.\n\nDüğmeyi kullanarak dilediÄŸiniz zaman maruz kalma günlüğünü devre dışı bırakabilirsiniz.\n\nUygulama hiçbir durumda adınız, adresiniz veya konumunuz gibi kiÅŸisel verileri toplamaz ve bu bilgileri diÄŸer kullanıcılara aktarmaz. Rastgele kimlikleri kullanarak kiÅŸilerin kimlikleri hakkında sonuçlar çıkarılamaz."</string> + <string name="settings_tracing_body_text">"Uygulamanın, enfeksiyon riski altında olup olmadığınızı belirleyebilmesi için maruz kalma günlüğü özelliÄŸini etkinleÅŸtirmeniz gerekir. Maruz kalma günlüğü özelliÄŸi uluslararası olarak çalışır; yani, kullanıcılara iliÅŸkin tüm olası maruz kalmalar da diÄŸer resmi koronavirüs uygulamalarıyla tespit edilir.\n\nMaruz kalma günlüğü özelliÄŸi, Android akıllı telefonunuzun Bluetooth üzerinden diÄŸer uygulama kullanıcılarının ÅŸifrelenmiÅŸ rastgele kimliklerini alması ve size ait rastgele kimlikleri diÄŸer kullanıcıların akıllı telefonlarına aktarmasıyla çalışır. Uygulama her gün, virüs testi pozitif olan ve uygulamaları aracılığıyla bu bilgileri (özellikle rastgele kimliklerini) gönüllü olarak paylaÅŸan tüm kullanıcıların rastgele kimliklerini içeren listeler ile birlikte bu kullanıcıların belirtilerinin baÅŸlangıcına iliÅŸkin gönüllü olarak verilen tüm bilgileri indirir. Ardından bu liste, sizin de enfekte olma olasılığınızı hesaplamak ve gereken durumlarda sizi uyarmak için Android akıllı telefonunuzun kaydetttiÄŸi ve karşılaÅŸtığınız diÄŸer kullanıcıların rastgele kimliklerle karşılaÅŸtırılır.\n\nDüğmeyi kullanarak dilediÄŸiniz zaman maruz kalma günlüğünü devre dışı bırakabilirsiniz.\n\nUygulama hiçbir durumda adınız, adresiniz veya konumunuz gibi kiÅŸisel verileri toplamaz ve bu bilgileri diÄŸer kullanıcılara aktarmaz. Rastgele kimlikleri kullanarak kiÅŸilerin kimlikleri hakkında sonuçlar çıkarılamaz."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Etkin"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -555,7 +548,7 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <string name="settings_tracing_status_connection_headline">"İnternet baÄŸlantısını aç"</string> <!-- XTXT: settings(tracing) - explains user what to do on card if connection is disabled --> - <string name="settings_tracing_status_connection_body">"Maruz kalma günlüğü, maruz kalmaları hesaplamak için İnternet baÄŸlantısı gerektirir. Lütfen cihazınızın ayarlarında WiFi\'ı veya mobil veriyi açın."</string> + <string name="settings_tracing_status_connection_body">"Maruz kalma günlüğü, enfeksiyon riskinizi hesaplamak için İnternet baÄŸlantısı gerektirir. Lütfen cihazınızın ayarlarında Wi-Fi\'ı veya mobil veriyi açın."</string> <!-- XBUT: settings(tracing) - go to operating system settings button on card --> <string name="settings_tracing_status_connection_button">"Cihaz Ayarlarını Aç"</string> <!-- XTXT: settings(tracing) - explains the circle progress indicator to the right with the current value --> @@ -686,7 +679,7 @@ <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_headline">"Size nasıl yardımcı olabiliriz?"</string> <!-- YTXT: Body text for technical contact and hotline information page --> - <string name="information_contact_body">"Corona-Warn-App hakkında teknik sorularınız için lütfen yardım hattımızla iletiÅŸime geçin.\n\nİşitme engeli bulunan kiÅŸiler yardım hattı ile iletiÅŸime geçmek için Tess Relay hizmetlerini (Almanca yazı dili ile iÅŸaret dili arasında tercüme) kullanabilir. Yazılımı Google Play\'den indirebilirsiniz."</string> + <string name="information_contact_body">"Corona-Warn-App hakkında teknik sorularınız için lütfen ulusal yardım hattımızla iletiÅŸime geçin.\n\nİşitme engeli bulunan kiÅŸiler yardım hattı ile iletiÅŸime geçmek için Tess Relay hizmetlerini (Almanca yazı dili ile iÅŸaret dili arasında tercüme) kullanabilir. Yazılımı Google Play\'den indirebilirsiniz."</string> <!-- XHED: Subtitle for technical contact and hotline information page --> <string name="information_contact_subtitle_phone">"Teknik yardım hattı:"</string> <!-- XLNK: Button / hyperlink to phone call for technical contact and hotline information page --> @@ -714,7 +707,7 @@ <!-- XHED: Headline for legal information page, publisher section --> <string name="information_legal_headline_publisher">"Yayınlayan"</string> <!-- YTXT: subtitle for legal information page, publisher section --> - <string name="information_legal_subtitle_publisher">"(§ 5, paragraf 1 TMG, § 55 paragraf 1 RStV, DS-GVO, BDSG uyarınca sorumludur)"</string> + <string name="information_legal_subtitle_publisher">"(§ 5 (1) TMG, § 18 (1) MStV uyarınca sorumludur)"</string> <!-- YTXT: body for legal information page, publisher section --> <string name="information_legal_body_publisher">"Robert Koch Institute"<xliff:g id="line_break">"\n"</xliff:g>"Nordufer 20"<xliff:g id="line_break">"\n"</xliff:g>"13353 Berlin"<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"BaÅŸkanı tarafından temsil edilir"</string> <!-- XHED: Headline for legal information page, contact section --> @@ -768,7 +761,7 @@ <!-- XHED: Dialog title for generic web request error --> <string name="submission_error_dialog_web_generic_error_title">"Hata"</string> <!-- XMSG: Dialog body for generic web request network error with status code --> - <string name="submission_error_dialog_web_generic_network_error_body">"BaÄŸlantı kurulamadı (%1$d). Lütfen tekrar deneyin."</string> + <string name="submission_error_dialog_web_generic_network_error_body">"İnternet baÄŸlantınız kesilmiÅŸ olabilir. Lütfen internet baÄŸlantınızın olduÄŸundan emin olun."</string> <!-- XMSG: Dialog body for generic web request error without status code --> <string name="submission_error_dialog_web_generic_error_body">"BaÄŸlantı kurulamadı. Lütfen tekrar deneyin."</string> <!-- XBUT: Positive button for generic web request error --> @@ -902,7 +895,7 @@ <!-- XHED: Page headline for other warnings screen --> <string name="submission_test_result_positive_steps_warning_others_heading">"DiÄŸer Kullanıcıları Uyarın"</string> <!-- YTXT: Body text for for other warnings screen--> - <string name="submission_test_result_positive_steps_warning_others_body">"Rastgele kimliklerinizi paylaşın ve diÄŸer kullanıcıları uyarın.\nHerhangi bir koronavirüs belirtisini ne zaman ilk kez fark ettiÄŸinizi de belirterek diÄŸer kullanıcıların enfeksiyon riskini daha doÄŸru ÅŸekilde belirlemeye yardımcı olun."</string> + <string name="submission_test_result_positive_steps_warning_others_body">"Lütfen rastgele kimliklerinizi paylaşın ve diÄŸer kullanıcıları uyarın.\nHerhangi bir koronavirüs belirtisini ne zaman ilk kez fark ettiÄŸinizi de belirterek diÄŸer kullanıcıların enfeksiyon riskini daha doÄŸru ÅŸekilde belirlemeye yardımcı olun."</string> <!-- XBUT: positive test result : continue button --> <string name="submission_test_result_positive_continue_button">"Sonraki"</string> <!-- XBUT: positive test result : continue button with symptoms--> @@ -947,7 +940,7 @@ <!-- XHED: Page title for TAN submission pge --> <string name="submission_tan_title">"TAN gir"</string> <!-- YTXT: Body text for the tan submission page --> - <string name="submission_tan_body">"Lütfen size verilen 10 haneli TAN\'yi girin."</string> + <string name="submission_tan_body">"Size verilen 10 haneli TAN\'yi girin."</string> <!-- XBUT: Submit TAN button --> <string name="submission_tan_button_text">"Sonraki"</string> <!-- XACT: Submission Tan page title --> @@ -960,6 +953,9 @@ <!-- Submission Intro --> <!-- XBUT: Submission introduction next button--> <string name="submission_intro_button_next">"Sonraki"</string> + <!-- YTXT: Description for illustration in submission onboarding--> + <string name="submission_intro_illustration_description">"ÅžifrelenmiÅŸ bir pozitif test tanısı sisteme aktarılır ve diÄŸer kullanıcılar uyarılır."</string> + <!-- Dispatcher --> <!-- XHED: Page headline for dispatcher menu --> @@ -1047,10 +1043,10 @@ <string name="submission_done_further_info_title">"DiÄŸer bilgiler:"</string> <!-- YTXT: submission done further info bullet points --> <string-array name="submission_done_further_info_bullet_points"> - <item>"Karantina süreniz genellikle 14 gündür. Lütfen belirtilerinizi gözlemleyin ve belirtilerinizin nasıl ortaya çıktığını izleyin."</item> - <item>"Kamu saÄŸlığı yetkilisi, temasa geçtiÄŸiniz kiÅŸilerin bir listesini oluÅŸturmanızı isteyecektir. Bu listeye, belirtileriniz ortaya çıkmadan önceki iki gün içinde 15 dakikadan uzun süreyle yakın temas halinde (2 metreden kısa mesafede, yüz yüze görüşme) olduÄŸunuz herkesi dahil etmeniz gerekir. Bunun için temas güncenizi kullanabilirsiniz: GiriÅŸleri dışa aktarmanız ve ardından yazdırmanız ya da e-posta yoluyla göndermeniz yeterlidir."</item> + <item>"Karantina süreniz genellikle 14 gündür. Lütfen belirtilerinizi nasıl geliÅŸtiÄŸini yakından izleyin."</item> + <item>"Kamu saÄŸlığı yetkiliniz, temasa geçtiÄŸiniz kiÅŸilerin bir listesini oluÅŸturmanızı isteyecektir. Bu listeye, hasta olmadan önceki iki gün içinde 15 dakikadan uzun süreyle yakın temas halinde (2 metreden kısa mesafede, yüz yüze görüşme) olduÄŸunuz herkesi dahil etmeniz gerekir. Bunun için temas güncenizi kullanabilirsiniz: GiriÅŸleri dışa aktarmanız ve ardından yazdırmanız ya da e-posta yoluyla göndermeniz yeterlidir."</item> <item>"Lütfen özellikle akıllı telefonu olmadığı veya uygulamayı yüklemediÄŸi için doÄŸrudan uygulamanın uyarmayacağı kiÅŸileri göz önünde bulundurun."</item> - <item>"Hiçbir belirtiniz kalmasa ve kendinizi iyi hissetseniz dahi yine de hastalığı bulaÅŸtırabilirsiniz."</item> + <item>"Hiçbir belirtiniz kalmasa ve kendinizi iyi hissetseniz dahi yine de hastalığı bulaÅŸtırabilirsiniz. Bu nedenle lütfen belirlenen karantina süresine uyun."</item> </string-array> <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Tamamlandı"</string> @@ -1113,22 +1109,22 @@ <!-- XBUT: submission contact enter tan button --> <string name="submission_contact_button_enter">"TAN gir"</string> <!-- YTXT: Body text for step 1 of contact page --> - <string name="submission_contact_step_1_body">"Yardım hattını arayın ve TAN talep edin:"</string> + <string name="submission_contact_step_1_body">"Ulusal yardım hattını arayın ve TAN talep edin:"</string> <!-- XLNK: Button / hyperlink to phone call for TAN contact page --> <string name="submission_contact_number_display">"0800 7540002"</string> <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"Uygulamaya TAN girerek testi kaydedin."</string> + <string name="submission_contact_step_2_body">"Uygulamaya TAN girerek testinizi kaydedin."</string> <!-- YTXT: Body text for operating hours in contact page--> - <string name="submission_contact_operating_hours_body">"Diller: \nİngilizce, Almanca, Türkçe\n\nMesai saatleri:\nPazartesi - Pazar: 24 saat\n\nArama ücretsizdir."</string> + <string name="submission_contact_operating_hours_body">"Diller: \nİngilizce, Almanca, Türkçe\n\nMesai saatleri:\n7 gün 24 saat\n\nArama ücretsizdir."</string> <!-- YTXT: Body text for technical contact and hotline information page --> <string name="submission_contact_body_other">"SaÄŸlıkla ilgili tüm sorularınız için lütfen aile hekiminizle veya tıbbi acil servis yardım hattı ile iletiÅŸime geçin. Telefon: 116 117."</string> <!-- XACT: Submission contact page title --> <string name="submission_contact_accessibility_title">"Yardım hattını arayın ve TAN talep edin"</string> <!-- XACT: Content Description for submission contact step 1, number has to sync with the display number --> - <string name="submission_contact_step_1_content">"İlk adımda yardım hattını arayıp TAN\'nizi talep edersiniz. 0800 7540002 numaralı telefondan yardım hattına ulaÅŸabilirsiniz. Mesai saatleri, Pazartesi - Cuma 8.00 ile 22.00 ve Cumartesi ve Pazar günleri 10.00 ile 22.00 arasıdır. Arama ücretsizdir."</string> + <string name="submission_contact_step_1_content">"İlk adımda ulusal yardım hattını arayıp TAN\'nizi talep edersiniz. 0800 7540002 numaralı telefondan yardım hattına ulaÅŸabilirsiniz. Mesai saatleri, Pazartesi - Cuma 8.00 ile 22.00 ve Cumartesi ve Pazar günleri 10.00 ile 22.00 arasıdır. Arama ücretsizdir."</string> <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"İkinci adımda, TAN\'nizi kullanarak testinizi uygulamaya kaydedersiniz."</string> @@ -1164,7 +1160,7 @@ <!-- XHED: Page title for the various submission status fetch ready --> <string name="submission_status_card_title_ready">"Test Sonucunuz Çıktı"</string> <!-- XHED: Subtitle for the submission status card: invalid --> - <string name="submission_status_card_subtitle_invalid">"Geçersiz test"</string> + <string name="submission_status_card_subtitle_invalid">"Testiniz deÄŸerlendirilemiyor"</string> <!-- XHED: Subtitle for the submission status card: negative --> <string name="submission_status_card_subtitle_negative">"Negatif Tanı"</string> <!-- XHED: Subtitle for the submission status card: ready --> @@ -1295,7 +1291,7 @@ <!-- XHED: Title for submission statistics card --> <string name="statistics_card_submission_title">"Uygulama Kullanıcılarının Uyarıları"</string> <!-- XTXT: Text displayed in the bottom of submission statistics card--> - <string name="statistics_card_submission_bottom_text">"Corona-Warn-App üzerinden"</string> + <string name="statistics_card_submission_bottom_text">"Corona-Warn-App Hakkında"</string> <!-- XTXT: Timestamp refers to today's date in submission and infections cards --> <string name="statistics_primary_value_today">"Bugün"</string> @@ -1333,9 +1329,9 @@ <!-- XHED: Explanation screen seven day r-value title --> <string name="statistics_explanation_seven_day_r_value_title">"7 Günlük R DeÄŸeri"</string> <!-- XTXT: Explanation screen seven day r-value text --> - <string name="statistics_explanation_seven_day_r_value_text">"Reprodüksiyon deÄŸeri R, enfekte olan bir kiÅŸinin ortalama olarak kaç kiÅŸiyi enfekte ettiÄŸini belirtir. Geçerli deÄŸerde, en fazla son 5 günün verileri dikkate alınır.\n\nDaha fazla bilgi için\nistatistiklere iliÅŸkin SSS bölümüne bakın."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> - <string name="statistics_explanation_seven_day_r_link_label">"SSS bölümüne bakın."</string> + <string name="statistics_explanation_seven_day_r_value_text">"Reprodüksiyon deÄŸeri R, enfekte olan bir kiÅŸinin ortalama olarak kaç kiÅŸiyi enfekte ettiÄŸini belirtir. Geçerli deÄŸerde, en fazla son 5 günün verileri dikkate alınır.\n\nDaha fazla bilgi için SSS bölümüne bakın:\nistatistikler için SSS."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> + <string name="statistics_explanation_seven_day_r_link_label">"istatistikler için SSS."</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Açıklama"</string> <!-- XHED: Explanation screen period title --> @@ -1355,7 +1351,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"Trend"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"Ok yönü trendin arttığını, azaldığını ya da sabit kaldığını belirtir. Sabit kalması, önceki güne göre %1’den az veya önceki haftaya göre %5’ten az sapma olduÄŸu anlamına gelir. Renk ise bu trendin pozitif (yeÅŸil), negatif (kırmızı) ya da nötr (gri) olduÄŸunu belirtir. Trend için, önceki güne ait deÄŸer iki gün önceki deÄŸerle karşılaÅŸtırılır ya da 7 günlük trendler için son 7 güne ait ortalama deÄŸer ondan önceki 7 güne ait ortalama deÄŸerle karşılaÅŸtırılır."</string> + <string name="statistics_explanation_trend_text">"Ok yönü trendin arttığını, azaldığını ya da sabit kaldığını belirtir. Sabit kalması, önceki güne göre %%1’den az veya önceki haftaya göre %%5’ten az sapma olduÄŸu anlamına gelir. Renk ise bu trendin pozitif (yeÅŸil), negatif (kırmızı) ya da nötr (gri) olduÄŸunu belirtir. Trend için, önceki güne ait deÄŸer iki gün önceki deÄŸerle karşılaÅŸtırılır ya da 7 günlük trendler için son 7 güne ait ortalama deÄŸer ondan önceki 7 güne ait ortalama deÄŸerle karşılaÅŸtırılır."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"Åžu trendler gösterilebilir:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1371,6 +1367,11 @@ <!-- XACT: Statistics explanation illustration description --> <string name="statistics_explanation_illustration_description">"Bilgi için dört yer tutucu bulunan bir akıllı telefonun soyut resmi"</string> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"İstatistik kutucuÄŸu"</string> + <!-- XTXT: Statistics Card Navigation Announcement --> + <string name="accessibility_statistics_card_navigation_information">"Daha fazla istatistik görüntülemek için kutucuklar arasında yatay olarak kaydırın"</string> + <!-- #################################### Button Tooltips for Accessibility ###################################### --> @@ -1406,6 +1407,9 @@ <string name="errors_risk_detection_limit_reached_title">"Sınıra ulaşıldı"</string> <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_description">"İşletim sisteminizce tanımlanan günlük azami denetim sayısına ulaÅŸtığınız için bugün daha fazla maruz kalma denetimi yapılamaz. Lütfen risk durumunuzu yarın yeniden kontrol edin."</string> + <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> + <string name="errors_ssl_certificate_deactivated">"Lütfen akıllı telefonunuzda SYSTEM güvenlik sertifikasını T-Systems Enterprise Services GmbH, T-TeleSec GlobalRoot Class 2 etkinleÅŸtirin. Daha fazla bilgi için https://coronawarn.app/tr/ adresinde “MADDE: 2001†bölümündeki SSS kısmına bakın."</string> + <!-- #################################### Generic Error Messages ###################################### --> diff --git a/Corona-Warn-App/src/main/res/values/colors.xml b/Corona-Warn-App/src/main/res/values/colors.xml index 959ba9df9f6eb8ddb65d1e27e7e2c93cfcd378c0..8bcf5822056382f34e3fd8a0006a8061ed36de76 100644 --- a/Corona-Warn-App/src/main/res/values/colors.xml +++ b/Corona-Warn-App/src/main/res/values/colors.xml @@ -84,4 +84,8 @@ <color name="colorStatisticsTrendPositive">#2E854B</color> <color name="colorStatisticsTrendNegative">#C11633</color> <color name="colorStatisticsTrendNeutral">#5D6F80</color> + + <!-- Bottom Nav bar--> + <color name="navItemColorSelected">@color/colorAccent</color> + <color name="navItemColorNormal">#999999</color> </resources> 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 4235e7cffd13bcad00397ef5e732f6d8d7d06d58..70a9eda52560635e9c3294a741fbb8512b867caa 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 @@ -12,7 +12,7 @@ <string name="contact_diary_person_list_no_items_title">"No people defined yet"</string> <string name="contact_diary_person_list_no_items_subtitle">"Create a person and add them to your contact journal."</string> <string name="contact_diary_person_bottom_sheet_title">"Person"</string> - <string name="contact_diary_person_bottom_sheet_text_input_hint">"First name, last name"</string> + <string name="contact_diary_person_bottom_sheet_text_input_hint">"Name"</string> <string name="contact_diary_person_bottom_sheet_save_button">"Save"</string> <string name="contact_diary_location_bottom_sheet_title">"Place"</string> <string name="contact_diary_location_bottom_sheet_text_input_hint">"Description"</string> @@ -42,7 +42,7 @@ <!-- XTXT: Contact diary onboarding screen fifth functionality --> <string name="contact_diary_onboarding_functionality_fifth_section">"You can export your contact journal in plain text format and then print out the entries, edit them, or provide them to your public health authority as needed."</string> <!-- XTXT: Contact diary onboarding screen sixth functionality --> - <string name="contact_diary_onboarding_functionality_sixth_section"></string> + <string name="contact_diary_onboarding_functionality_sixth_section">"The indicated exposures are not necessarily related to the people and places you have recorded. Please don’t draw any wrong conclusions."</string> <!-- XTXT: Title for the contact diary onboarding screen --> <string name="contact_diary_title">"Contact Journal"</string> <!-- XTXT: Body for legal information of the contact diary onboarding screen --> @@ -66,13 +66,13 @@ <!-- XTXT: Title for contact diary overview screen high risk information --> - <string name="contact_diary_high_risk_title"></string> + <string name="contact_diary_high_risk_title">"Increased Risk"</string> <!-- XTXT: Title for contact diary overview screen low risk information --> - <string name="contact_diary_low_risk_title"></string> + <string name="contact_diary_low_risk_title">"Low Risk"</string> <!-- XTXT: Body for contact diary overview screen risk information --> - <string name="contact_diary_risk_body"></string> + <string name="contact_diary_risk_body">"based on the encounters evaluated by the app."</string> <!-- XTXT: Extended Body for contact diary overview screen risk information --> - <string name="contact_diary_risk_body_extended"></string> + <string name="contact_diary_risk_body_extended">"based on the encounters evaluated by the app. They are not necessarily related to the people and places you have recorded."</string> <!-- XTXT: content description of contact journal image on home screen --> @@ -115,7 +115,7 @@ <!-- 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--> - <string name="contact_diary_export_intro_one" translatable="false">"Kontakte der letzten 14 Tage (%1$s - %2$s)"</string> + <string name="contact_diary_export_intro_one" translatable="false">"Kontakte der letzten 15 Tage (%1$s - %2$s)"</string> <!-- XTXT: Intro text for the contact journal email export part two--> <string name="contact_diary_export_intro_two" translatable="false">"Die nachfolgende Liste dient dem zuständigen Gesundheitsamt zur Kontaktnachverfolgung gem. § 25 IfSG."</string> @@ -141,4 +141,5 @@ <!-- XTXT: Edit (description for screen readers) --> <string name="accessibility_edit">"Edit"</string> + </resources> 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 32e7a8a97571ff6ebd95e43caa897bfb47f4f3cf..2230002a32421921656f0dcc6ee7ea0925c03330 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 @@ -5,20 +5,34 @@ ###################################### --> <!-- XHED: Title for the release info screen --> - <string name="release_info_header"/> + <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" /> - <!-- XTXT: Encounter history title for the release info screen --> - <string name="release_info_1_12_encounter_history_title" /> - <!-- XTXT: Encounter history body for the release info screen --> - <string name="release_info_1_12_encounter_history_body" /> - <!-- XTXT: Statistics title for the release info screen --> - <string name="release_info_1_12_statistics_title" /> - <!-- XTXT: Statistics body for the release info screen --> - <string name="release_info_1_12_statistics_body" /> <!-- XBUT: Continue button for the release info screen --> - <string name="release_info_continue_button" /> + <string name="release_info_continue_button">"Next"</string> + <!-- XTXT: New release info footer --> + <string name="new_release_bottom">Änderungen zum Release finden Sie in den App-Einstellungen unter dem Menüpunkt „Neue Funktionenâ€.</string> + + <!-- XHED: Titles for the release info screen bullet points --> + <string-array name="new_release_title"> + <item>Link auf RKI-Umfrage</item> + <item>Einwilligung zur Datenspende (optional)</item> + <item>Anpassungen der Risiko-Karten</item> + <item>Fortführung der Risiko-Ermittlung nach Teilung der Zufalls-IDs</item> + <item>Einführung von Registerkarten</item> + <item>Mehr Informationen zum Testablauf</item> + </string-array> + + <!-- XTXT: Text bodies for the release info screen bullet points --> + <string-array name="new_release_body"> + <item>Wenn Sie ein erhöhtes Risiko haben, können Sie aus der App heraus eine Umfrage des Robert-Koch-Instituts aufrufen und daran teilnehmen.</item> + <item>Sie haben nun die Möglichkeit, Ihre Nutzungsdaten zur Verfügung zu stellen und uns somit bei der Verbesserung der App zu unterstützen.</item> + <item>Die Texte auf den Risiko-Karten wurden leicht angepasst.</item> + <item>Nachdem Sie Ihre verschlüsselten Zufalls-IDs geteilt haben, können Sie entscheiden, ob und wann Sie die Risiko-Ermittlung wieder einschalten.</item> + <item>Es gibt nun eine eigene Registerkarte für das Kontakt-Tagebuch. Somit können Sie schneller auf Ihr Tagebuch zugreifen und vom Kontakt-Tagebuch wieder zurück auf die Startseite der App wechseln.</item> + <item>Die App enthält nun detaillierte Informationen zum Testablauf.</item> + </string-array> </resources> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 46036beca8ec374461c668f4a337963b3069fcfc..d628bd7845e5146c33c538f4e33673de6a14aac6 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -161,25 +161,50 @@ <string name="risk_card_low_risk_no_encounters_body">"No exposures"</string> <!-- XTXT: risk card - Low risk state - Days with low risk encounters --> <plurals name="risk_card_low_risk_encounter_days_body"> - <item quantity="one">"Exposures with low risk on one day"</item> + <item quantity="one">"Exposures with low risk on %1$d day"</item> <item quantity="other">"Exposures with low risk on %1$d days"</item> - <item quantity="zero">"Exposures with low risk on %1$d days"</item> + <item quantity="zero">"No exposures"</item> <item quantity="two">"Exposures with low risk on %1$d days"</item> <item quantity="few">"Exposures with low risk on %1$d days"</item> <item quantity="many">"Exposures with low risk on %1$d days"</item> </plurals> + <!-- XACT: risk card - Low risk state - Days with low risk encounters description --> + <plurals name="risk_card_low_risk_encounter_days_body_description"> + <item quantity="one">"Exposures with low risk on %1$d day"</item> + <item quantity="other">"Exposures with low risk on %1$d days"</item> + <item quantity="zero">"No exposures"</item> + <item quantity="two">"Exposures with low risk on %1$d days"</item> + <item quantity="few">"Exposures with low risk on %1$d days"</item> + <item quantity="many">"Exposures with low risk on %1$d days"</item> + </plurals> + <!-- XTXT: risk card - Low risk state - Most recent date with low risk and single day of encounters --> + <string name="risk_card_low_risk_most_recent_body_encounter_on_single_day">"Am %1$s"</string> + <!-- XTXT: risk card - Low risk state - Most recent date with low risk and more than one day of encounters --> + <string name="risk_card_low_risk_most_recent_body_encounters_on_more_than_one_day">"Zuletzt am %1$s"</string> <!-- XTXT: risk card - High risk state - Days with high risk encounters --> <plurals name="risk_card_high_risk_encounter_days_body"> - <item quantity="one">"Exposures on one day with increased risk"</item> + <item quantity="one">"Exposures on %1$d day with increased risk"</item> + <item quantity="other">"Exposures on %1$d days with increased risk"</item> + <item quantity="zero">"Exposures on %1$d days with increased risk"</item> + <item quantity="two">"Exposures on %1$d days with increased risk"</item> + <item quantity="few">"Exposures on %1$d days with increased risk"</item> + <item quantity="many">"Exposures on %1$d days with increased risk"</item> + </plurals> + <!-- XACT: risk card - High risk state - Days with high risk encounters description --> + <plurals name="risk_card_high_risk_encounter_days_body_description"> + <item quantity="one">"Exposures on %1$d day with increased risk"</item> <item quantity="other">"Exposures on %1$d days with increased risk"</item> <item quantity="zero">"Exposures on %1$d days with increased risk"</item> <item quantity="two">"Exposures on %1$d days with increased risk"</item> <item quantity="few">"Exposures on %1$d days with increased risk"</item> <item quantity="many">"Exposures on %1$d days with increased risk"</item> </plurals> - <!-- XTXT: risk card - High risk state - Most recent date with high risk --> - <string name="risk_card_high_risk_most_recent_body">"Most recently on %1$s"</string> + + <!-- XTXT: risk card - High risk state - Most recent date with high risk and single day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounter_on_single_day">"Am %1$s"</string> + <!-- XTXT: risk card - High risk state - Most recent date with high risk and more than one day of encounters --> + <string name="risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day">"Most recently on %1$s"</string> <!-- #################################### Risk Card - Progress @@ -235,11 +260,11 @@ <!-- XHED: App overview subtitle for tracing explanation--> <string name="main_overview_subtitle_tracing">"Exposure Logging"</string> <!-- YTXT: App overview body text about tracing --> - <string name="main_overview_body_tracing">"Exposure logging is one of the three central features of the app. When you activate it, encounters with people’s smartphones are logged. You don’t have to do anything else."</string> + <string name="main_overview_body_tracing">"Exposure logging is one of the three central features of the app. When you activate it, encounters with people’s smartphones are logged automatically."</string> <!-- XHED: App overview subtitle for risk explanation --> <string name="main_overview_subtitle_risk">"Risk of Infection"</string> <!-- YTXT: App overview body text about risk levels --> - <string name="main_overview_body_risk">"If you have had contact within the last 14 days with a person who was diagnosed with coronavirus, the app calculates your personal risk of infection. It does this by measuring duration and proximity of the exposure."</string> + <string name="main_overview_body_risk">"When exposure logging is active, the app calculates your personal risk of infection. It does this by measuring duration and proximity of your exposure to people who have been diagnosed with coronavirus over the last 14 days."</string> <!-- XHED: App overview subtitle for risk level list --> <string name="main_overview_subtitle_risk_levels">"The following risk status can be shown:"</string> <!-- XTXT: App overview increased risk level --> @@ -251,7 +276,7 @@ <!-- XHED: App overview subtitle for test procedure explanation --> <string name="main_overview_headline_test">"Notifying Other Users"</string> <!-- YTXT: App overview body text about rest procedure --> - <string name="main_overview_body_test">"Another central feature is registering your test and retrieving the result. If you are diagnosed with coronavirus, you can notify others and break the chain of infection."</string> + <string name="main_overview_body_test">"You can register your test and retrieve your test result in the app. If you are diagnosed with coronavirus, you can notify others and break the chain of infection."</string> <!-- XHED: App overview headline for glossary --> <string name="main_overview_headline_glossary">"Definition of Terms:"</string> <!-- XHED: App overview subtitle for glossary key storage --> @@ -261,7 +286,7 @@ <!-- XHED: App overview subtitle for glossary risk calculation --> <string name="main_overview_subtitle_glossary_calculation">"Exposure Check"</string> <!-- YTXT: App overview body for glossary risk calculation --> - <string name="main_overview_body_glossary_calculation">"Exposure log data is retrieved and synchronized with reported infections of other users. Your risk is checked automatically several times per day."</string> + <string name="main_overview_body_glossary_calculation">"Exposure log data is retrieved and synchronized with risk notifications from other users. Your risk is checked automatically several times per day."</string> <!-- XHED: App overview subtitle for glossary contact --> <string name="main_overview_subtitle_glossary_contact">"Exposure Risk"</string> <!-- YTXT: App overview body for glossary contact --> @@ -305,6 +330,14 @@ <string name="risk_details_behavior_body_wear_mask">"Wear a face mask when you encounter other people."</string> <!-- XMSG: risk details - stay 1,5 away, something like a bullet point --> <string name="risk_details_behavior_body_stay_away">"Keep at least 1.5 meters distance from other people."</string> + + <!-- XMSG: risk details - link to faq, something like a bullet point --> + <string name="risk_details_increased_risk_faq_link_text">""</string> + <!-- XTXT: Explanation screen increased risk level link label --> + <string name="risk_details_increased_risk_faq_link_label">""</string> + <!-- XTXT: Explains user about increased risk level: URL, has to be "translated" into english (relevant for all languages except german) - https://www.coronawarn.app/en/faq/#further_details --> + <string name="risk_details_increased_risk_faq_url">""</string> + <!-- XMSG: risk details - cough/sneeze, something like a bullet point --> <string name="risk_details_behavior_body_cough_sneeze">"Sneeze or cough into your elbow or a tissue."</string> <!-- XMSG: risk details - contact your doctor, bullet point --> @@ -359,8 +392,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> - <!-- 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> <!-- XHED: risk details - deadman notification title --> <string name="risk_details_deadman_notification_title">"Your Risk Status"</string> @@ -432,7 +463,7 @@ <!-- XHED: onboarding(tracing) - two/three line headline under an illustration --> <string name="onboarding_tracing_subtitle">"To identify whether you are at risk of infection, you must activate exposure logging."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> - <string name="onboarding_tracing_body">"Exposure logging works by your Android smartphone receiving, via Bluetooth, encrypted random IDs of other users and passing your own random IDs to their smartphones. Exposure logging can be deactivated at any time. "</string> + <string name="onboarding_tracing_body">"Exposure logging works by your Android smartphone receiving, via Bluetooth, encrypted random IDs of other app users and passing your own random IDs to their smartphones. Exposure logging can be deactivated at any time."</string> <!-- YTXT: onboarding(tracing) - explain tracing --> <string name="onboarding_tracing_body_emphasized">"The encrypted random IDs only pass information about date, duration, and proximity (calculated from signal strength) to other people. Individuals cannot be identified based on the random IDs."</string> <!-- YTXT: onboarding(tracing) - easy language explain tracing link--> @@ -482,9 +513,9 @@ <!-- XACT: Onboarding (test) page title --> <string name="onboarding_test_accessibility_title">"Onboarding page 5 of 6: If You Are Diagnosed with Coronavirus"</string> <!-- XHED: onboarding(test) - about positive tests --> - <string name="onboarding_test_headline">"If you are diagnosed with coronavirus…"</string> + <string name="onboarding_test_headline">"If you are diagnosed with coronavirus"</string> <!-- XHED: onboarding(test) - two/three line headline under an illustration --> - <string name="onboarding_test_subtitle">"… please report this in the Corona-Warn-App. Sharing your test results is voluntary and secure. Please do this for the sake of everyone’s health."</string> + <string name="onboarding_test_subtitle">"If you receive a positive test result, please report this in the app. Sharing your test results is voluntary and secure. Please do this for the sake of everyone’s health."</string> <!-- YTXT: onboarding(test) - explain test --> <string name="onboarding_test_body">"Your notification is encrypted securely and processed on a secure server. People whose encrypted random IDs your smartphone has collected will now receive a warning along with information about what they should now do."</string> <!-- XACT: onboarding(test) - illustraction description, header image --> @@ -534,7 +565,7 @@ <!-- XTXT: settings(tracing) - shows status under header in home, inactive location --> <string name="settings_tracing_body_inactive_location">"Location services deactivated"</string> <!-- YTXT: settings(tracing) - explains tracings --> - <string name="settings_tracing_body_text">"You need to enable the exposure logging feature so that the app can determine whether you are at risk of infection. The exposure logging feature works transnationally, meaning any possible exposure involving users is also detected by other official coronavirus apps.\n\nThe exposure logging feature works by your Android smartphone receiving encrypted random IDs from other users via Bluetooth and passing your own random IDs to their smartphones. Every day, the app downloads lists containing the random IDs – along with any voluntary information about the onset of symptoms – of all users who have tested positive for the virus and voluntarily shared this information (specifically: their random IDs) via their app. This list is then compared with the random IDs of other users you have encountered that have been recorded by your Android smartphone, in order to calculate the likelihood that you have also been infected and to warn you if necessary.\n\nYou can use the toggle switch to disable exposure logging at any time.\n\nThe app never collects personal data such as your name, address or location, nor is this information passed on to other users. It is not possible to use random IDs to draw conclusions about individual persons."</string> + <string name="settings_tracing_body_text">"You need to enable the exposure logging feature so that the app can determine whether you are at risk of infection. The exposure logging feature works transnationally, meaning any possible exposure involving users is also detected by other official coronavirus apps.\n\nThe exposure logging feature works by your Android smartphone receiving encrypted random IDs from other app users via Bluetooth and passing your own random IDs to their smartphones. Every day, the app downloads lists containing the random IDs – along with any voluntary information about the onset of symptoms – of all users who have tested positive for the virus and voluntarily shared this information (specifically: their random IDs) via their app. This list is then compared with the random IDs of other users you have encountered that have been recorded by your Android smartphone, in order to calculate the likelihood that you have also been infected and to warn you if necessary.\n\nYou can use the toggle switch to disable exposure logging at any time.\n\nThe app never collects personal data such as your name, address or location, nor is this information passed on to other users. It is not possible to use random IDs to draw conclusions about individual persons."</string> <!-- XTXT: settings(tracing) - status next to switch under title --> <string name="settings_tracing_status_active">"Active"</string> <!-- XTXT: settings(tracing) - status next to switch under title --> @@ -562,7 +593,7 @@ <!--XHED : settings(tracing) - headline on card about the current status and what to do --> <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 exposures. Please turn on WIFI or mobile data in your device settings."</string> + <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> <!-- 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 --> @@ -723,7 +754,7 @@ <!-- XHED: Headline for legal information page, publisher section --> <string name="information_legal_headline_publisher">"Published by"</string> <!-- YTXT: subtitle for legal information page, publisher section --> - <string name="information_legal_subtitle_publisher">"(responsible in accordance with § 5, para. 1 TMG, § 55 para. 1 RStV, DS-GVO, BDSG)"</string> + <string name="information_legal_subtitle_publisher">"(responsible in accordance with § 5 (1) TMG, § 18 (1) MStV)"</string> <!-- YTXT: body for legal information page, publisher section --> <string name="information_legal_body_publisher">"Robert Koch Institute"<xliff:g id="line_break">"\n"</xliff:g>"Nordufer 20"<xliff:g id="line_break">"\n"</xliff:g>"13353 Berlin"<xliff:g id="line_break">"\n"</xliff:g><xliff:g id="line_break">"\n"</xliff:g>"represented by its president"</string> <!-- XHED: Headline for legal information page, contact section --> @@ -778,7 +809,7 @@ <!-- XHED: Dialog title for generic web request error --> <string name="submission_error_dialog_web_generic_error_title">"Error"</string> <!-- XMSG: Dialog body for generic web request network error with status code --> - <string name="submission_error_dialog_web_generic_network_error_body">"Connection could not be established (%1$d). Please try again."</string> + <string name="submission_error_dialog_web_generic_network_error_body">"Your Internet connection may have been lost. Please ensure that you are connected to the Internet."</string> <!-- XMSG: Dialog body for generic web request error without status code --> <string name="submission_error_dialog_web_generic_error_body">"Connection could not be established. Please try again."</string> <!-- XBUT: Positive button for generic web request error --> @@ -885,6 +916,8 @@ <string name="submission_consent_main_third_point">"Your identity will remain secret. Other users will not find out who has shared a test result."</string> <!-- YTXT: Body for consent main section fourth point --> <string name="submission_consent_main_fourth_point">"You must be at least 16 years old to grant your consent."</string> + <!-- YTXT: Content Description for the illustration --> + <string name="submission_consent_main_illustration_description"></string> <!-- Submission Test Result --> <!-- XHED: Page headline for test result --> @@ -912,7 +945,7 @@ <!-- XHED: Page headline for other warnings screen --> <string name="submission_test_result_positive_steps_warning_others_heading">"Warn Others"</string> <!-- YTXT: Body text for for other warnings screen--> - <string name="submission_test_result_positive_steps_warning_others_body">"Share your random IDs and warn others.\nHelp determine the risk of infection for others more accurately by also indicating when you first noticed any coronavirus symptoms."</string> + <string name="submission_test_result_positive_steps_warning_others_body">"Please share your random IDs and warn others.\nHelp determine the risk of infection for others more accurately by also indicating when you first noticed any coronavirus symptoms."</string> <!-- XBUT: positive test result : continue button --> <string name="submission_test_result_positive_continue_button">"Next"</string> <!-- XBUT: positive test result : continue button with symptoms--> @@ -957,7 +990,7 @@ <!-- XHED: Page title for TAN submission pge --> <string name="submission_tan_title">"Enter TAN"</string> <!-- YTXT: Body text for the tan submission page --> - <string name="submission_tan_body">"Please enter the 10-digit TAN that you were given."</string> + <string name="submission_tan_body">"Enter the 10-digit TAN that you were given."</string> <!-- XBUT: Submit TAN button --> <string name="submission_tan_button_text">"Next"</string> <!-- XACT: Submission Tan page title --> @@ -971,7 +1004,7 @@ <!-- XBUT: Submission introduction next button--> <string name="submission_intro_button_next">"Next"</string> <!-- YTXT: Description for illustration in submission onboarding--> - <string name="submission_intro_illustration_description"></string> + <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 --> @@ -1060,10 +1093,10 @@ <string name="submission_done_further_info_title">"Other information:"</string> <!-- YTXT: submission done further info bullet points --> <string-array name="submission_done_further_info_bullet_points"> - <item>"Your quarantine period is usually 14 days. Please observe your symptoms and monitor how they develop."</item> - <item>"You will be asked by the public health authority to create a list of people you have had contact with. This should include all people with whom you have had close contact with (less than 2 meters, face-to-face conversation) for over 15 minutes in the two days before you developed symptoms. You can use your contact journal for this: simply export the entries and then print them out or send them via e-mail."</item> + <item>"Your quarantine period is usually 14 days. Please monitor how your symptoms develop closely."</item> + <item>"You will be asked by your public health authority to create a list of people you have had contact with. This should include all people with whom you have had close contact with (less than 2 meters, face-to-face conversation) for over 15 minutes in the two days before you became ill. You can use your contact journal for this: simply export the entries and then print them out or send them via e-mail."</item> <item>"Please particularly consider people who will not be notified directly by the app since they don’t own a smartphone, or haven’t installed the app."</item> - <item>"Even when you no longer have any symptoms and you feel well again, you could still be infectious."</item> + <item>"Even when you no longer have any symptoms and you feel well again, you could still be infectious, so please follow the designated quarantine period."</item> </string-array> <!-- XBUT: submission finished button --> <string name="submission_done_button_done">"Done"</string> @@ -1132,16 +1165,16 @@ <!-- XLNK: Technical number which is called when the user clicks on the display number --> <string name="submission_contact_number_dial">"0800 7540002"</string> <!-- YTXT: Body text for step 2 of contact page--> - <string name="submission_contact_step_2_body">"Register the test by entering the TAN in the app."</string> + <string name="submission_contact_step_2_body">"Register your test by entering the TAN in the app."</string> <!-- YTXT: Body text for operating hours in contact page--> - <string name="submission_contact_operating_hours_body">"Languages: \nEnglish, German, Turkish\n\nBusiness hours:\nMonday to Sunday: 24 hours\n\nThe call is free of charge."</string> + <string name="submission_contact_operating_hours_body">"Languages: \nEnglish, German, Turkish\n\nBusiness hours:\n24/7\n\nThe call is free of charge."</string> <!-- YTXT: Body text for technical contact and hotline information page --> <string name="submission_contact_body_other">"If you have any health-related questions, please contact your general practitioner or the medical emergency service hotline, telephone: 116 117."</string> <!-- XACT: Submission contact page title --> <string name="submission_contact_accessibility_title">"Call the hotline and request a TAN"</string> <!-- XACT: Content Description for submission contact step 1, number has to sync with the display number --> - <string name="submission_contact_step_1_content">"In the first step, you call the nationwide hotline and request your TAN. You can reach the nationwide hotline on 0800 7540002. The business hours are Monday to Friday from 8 am to 10 pm and Saturday and Sunday from 10 am to 10 pm. The call is free of charge."</string> + <string name="submission_contact_step_1_content">"In the first step, you call the nationwide hotline and request your TAN. You can reach the hotline on 0800 7540002. The business hours are Monday to Friday from 8 am to 10 pm and Saturday and Sunday from 10 am to 10 pm. The call is free of charge."</string> <!-- XACT: Content Description for submission contact step 2 --> <string name="submission_contact_step_2_content">"In the second step, you register your test with your TAN in the app."</string> @@ -1177,7 +1210,7 @@ <!-- XHED: Page title for the various submission status fetch ready --> <string name="submission_status_card_title_ready">"Your Test Result Is Available"</string> <!-- XHED: Subtitle for the submission status card: invalid --> - <string name="submission_status_card_subtitle_invalid">"Invalid test"</string> + <string name="submission_status_card_subtitle_invalid">"Your test cannot be evaluated"</string> <!-- XHED: Subtitle for the submission status card: negative --> <string name="submission_status_card_subtitle_negative">"Negative Diagnosis"</string> <!-- XHED: Subtitle for the submission status card: ready --> @@ -1308,7 +1341,7 @@ <!-- XHED: Title for submission statistics card --> <string name="statistics_card_submission_title">"Warnings by App Users"</string> <!-- XTXT: Text displayed in the bottom of submission statistics card--> - <string name="statistics_card_submission_bottom_text">"through the Corona-Warn-App"</string> + <string name="statistics_card_submission_bottom_text">"About Corona-Warn-App"</string> <!-- XTXT: Timestamp refers to today's date in submission and infections cards --> <string name="statistics_primary_value_today">"Today"</string> @@ -1346,8 +1379,8 @@ <!-- XHED: Explanation screen seven day r-value title --> <string name="statistics_explanation_seven_day_r_value_title">"7-Day R Value"</string> <!-- XTXT: Explanation screen seven day r-value text --> - <string name="statistics_explanation_seven_day_r_value_text">"The reproduction figure, R, indicates how many people an average infected person has infected further. The current value takes data up to the last 5 days into account.\n\nFor more information, refer to the\nFAQ for the statistics."</string> - <!-- XTXT: Explanation screen seven day r-value link label --> + <string name="statistics_explanation_seven_day_r_value_text">"The reproduction figure, R, indicates how many people an average infected person has infected further. The current value takes data up to the last 5 days into account.\n\nFor more information, refer to the FAQ:\nFAQ for the statistics."</string> + <!-- XTXT: Explanation screen seven day r-value link label - the characters have to match the last part of the text label above --> <string name="statistics_explanation_seven_day_r_link_label">"FAQ for the statistics."</string> <!-- XHED: Explanation screen legend title --> <string name="statistics_explanation_legend_title">"Legend"</string> @@ -1368,7 +1401,7 @@ <!-- XHED: Explanation screen trend title --> <string name="statistics_explanation_trend_title">"Trend"</string> <!-- YTXT: Explanation screen trend text --> - <string name="statistics_explanation_trend_text">"The arrow direction indicates whether the trend is increasing, decreasing, or remaining steady – that is, demonstrates a deviation of less than 1\% compared to the previous day or 5\% compared to the previous week. The color indicates this trend as positive (green), negative (red), or neutral (gray). The trend compares the value from the previous day with the value from two days ago or, for the 7-day trends, the average value from the last 7 days with the average value from the 7 days prior to that."</string> + <string name="statistics_explanation_trend_text">"The arrow direction indicates whether the trend is increasing, decreasing, or remaining steady – that is, demonstrates a deviation of less than 1%% compared to the previous day or 5%% compared to the previous week. The color indicates this trend as positive (green), negative (red), or neutral (gray). The trend compares the value from the previous day with the value from two days ago or, for the 7-day trends, the average value from the last 7 days with the average value from the 7 days prior to that."</string> <!-- XHED: Explanation screen trend icons title --> <string name="statistics_explanation_trend_icons_title">"The following trends can be shown:"</string> <!-- XHED: Explanation screen trend increasing title --> @@ -1379,11 +1412,16 @@ <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) - 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)0 - 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> + <!-- XTXT: Statistics Card Announcement --> + <string name="accessibility_statistics_card_announcement">"Statistics tile"</string> + <!-- 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 @@ -1421,7 +1459,7 @@ <!-- XTXT: error dialog - Error description when the provideDiagnosisKeys quota limit was reached. --> <string name="errors_risk_detection_limit_reached_description">"No more exposure checks possible today, as you have reached the maximum number of checks per day defined by your operating system. Please check your risk status again tomorrow."</string> <!-- XTXT: error dialog - Error description when the ssl certificate is deactivated on the device. --> - <string name="errors_ssl_certificate_deactivated">"Bitte aktivieren Sie das SYSTEM Sicherheitszertifikat T-Systems Enterprise Services GmbH, T-TeleSec GlobalRoot Class 2 auf Ihrem Gerät. Mehr Informationen finden Sie in den FAQs auf https://coronawarn.app unter „URSACHE 2001“."</string> + <string name="errors_ssl_certificate_deactivated">"Please activate the SYSTEM security certificate T-Systems Enterprise Services GmbH, T-TeleSec GlobalRoot Class 2 on your smartphone. For more information, refer to the FAQs on https://coronawarn.app/en/ under “CAUSE: 2001â€."</string> <!-- #################################### Generic Error Messages @@ -1632,6 +1670,33 @@ <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">Home</string> + <!-- XHED: Title for BottomNav diary screen title --> + <string name="bottom_nav_diary_title">Journal</string> + + <!-- #################################### + Data Donation & Survey + ###################################### --> + + <!-- XHED: Title for the access survey card displayed in the risk details screen --> + <string name="datadonation_details_access_survey_card_title">"Befragung zur Corona-Warn-App"</string> + <!-- XHED: Text for the access survey card displayed in the risk details screen --> + <string name="datadonation_details_access_survey_card_content">"Helfen Sie uns, die App zu verbessern, indem Sie einige einfache Fragen beantworten."</string> + <!-- XHED: Text for the access survey card displayed in the risk details screen --> + <string name="datadonation_details_access_survey_card_button_text">"Zur Befragung"</string> + + <!-- XHED: Title for the access survey button displayed at the bottom of the screen --> + <string name="datadonation_details_survey_consent_button_title">"Weiter"</string> + <!-- XHED: Text for the access survey title displayed at the top of survey consent screen --> + <string name="datadonation_details_survey_consent_top_title">"Befragung zur Verbesserung der Corona-Warn-App"</string> + <!-- XHED: Text for the access survey body displayed under the title of survey consent screen --> + <string name="datadonation_details_survey_consent_top_body">"Mit Ihrer Teilnahme helfen Sie uns zu verstehen, wie sich Warnungen über die App auf das Verhalten von Personen mit erhöhtem Risiko auswirken."</string> + <!-- XHED: Text for the consent part title displayed over the button in the survey consent screen --> + <string name="datadonation_details_survey_consent_info_card_title">"Ihr Einverständnis"</string> + <!-- XHED: Text for the consent part body displayed over the button in the survey consent screen --> + <string name="datadonation_details_survey_consent_info_card_body">"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut t amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."</string> + <!-- #################################### Data donation diff --git a/Corona-Warn-App/src/main/res/values/styles.xml b/Corona-Warn-App/src/main/res/values/styles.xml index 0fa57df7b1beafeb08892ab97cbdb08337c39a1c..1a62baa5e7fd4e0c89eeb3be7a9c323330876a9d 100644 --- a/Corona-Warn-App/src/main/res/values/styles.xml +++ b/Corona-Warn-App/src/main/res/values/styles.xml @@ -1,37 +1,24 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="AppTheme" parent="Theme.AppCompat.DayNight"> + <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.Bridge"> <item name="colorPrimary">@color/colorBrandSecondary</item> <item name="colorPrimaryDark">@color/colorStableDark</item> <item name="android:windowBackground">@color/colorBackground</item> - <item name="alertDialogTheme">@style/dialog</item> + <item name="alertDialogTheme">@style/DialogAlertTheme</item> <item name="android:actionOverflowButtonStyle">@style/CWAToolbar.Overflow</item> </style> <style name="AppTheme.NoActionBar" parent="AppTheme"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> - </style> - - <style name="MaterialAppTheme" parent="Theme.MaterialComponents.DayNight"> - <item name="colorPrimary">@color/colorBrandSecondary</item> - <item name="colorPrimaryDark">@color/colorStableDark</item> - <item name="android:windowBackground">@color/colorBackground</item> - <item name="alertDialogTheme">@style/diaryDialog</item> - </style> - - <style name="MaterialAppTheme.NoActionBar" parent="MaterialAppTheme"> - <item name="windowActionBar">false</item> - <item name="windowNoTitle">true</item> - <item name="dialogTheme">@style/diaryDialog</item> + <item name="dialogTheme">@style/DialogTheme</item> + <item name="bottomSheetDialogTheme">@style/BottomSheetDialogTheme</item> </style> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> - <style name="AppTheme.ContactDiary" parent="MaterialAppTheme.NoActionBar" /> - <style name="ThemeOverlay.App.ExtendedFloatingActionButton" parent=""> <item name="colorSecondary">@color/colorAccentTintButton</item> <item name="colorOnSecondary">@color/colorTextEmphasizedButton</item> @@ -76,21 +63,25 @@ <item name="navigationContentDescription">@string/accessibility_close</item> </style> - <!-- Default dialog --> - <style name="dialog" parent="ThemeOverlay.AppCompat.Dialog.Alert"> - <item name="buttonBarPositiveButtonStyle">@style/dialogPositiveButtonStyle</item> - <item name="buttonBarNegativeButtonStyle">@style/dialogNegativeButtonStyle</item> + <!-- Dialog Theme--> + <style name="DialogTheme" parent="Theme.MaterialComponents.DayNight.Dialog.Alert"> + <item name="buttonBarPositiveButtonStyle">@style/DialogButtonTheme</item> + <item name="buttonBarNegativeButtonStyle">@style/DialogButtonTheme</item> </style> - <style name="diaryDialogButton" parent="Widget.MaterialComponents.Button.TextButton"> + <style name="BottomSheetDialogTheme" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog"> + <item name="colorPrimary">@color/colorBrandSecondary</item> + <item name="colorPrimaryDark">@color/colorStableDark</item> + </style> + + <style name="DialogButtonTheme" parent="Widget.MaterialComponents.Button.TextButton"> <item name="android:textColor">@color/colorAccentTintButton</item> </style> - <style name="diaryButtonNegative" parent="Widget.MaterialComponents.Button.TextButton" /> - <!-- Contact Diary Dialog--> - <style name="diaryDialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert"> - <item name="buttonBarPositiveButtonStyle">@style/diaryDialogButton</item> - <item name="buttonBarNegativeButtonStyle">@style/diaryDialogButton</item> + <!-- Alert Dialog Theme--> + <style name="DialogAlertTheme" parent="ThemeOverlay.AppCompat.Dialog.Alert"> + <item name="buttonBarPositiveButtonStyle">@style/dialogPositiveButtonStyle</item> + <item name="buttonBarNegativeButtonStyle">@style/dialogNegativeButtonStyle</item> </style> <style name="dialogPositiveButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog"> @@ -245,6 +236,7 @@ <style name="headline5Tint" parent="@style/headline5"> <item name="android:textColor">@color/colorTextTint</item> </style> + <style name="headline5Bold" parent="@style/TextAppearance.MaterialComponents.Headline5"> <item name="android:textStyle">bold</item> </style> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt index ce08704edd0ec6980c1ce8ec80834dd605006e43..9faa43631ab10fa795b30849449eb1fb3201fae4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/internal/AppConfigSourceTest.kt @@ -12,11 +12,13 @@ import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.clearAllMocks import io.mockk.coEvery +import io.mockk.coVerifyOrder import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.test.runBlockingTest import org.joda.time.Duration import org.joda.time.Instant @@ -78,6 +80,14 @@ class AppConfigSourceTest : BaseTest() { every { cwaSettings.wasDeviceTimeIncorrectAcknowledged } returns false every { cwaSettings.wasDeviceTimeIncorrectAcknowledged = any() } just Runs + + every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH + every { cwaSettings.firstReliableDeviceTime = any() } just Runs + + every { cwaSettings.lastDeviceTimeStateChangeAt } returns Instant.EPOCH + every { cwaSettings.lastDeviceTimeStateChangeAt = any() } just Runs + every { cwaSettings.lastDeviceTimeStateChangeState } returns ConfigData.DeviceTimeState.INCORRECT + every { cwaSettings.lastDeviceTimeStateChangeState = any() } just Runs } @AfterEach @@ -113,7 +123,7 @@ class AppConfigSourceTest : BaseTest() { val instance = createInstance() instance.getConfigData() shouldBe remoteConfig - coVerifySequence { + coVerifyOrder { localSource.getConfigData() timeStamper.nowUTC remoteSource.getConfigData() @@ -160,7 +170,7 @@ class AppConfigSourceTest : BaseTest() { createInstance().getConfigData() - coVerifySequence { + coVerifyOrder { localSource.getConfigData() remoteSource.getConfigData() cwaSettings.wasDeviceTimeIncorrectAcknowledged @@ -183,4 +193,62 @@ class AppConfigSourceTest : BaseTest() { remoteSource.getConfigData() } } + + @Test + fun `first reliable device time is set when the remote config has the correct device time`() = runBlockingTest { + coEvery { localSource.getConfigData() } returns null + + createInstance().getConfigData() + + verify { + cwaSettings.firstReliableDeviceTime = Instant.EPOCH.plus(Duration.standardHours(1)) + } + } + + @Test + fun `first reliable device time is not set, if it has already been set`() = runBlockingTest { + coEvery { localSource.getConfigData() } returns null + every { cwaSettings.firstReliableDeviceTime } returns Instant.ofEpochMilli(1234L) + + createInstance().getConfigData() + + verify(exactly = 0) { + cwaSettings.firstReliableDeviceTime = any() + } + } + + @Test + fun `first reliable device time is not set, if the device time is incorrect`() = runBlockingTest { + coEvery { remoteSource.getConfigData() } returns remoteConfig.copy(localOffset = Duration.standardDays(1)) + coEvery { localSource.getConfigData() } returns null + + createInstance().getConfigData() + + verify(exactly = 0) { + cwaSettings.firstReliableDeviceTime = any() + } + } + + @Test + fun `if the device time state changes we save the timestamp and the current state`() = runBlockingTest { + coEvery { localSource.getConfigData() } returns null + // INCORRECT + coEvery { remoteSource.getConfigData() } returns remoteConfig.copy(localOffset = Duration.standardDays(1)) + + createInstance().getConfigData() + + verify(exactly = 0) { + cwaSettings.lastDeviceTimeStateChangeAt = any() + cwaSettings.lastDeviceTimeStateChangeState = any() + } + + coEvery { remoteSource.getConfigData() } returns remoteConfig + + createInstance().getConfigData() + + verify { + cwaSettings.lastDeviceTimeStateChangeAt = Instant.EPOCH.plus(Duration.standardHours(1)) + cwaSettings.lastDeviceTimeStateChangeState = ConfigData.DeviceTimeState.CORRECT + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b8b9fda93b84e002cf8e02b10a5d943dc55a072e --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/OneTimePasswordTest.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.datadonation + +import io.kotest.matchers.shouldBe +import okio.ByteString.Companion.decodeBase64 +import org.junit.Test +import java.util.UUID + +class OneTimePasswordTest { + + @Test + fun `payload generation`() { + val otpPayload = + OneTimePassword(UUID.fromString("15cff19f-af26-41bc-94f2-c1a65075e894")) + .payloadForRequest + val expected = "MTVjZmYxOWYtYWYyNi00MWJjLTk0ZjItYzFhNjUwNzVlODk0".decodeBase64()!!.toByteArray() + otpPayload shouldBe expected + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..2f6e7d03f44df05437f531a243ffb0049c61aed5 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/AttestationContainerTest.kt @@ -0,0 +1,237 @@ +package de.rki.coronawarnapp.datadonation.safetynet + +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import de.rki.coronawarnapp.appconfig.SafetyNetRequirementsContainer +import io.kotest.assertions.throwables.shouldNotThrowAny +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class AttestationContainerTest : BaseTest() { + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun create(rawJson: String) = AttestationContainer( + ourSalt = ByteArray(16), + report = SafetyNetClientWrapper.Report( + jwsResult = "jwsResult", + header = JsonObject(), + body = JsonParser.parseString(rawJson).asJsonObject, + signature = ByteArray(128) + ) + ) + + @Test + fun `nothing required`() { + val attestation = """ + { + + } + """.trimIndent().let { create(it) } + + shouldNotThrowAny { + attestation.requirePass(SafetyNetRequirementsContainer()) + } + } + + @Test + fun `basic integrity required`() { + val attestation = """ + { + "basicIntegrity": false + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.BASIC_INTEGRITY_REQUIRED + } + + @Test + fun `basic integrity required bad JSON`() { + val attestation = """ + { + "basicIntegrity": "test" + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.BASIC_INTEGRITY_REQUIRED + } + + @Test + fun `cts profile match required`() { + val attestation = """ + { + "ctsProfileMatch": false, + "basicIntegrity": true + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.CTS_PROFILE_MATCH_REQUIRED + } + + @Test + fun `cts profile match required BAD JSON`() { + val attestation = """ + { + "ctsProfileMatch": "123", + "basicIntegrity": true + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.CTS_PROFILE_MATCH_REQUIRED + } + + @Test + fun `evaluation type basic required`() { + val attestation = """ + { + "ctsProfileMatch": true, + "basicIntegrity": true + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true, + requireEvaluationTypeBasic = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.EVALUATION_TYPE_BASIC_REQUIRED + } + + @Test + fun `evaluation type basic required BAD JSON`() { + val attestation = """ + { + "ctsProfileMatch": true, + "basicIntegrity": true, + "evaluationType": "" + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true, + requireEvaluationTypeBasic = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.EVALUATION_TYPE_BASIC_REQUIRED + } + + @Test + fun `evaluation type hardwarebacked required`() { + val attestation = """ + { + "ctsProfileMatch": true, + "basicIntegrity": true, + "evaluationType": " BASIC " + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true, + requireEvaluationTypeBasic = true, + requireEvaluationTypeHardwareBacked = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED + } + + @Test + fun `evaluation type hardwarebacked required BAD JSON`() { + val attestation = """ + { + "ctsProfileMatch": true, + "basicIntegrity": true, + "evaluationType": "BASIC, SURPRISE" + } + """.trimIndent().let { create(it) } + + val exception = shouldThrow<SafetyNetException> { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true, + requireEvaluationTypeBasic = true, + requireEvaluationTypeHardwareBacked = true + ) + ) + } + exception.type shouldBe SafetyNetException.Type.EVALUATION_TYPE_HARDWARE_BACKED_REQUIRED + } + + @Test + fun `everything required and pass`() { + val attestation = """ + { + "ctsProfileMatch": true, + "basicIntegrity": true, + "evaluationType": "BASIC,HARDWARE_BACKED" + } + """.trimIndent().let { create(it) } + + shouldNotThrowAny { + attestation.requirePass( + SafetyNetRequirementsContainer( + requireBasicIntegrity = true, + requireCTSProfileMatch = true, + requireEvaluationTypeBasic = true, + requireEvaluationTypeHardwareBacked = true + ) + ) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..847d271b26a80eaa1f855eccda4f147c4e91f391 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt @@ -0,0 +1,195 @@ +package de.rki.coronawarnapp.datadonation.safetynet + +import android.content.Context +import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData +import de.rki.coronawarnapp.environment.EnvironmentSetup +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpacAndroid +import de.rki.coronawarnapp.util.HashExtensions.toSHA256 +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.gplay.GoogleApiVersion +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.test.runBlockingTest +import okio.ByteString.Companion.decodeBase64 +import org.joda.time.Duration +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import java.security.SecureRandom +import kotlin.random.Random + +class CWASafetyNetTest : BaseTest() { + + @MockK lateinit var context: Context + @MockK lateinit var googleApiVersion: GoogleApiVersion + @MockK lateinit var safetyNetClientWrapper: SafetyNetClientWrapper + @MockK lateinit var environmentSetup: EnvironmentSetup + @MockK lateinit var clientReport: SafetyNetClientWrapper.Report + @MockK lateinit var secureRandom: SecureRandom + @MockK lateinit var timeStamper: TimeStamper + @MockK lateinit var cwaSettings: CWASettings + + @MockK lateinit var appConfigProvider: AppConfigProvider + @MockK lateinit var appConfigData: ConfigData + + private val defaultPayload = "Computer says no.".toByteArray() + private val firstSalt = "LMK0jFCu/lOzl07ZHmtOqQ==".decodeBase64()!! + private val defaultNonce = (firstSalt.toByteArray() + defaultPayload).toSHA256() + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + every { environmentSetup.safetyNetApiKey } returns "very safe" + coEvery { safetyNetClientWrapper.attest(any()) } returns clientReport + every { secureRandom.nextBytes(any()) } answers { + val byteArray = arg<ByteArray>(0) + Random(0).nextBytes(byteArray) + } + + clientReport.apply { + every { jwsResult } returns "JWSRESULT" + every { nonce } returns defaultNonce + every { apkPackageName } returns "de.rki.coronawarnapp.test" + every { error } returns "error" + } + + every { googleApiVersion.isPlayServicesVersionAvailable(any()) } returns true + + every { context.packageName } returns "de.rki.coronawarnapp.test" + + coEvery { appConfigProvider.getAppConfig() } returns appConfigData + every { appConfigData.deviceTimeState } returns ConfigData.DeviceTimeState.CORRECT + + every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH.plus(Duration.standardDays(7)) + every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardDays(8)) + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createInstance() = CWASafetyNet( + context = context, + client = safetyNetClientWrapper, + secureRandom = secureRandom, + appConfigProvider = appConfigProvider, + googleApiVersion = googleApiVersion, + timeStamper = timeStamper, + cwaSettings = cwaSettings + ) + + @Test + fun `salt generation used injected random source`() { + createInstance().apply { + generateSalt() shouldBe "LMK0jFCu/lOzl07ZHmtOqQ==".decodeBase64()!!.toByteArray() + } + } + + @Test + fun `nonce generation`() { + createInstance().apply { + val payload = "Computer says no.".toByteArray() + val salt = "Don't be so salty".toByteArray() + val nonce = calculateNonce(salt, payload) + nonce shouldBe (salt + payload).toSHA256() + } + } + + @Test + fun `successful attestation`() = runBlockingTest { + createInstance().apply { + val attestationResult = attest(TestAttestationRequest(defaultPayload)) + + coVerify { + safetyNetClientWrapper.attest(defaultNonce.toByteArray()) + } + + attestationResult.accessControlProtoBuf shouldBe PpacAndroid.PPACAndroid.newBuilder().apply { + salt = firstSalt.base64() + safetyNetJws = "JWSRESULT" + }.build() + } + } + + @Test + fun `minimum google play api version is 13000000`() = runBlockingTest { + every { googleApiVersion.isPlayServicesVersionAvailable(any()) } returns false + val exception = shouldThrow<SafetyNetException> { + createInstance().attest(mockk()) + } + exception.type shouldBe SafetyNetException.Type.PLAY_SERVICES_VERSION_MISMATCH + } + + @Test + fun `request nonce must match response nonce`() = runBlockingTest { + every { clientReport.nonce } returns "missmatch" + val exception = shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + } + exception.type shouldBe SafetyNetException.Type.NONCE_MISMATCH + } + + @Test + fun `request APK name must match response APK name`() = runBlockingTest { + every { context.packageName } returns "package.name" + + val exception = shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + } + exception.type shouldBe SafetyNetException.Type.APK_PACKAGE_NAME_MISMATCH + } + + @Test + fun `incorrect device time fails the attestation`() = runBlockingTest { + every { appConfigData.deviceTimeState } returns ConfigData.DeviceTimeState.ASSUMED_CORRECT + + val exception = shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + } + exception.type shouldBe SafetyNetException.Type.DEVICE_TIME_UNVERIFIED + } + + @Test + fun `first reliable devicetime timestamp needs to be more than 24 hours ago`() = runBlockingTest { + every { timeStamper.nowUTC } returns Instant.EPOCH + val exception = shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + } + exception.type shouldBe SafetyNetException.Type.TIME_SINCE_ONBOARDING_UNVERIFIED + } + + @Test + fun `first reliable devicetime timestamp needs to be set`() = runBlockingTest { + every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH + val exception = shouldThrow<SafetyNetException> { + createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) + } + exception.type shouldBe SafetyNetException.Type.TIME_SINCE_ONBOARDING_UNVERIFIED + } + + data class TestAttestationRequest( + override val scenarioPayload: ByteArray + ) : DeviceAttestation.Request { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as TestAttestationRequest + if (!scenarioPayload.contentEquals(other.scenarioPayload)) return false + return true + } + + override fun hashCode(): Int = scenarioPayload.contentHashCode() + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..4e65028918bcd6dd84b479084ccc34312ab94fc8 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/SafetyNetClientWrapperTest.kt @@ -0,0 +1,237 @@ +package de.rki.coronawarnapp.datadonation.safetynet + +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.CommonStatusCodes +import com.google.android.gms.common.api.Status +import com.google.android.gms.safetynet.SafetyNetApi +import com.google.android.gms.safetynet.SafetyNetClient +import com.google.gson.JsonParser +import de.rki.coronawarnapp.environment.EnvironmentSetup +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.instanceOf +import io.mockk.Called +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.async +import kotlinx.coroutines.test.runBlockingTest +import okio.ByteString.Companion.decodeBase64 +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.coroutines.runBlockingTest2 +import testhelpers.gms.MockGMSTask +import java.io.IOException + +@Suppress("MaxLineLength") +class SafetyNetClientWrapperTest : BaseTest() { + + @MockK lateinit var safetyNetClient: SafetyNetClient + @MockK lateinit var environmentSetup: EnvironmentSetup + @MockK lateinit var report: SafetyNetApi.AttestationResponse + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + every { environmentSetup.safetyNetApiKey } returns "very safe" + every { safetyNetClient.attest(any(), any()) } returns MockGMSTask.forValue(report) + + every { report.jwsResult } returns JWS_BASE64 + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createInstance() = SafetyNetClientWrapper( + safetyNetClient, + environmentSetup + ) + + @Test + fun `init is sideeffect free`() { + createInstance() + + verify { + safetyNetClient wasNot Called + environmentSetup wasNot Called + } + } + + @Test + fun `results are forwarded`() { + runBlockingTest { + createInstance().attest("hodl".toByteArray()).jwsResult shouldBe JWS_BASE64 + } + + verify { safetyNetClient.attest("hodl".toByteArray(), "very safe") } + } + + @Test + fun `attestation can time out`() = runBlockingTest2(ignoreActive = true) { + every { safetyNetClient.attest(any(), any()) } returns MockGMSTask.timeout() + + val resultAsync = async { + shouldThrow<SafetyNetException> { + createInstance().attest("hodl".toByteArray()).jwsResult shouldBe JWS_BASE64 + } + } + + advanceTimeBy(31 * 1000L) + + val error = resultAsync.await() + error.type shouldBe SafetyNetException.Type.ATTESTATION_FAILED + error.cause shouldBe instanceOf(TimeoutCancellationException::class) + } + + @Test + fun `exception are forwarded`() { + every { safetyNetClient.attest(any(), any()) } returns MockGMSTask.forError(IOException()) + + runBlockingTest { + val exception = shouldThrow<SafetyNetException> { + createInstance().attest("hodl".toByteArray()) + } + exception.type shouldBe SafetyNetException.Type.ATTESTATION_FAILED + } + } + + @Test + fun `network errors are using a different error type`() { + every { safetyNetClient.attest(any(), any()) } returns MockGMSTask.forError( + ApiException(Status(CommonStatusCodes.NETWORK_ERROR)) + ) + + runBlockingTest { + val exception = shouldThrow<SafetyNetException> { + createInstance().attest("hodl".toByteArray()) + } + exception.type shouldBe SafetyNetException.Type.ATTESTATION_REQUEST_FAILED + } + } + + @Test + fun `an empty jwsResult is an error`() { + every { report.jwsResult } returns null + + runBlockingTest { + val exception = shouldThrow<SafetyNetException> { + createInstance().attest("hodl".toByteArray()) + } + exception.type shouldBe SafetyNetException.Type.ATTESTATION_FAILED + } + } + + @Test + fun `api key is retrieved on each call`() { + every { environmentSetup.safetyNetApiKey } returns "wow" + + runBlockingTest { + createInstance().attest("hodl".toByteArray()).jwsResult shouldBe JWS_BASE64 + } + + verify { safetyNetClient.attest("hodl".toByteArray(), "wow") } + } + + @Test + fun `result is checked by attempting decoding it`() { + runBlockingTest { + createInstance().attest("hodl".toByteArray()).apply { + jwsResult shouldBe JWS_BASE64 + header shouldBe JsonParser.parseString(JWS_HEADER) + body shouldBe JsonParser.parseString(JWS_BODY) + signature shouldBe JWS_SIGNATURE_BASE64.decodeBase64()!!.toByteArray() + + nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==" + apkPackageName shouldBe "de.rki.coronawarnapp.test" + basicIntegrity shouldBe false + ctsProfileMatch shouldBe false + evaluationTypes shouldBe listOf("BASIC") + } + } + } + + @Test + fun `JWS with unusual and unexpected fields`() { + every { report.jwsResult } returns JWS_BASE64_MINIMAL + runBlockingTest { + createInstance().attest("hodl".toByteArray()).apply { + body shouldBe JsonParser.parseString(JWS_BODY_MINIMAL) + + nonce shouldBe "AAAAAAAAAAAAAAAAAAAAAA==" + apkPackageName shouldBe "de.rki.coronawarnapp.test" + basicIntegrity shouldBe false + ctsProfileMatch shouldBe false + evaluationTypes shouldBe emptyList() + error shouldBe "Something went wrong" + } + } + } + + companion object { + private val JWS_BASE64_MINIMAL = listOf( + // Header + "e30", + // Body + "ICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm5vbmNlIjogIkFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9PSIsCiAgICAgICAgICAgICAgICAidGltZXN0YW1wTXMiOiAxNjA4NTU4MzYzNzAyLAogICAgICAgICAgICAgICAgImFwa1BhY2thZ2VOYW1lIjogImRlLnJraS5jb3JvbmF3YXJuYXBwLnRlc3QiLAogICAgICAgICAgICAgICAgImFwa0RpZ2VzdFNoYTI1NiI6ICI5b2lxT01RQVpmQmdDbkkwanlON1RnUEFRTlNTeFdyamgxNGYwZVhwQjNVPSIsCiAgICAgICAgICAgICAgICAiY3RzUHJvZmlsZU1hdGNoIjogZmFsc2UsCiAgICAgICAgICAgICAgICAiYXBrQ2VydGlmaWNhdGVEaWdlc3RTaGEyNTYiOiBbCiAgICAgICAgICAgICAgICAgICAgIjlWTHZVR1YwR2t4MjRldHJ1RUJZaWt2QXRxU1E5aVk2cll1S2hHK3h3S0U9IgogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJiYXNpY0ludGVncml0eSI6IGZhbHNlLAogICAgICAgICAgICAgICAgIm5vYm9keUV4cGVjdHMiOiJUaGVTcGFuaXNoSW5xdWlzaXRpb24iLAogICAgICAgICAgICAgICAgImVycm9yIjoiU29tZXRoaW5nIHdlbnQgd3JvbmciCiAgICAgICAgICAgIH0", + // Signature + "" + ).joinToString(".") + private val JWS_BODY_MINIMAL = """ + { + "nonce": "AAAAAAAAAAAAAAAAAAAAAA==", + "timestampMs": 1608558363702, + "apkPackageName": "de.rki.coronawarnapp.test", + "apkDigestSha256": "9oiqOMQAZfBgCnI0jyN7TgPAQNSSxWrjh14f0eXpB3U=", + "ctsProfileMatch": false, + "apkCertificateDigestSha256": [ + "9VLvUGV0Gkx24etruEBYikvAtqSQ9iY6rYuKhG+xwKE=" + ], + "basicIntegrity": false, + "nobodyExpects":"TheSpanishInquisition", + "error":"Something went wrong" + } + """.trimIndent() + + private val JWS_BASE64 = listOf( + // Header + "eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlGa3pDQ0JIdWdBd0lCQWdJUkFOY1NramRzNW42K0NBQUFBQUFwYTBjd0RRWUpLb1pJaHZjTkFRRUxCUUF3UWpFTE1Ba0dBMVVFQmhNQ1ZWTXhIakFjQmdOVkJBb1RGVWR2YjJkc1pTQlVjblZ6ZENCVFpYSjJhV05sY3pFVE1CRUdBMVVFQXhNS1IxUlRJRU5CSURGUE1UQWVGdzB5TURBeE1UTXhNVFF4TkRsYUZ3MHlNVEF4TVRFeE1UUXhORGxhTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlFd3BEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIRXcxTmIzVnVkR0ZwYmlCV2FXVjNNUk13RVFZRFZRUUtFd3BIYjI5bmJHVWdURXhETVJzd0dRWURWUVFERXhKaGRIUmxjM1F1WVc1a2NtOXBaQzVqYjIwd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUNXRXJCUVRHWkdOMWlaYk45ZWhSZ2lmV0J4cWkyUGRneHcwM1A3VHlKWmZNeGpwNUw3ajFHTmVQSzVIemRyVW9JZDF5Q0l5Qk15eHFnYXpxZ3RwWDVXcHNYVzRWZk1oSmJOMVkwOXF6cXA2SkQrMlBaZG9UVTFrRlJBTVdmTC9VdVp0azdwbVJYZ0dtNWpLRHJaOU54ZTA0dk1ZUXI4OE5xd1cva2ZaMWdUT05JVVQwV3NMVC80NTIyQlJXeGZ3eGMzUUUxK1RLV2tMQ3J2ZWs2V2xJcXlhQzUyVzdNRFI4TXBGZWJ5bVNLVHZ3Zk1Sd3lLUUxUMDNVTDR2dDQ4eUVjOHNwN3dUQUhNL1dEZzhRb3RhcmY4T0JIa25vWjkyWGl2aWFWNnRRcWhST0hDZmdtbkNYaXhmVzB3RVhDdnFpTFRiUXRVYkxzUy84SVJ0ZFhrcFFCOUFnTUJBQUdqZ2dKWU1JSUNWREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUhBd0V3REFZRFZSMFRBUUgvQkFJd0FEQWRCZ05WSFE0RUZnUVU2REhCd3NBdmI1M2cvQzA3cHJUdnZ3TlFRTFl3SHdZRFZSMGpCQmd3Rm9BVW1OSDRiaERyejV2c1lKOFlrQnVnNjMwSi9Tc3daQVlJS3dZQkJRVUhBUUVFV0RCV01DY0dDQ3NHQVFVRkJ6QUJoaHRvZEhSd09pOHZiMk56Y0M1d2Eya3VaMjl2Wnk5bmRITXhiekV3S3dZSUt3WUJCUVVITUFLR0gyaDBkSEE2THk5d2Eya3VaMjl2Wnk5bmMzSXlMMGRVVXpGUE1TNWpjblF3SFFZRFZSMFJCQll3RklJU1lYUjBaWE4wTG1GdVpISnZhV1F1WTI5dE1DRUdBMVVkSUFRYU1CZ3dDQVlHWjRFTUFRSUNNQXdHQ2lzR0FRUUIxbmtDQlFNd0x3WURWUjBmQkNnd0pqQWtvQ0tnSUlZZWFIUjBjRG92TDJOeWJDNXdhMmt1WjI5dlp5OUhWRk14VHpFdVkzSnNNSUlCQkFZS0t3WUJCQUhXZVFJRUFnU0I5UVNCOGdEd0FIY0E5bHlVTDlGM01DSVVWQmdJTUpSV2p1Tk5FeGt6djk4TUx5QUx6RTd4Wk9NQUFBRnZudXkwWndBQUJBTUFTREJHQWlFQTdlLzBZUnUzd0FGbVdIMjdNMnZiVmNaL21ycCs0cmZZYy81SVBKMjlGNmdDSVFDbktDQ0FhY1ZOZVlaOENDZllkR3BCMkdzSHh1TU9Ia2EvTzQxaldlRit6Z0IxQUVTVVpTNnc3czZ2eEVBSDJLaitLTURhNW9LKzJNc3h0VC9UTTVhMXRvR29BQUFCYjU3c3RKTUFBQVFEQUVZd1JBSWdFWGJpb1BiSnA5cUMwRGoyNThERkdTUk1BVStaQjFFaVZFYmJiLzRVdk5FQ0lCaEhrQnQxOHZSbjl6RHZ5cmZ4eXVkY0hUT1NsM2dUYVlBLzd5VC9CaUg0TUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFESUFjUUJsbWQ4TUVnTGRycnJNYkJUQ3ZwTVhzdDUrd3gyRGxmYWpKTkpVUDRqWUZqWVVROUIzWDRFMnpmNDluWDNBeXVaRnhBcU9SbmJqLzVqa1k3YThxTUowajE5ekZPQitxZXJ4ZWMwbmhtOGdZbExiUW02c0tZN1AwZXhmcjdIdUszTWtQMXBlYzE0d0ZFVWFHcUR3VWJHZ2wvb2l6MzhGWENFK0NXOEUxUUFFVWZ2YlFQVFliS3hZait0Q05sc3MwYlRTb0wyWjJkL2ozQnBMM01GdzB5eFNLL1VUcXlrTHIyQS9NZGhKUW14aStHK01LUlNzUXI2MkFuWmF1OXE2WUZvaSs5QUVIK0E0OFh0SXlzaEx5Q1RVM0h0K2FLb2hHbnhBNXVsMVhSbXFwOEh2Y0F0MzlQOTVGWkdGSmUwdXZseWpPd0F6WHVNdTdNK1BXUmMiLCJNSUlFU2pDQ0F6S2dBd0lCQWdJTkFlTzBtcUdOaXFtQkpXbFF1REFOQmdrcWhraUc5dzBCQVFzRkFEQk1NU0F3SGdZRFZRUUxFeGRIYkc5aVlXeFRhV2R1SUZKdmIzUWdRMEVnTFNCU01qRVRNQkVHQTFVRUNoTUtSMnh2WW1Gc1UybG5iakVUTUJFR0ExVUVBeE1LUjJ4dlltRnNVMmxuYmpBZUZ3MHhOekEyTVRVd01EQXdOREphRncweU1URXlNVFV3TURBd05ESmFNRUl4Q3pBSkJnTlZCQVlUQWxWVE1SNHdIQVlEVlFRS0V4VkhiMjluYkdVZ1ZISjFjM1FnVTJWeWRtbGpaWE14RXpBUkJnTlZCQU1UQ2tkVVV5QkRRU0F4VHpFd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURRR005RjFJdk4wNXprUU85K3ROMXBJUnZKenp5T1RIVzVEekVaaEQyZVBDbnZVQTBRazI4RmdJQ2ZLcUM5RWtzQzRUMmZXQllrL2pDZkMzUjNWWk1kUy9kTjRaS0NFUFpSckF6RHNpS1VEelJybUJCSjV3dWRnem5kSU1ZY0xlL1JHR0ZsNXlPRElLZ2pFdi9TSkgvVUwrZEVhbHROMTFCbXNLK2VRbU1GKytBY3hHTmhyNTlxTS85aWw3MUkyZE44RkdmY2Rkd3VhZWo0YlhocDBMY1FCYmp4TWNJN0pQMGFNM1Q0SStEc2F4bUtGc2JqemFUTkM5dXpwRmxnT0lnN3JSMjV4b3luVXh2OHZObWtxN3pkUEdIWGt4V1k3b0c5aitKa1J5QkFCazdYckpmb3VjQlpFcUZKSlNQazdYQTBMS1cwWTN6NW96MkQwYzF0Skt3SEFnTUJBQUdqZ2dFek1JSUJMekFPQmdOVkhROEJBZjhFQkFNQ0FZWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0hRWURWUjBPQkJZRUZKalIrRzRRNjgrYjdHQ2ZHSkFib090OUNmMHJNQjhHQTFVZEl3UVlNQmFBRkp2aUIxZG5IQjdBYWdiZVdiU2FMZC9jR1lZdU1EVUdDQ3NHQVFVRkJ3RUJCQ2t3SnpBbEJnZ3JCZ0VGQlFjd0FZWVphSFIwY0RvdkwyOWpjM0F1Y0d0cExtZHZiMmN2WjNOeU1qQXlCZ05WSFI4RUt6QXBNQ2VnSmFBamhpRm9kSFJ3T2k4dlkzSnNMbkJyYVM1bmIyOW5MMmR6Y2pJdlozTnlNaTVqY213d1B3WURWUjBnQkRnd05qQTBCZ1puZ1F3QkFnSXdLakFvQmdnckJnRUZCUWNDQVJZY2FIUjBjSE02THk5d2Eya3VaMjl2Wnk5eVpYQnZjMmwwYjNKNUx6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFHb0ErTm5uNzh5NnBSamQ5WGxRV05hN0hUZ2laL3IzUk5Ha21VbVlIUFFxNlNjdGk5UEVhanZ3UlQyaVdUSFFyMDJmZXNxT3FCWTJFVFV3Z1pRK2xsdG9ORnZoc085dHZCQ09JYXpwc3dXQzlhSjl4anU0dFdEUUg4TlZVNllaWi9YdGVEU0dVOVl6SnFQalk4cTNNRHhyem1xZXBCQ2Y1bzhtdy93SjRhMkc2eHpVcjZGYjZUOE1jRE8yMlBMUkw2dTNNNFR6czNBMk0xajZieWtKWWk4d1dJUmRBdktMV1p1L2F4QlZielltcW13a201ekxTRFc1bklBSmJFTENRQ1p3TUg1NnQyRHZxb2Z4czZCQmNDRklaVVNweHU2eDZ0ZDBWN1N2SkNDb3NpclNtSWF0ai85ZFNTVkRRaWJldDhxLzdVSzR2NFpVTjgwYXRuWnoxeWc9PSJdfQ", + // Body + "eyJub25jZSI6IkFBQUFBQUFBQUFBQUFBQUFBQUFBQUE9PSIsInRpbWVzdGFtcE1zIjoxNjA4NTU4MzYzNzAyLCJhcGtQYWNrYWdlTmFtZSI6ImRlLnJraS5jb3JvbmF3YXJuYXBwLnRlc3QiLCJhcGtEaWdlc3RTaGEyNTYiOiI5b2lxT01RQVpmQmdDbkkwanlON1RnUEFRTlNTeFdyamgxNGYwZVhwQjNVPSIsImN0c1Byb2ZpbGVNYXRjaCI6ZmFsc2UsImFwa0NlcnRpZmljYXRlRGlnZXN0U2hhMjU2IjpbIjlWTHZVR1YwR2t4MjRldHJ1RUJZaWt2QXRxU1E5aVk2cll1S2hHK3h3S0U9Il0sImJhc2ljSW50ZWdyaXR5IjpmYWxzZSwiYWR2aWNlIjoiUkVTVE9SRV9UT19GQUNUT1JZX1JPTSxMT0NLX0JPT1RMT0FERVIiLCJldmFsdWF0aW9uVHlwZSI6IkJBU0lDIn0", + // Signature + "RJOCf-JTA58PitBWfYUAkJArnTE5r9QwQzApZk2tSk4r_CGoHzyI64i9HQFCp_ChhtemiHhtPk-20ifBZ4fIzCLeOdQABnF2ygKuheMrJxHbZFezO5WdQV3QpNkVBxoUqOq_Oq9NEf_3Bl8GHtyI4r-AczfJ9hlOIhJ2yAQpbxaeh-h4UJj6lSZ05-szYQXU3cukkHl1aSJmVK6hOJxtEv22MVK0fpIoi_4IzAuUFjcbrPsN8Lk5wisWCxnzfZ50AkrINXEQ4mMHZFwUQzRQ6zAakwyxH7gsGjU-0zkxyCIWg917Kpbp4MlVqOuUpDXcHJbh_-qduZ7jDTmP3zl7xg" + ).joinToString(".") + private val JWS_BODY = """ + { + "nonce": "AAAAAAAAAAAAAAAAAAAAAA==", + "timestampMs": 1608558363702, + "apkPackageName": "de.rki.coronawarnapp.test", + "apkDigestSha256": "9oiqOMQAZfBgCnI0jyN7TgPAQNSSxWrjh14f0eXpB3U=", + "ctsProfileMatch": false, + "apkCertificateDigestSha256": [ + "9VLvUGV0Gkx24etruEBYikvAtqSQ9iY6rYuKhG+xwKE=" + ], + "basicIntegrity": false, + "advice": "RESTORE_TO_FACTORY_ROM,LOCK_BOOTLOADER", + "evaluationType": "BASIC" + } + """.trimIndent() + private val JWS_HEADER = """ + { + "alg": "RS256", + "x5c": [ + "MIIFkzCCBHugAwIBAgIRANcSkjds5n6+CAAAAAApa0cwDQYJKoZIhvcNAQELBQAwQjELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczETMBEGA1UEAxMKR1RTIENBIDFPMTAeFw0yMDAxMTMxMTQxNDlaFw0yMTAxMTExMTQxNDlaMGwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgTExDMRswGQYDVQQDExJhdHRlc3QuYW5kcm9pZC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCWErBQTGZGN1iZbN9ehRgifWBxqi2Pdgxw03P7TyJZfMxjp5L7j1GNePK5HzdrUoId1yCIyBMyxqgazqgtpX5WpsXW4VfMhJbN1Y09qzqp6JD+2PZdoTU1kFRAMWfL/UuZtk7pmRXgGm5jKDrZ9Nxe04vMYQr88NqwW/kfZ1gTONIUT0WsLT/4522BRWxfwxc3QE1+TKWkLCrvek6WlIqyaC52W7MDR8MpFebymSKTvwfMRwyKQLT03UL4vt48yEc8sp7wTAHM/WDg8Qotarf8OBHknoZ92XiviaV6tQqhROHCfgmnCXixfW0wEXCvqiLTbQtUbLsS/8IRtdXkpQB9AgMBAAGjggJYMIICVDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU6DHBwsAvb53g/C07prTvvwNQQLYwHwYDVR0jBBgwFoAUmNH4bhDrz5vsYJ8YkBug630J/SswZAYIKwYBBQUHAQEEWDBWMCcGCCsGAQUFBzABhhtodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHMxbzEwKwYIKwYBBQUHMAKGH2h0dHA6Ly9wa2kuZ29vZy9nc3IyL0dUUzFPMS5jcnQwHQYDVR0RBBYwFIISYXR0ZXN0LmFuZHJvaWQuY29tMCEGA1UdIAQaMBgwCAYGZ4EMAQICMAwGCisGAQQB1nkCBQMwLwYDVR0fBCgwJjAkoCKgIIYeaHR0cDovL2NybC5wa2kuZ29vZy9HVFMxTzEuY3JsMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHcA9lyUL9F3MCIUVBgIMJRWjuNNExkzv98MLyALzE7xZOMAAAFvnuy0ZwAABAMASDBGAiEA7e/0YRu3wAFmWH27M2vbVcZ/mrp+4rfYc/5IPJ29F6gCIQCnKCCAacVNeYZ8CCfYdGpB2GsHxuMOHka/O41jWeF+zgB1AESUZS6w7s6vxEAH2Kj+KMDa5oK+2MsxtT/TM5a1toGoAAABb57stJMAAAQDAEYwRAIgEXbioPbJp9qC0Dj258DFGSRMAU+ZB1EiVEbbb/4UvNECIBhHkBt18vRn9zDvyrfxyudcHTOSl3gTaYA/7yT/BiH4MA0GCSqGSIb3DQEBCwUAA4IBAQDIAcQBlmd8MEgLdrrrMbBTCvpMXst5+wx2DlfajJNJUP4jYFjYUQ9B3X4E2zf49nX3AyuZFxAqORnbj/5jkY7a8qMJ0j19zFOB+qerxec0nhm8gYlLbQm6sKY7P0exfr7HuK3MkP1pec14wFEUaGqDwUbGgl/oiz38FXCE+CW8E1QAEUfvbQPTYbKxYj+tCNlss0bTSoL2Z2d/j3BpL3MFw0yxSK/UTqykLr2A/MdhJQmxi+G+MKRSsQr62AnZau9q6YFoi+9AEH+A48XtIyshLyCTU3Ht+aKohGnxA5ul1XRmqp8HvcAt39P95FZGFJe0uvlyjOwAzXuMu7M+PWRc", + "MIIESjCCAzKgAwIBAgINAeO0mqGNiqmBJWlQuDANBgkqhkiG9w0BAQsFADBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEyMTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3QgU2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxTzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQGM9F1IvN05zkQO9+tN1pIRvJzzyOTHW5DzEZhD2ePCnvUA0Qk28FgICfKqC9EksC4T2fWBYk/jCfC3R3VZMdS/dN4ZKCEPZRrAzDsiKUDzRrmBBJ5wudgzndIMYcLe/RGGFl5yODIKgjEv/SJH/UL+dEaltN11BmsK+eQmMF++AcxGNhr59qM/9il71I2dN8FGfcddwuaej4bXhp0LcQBbjxMcI7JP0aM3T4I+DsaxmKFsbjzaTNC9uzpFlgOIg7rR25xoynUxv8vNmkq7zdPGHXkxWY7oG9j+JkRyBABk7XrJfoucBZEqFJJSPk7XA0LKW0Y3z5oz2D0c1tJKwHAgMBAAGjggEzMIIBLzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFJjR+G4Q68+b7GCfGJAboOt9Cf0rMB8GA1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAlBggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0gBDgwNjA0BgZngQwBAgIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9yZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAGoA+Nnn78y6pRjd9XlQWNa7HTgiZ/r3RNGkmUmYHPQq6Scti9PEajvwRT2iWTHQr02fesqOqBY2ETUwgZQ+lltoNFvhsO9tvBCOIazpswWC9aJ9xju4tWDQH8NVU6YZZ/XteDSGU9YzJqPjY8q3MDxrzmqepBCf5o8mw/wJ4a2G6xzUr6Fb6T8McDO22PLRL6u3M4Tzs3A2M1j6bykJYi8wWIRdAvKLWZu/axBVbzYmqmwkm5zLSDW5nIAJbELCQCZwMH56t2Dvqofxs6BBcCFIZUSpxu6x6td0V7SvJCCosirSmIatj/9dSSVDQibet8q/7UK4v4ZUN80atnZz1yg==" + ] + } + """.trimIndent() + private const val JWS_SIGNATURE_BASE64 = + "RJOCf-JTA58PitBWfYUAkJArnTE5r9QwQzApZk2tSk4r_CGoHzyI64i9HQFCp_ChhtemiHhtPk-20ifBZ4fIzCLeOdQABnF2ygKuheMrJxHbZFezO5WdQV3QpNkVBxoUqOq_Oq9NEf_3Bl8GHtyI4r-AczfJ9hlOIhJ2yAQpbxaeh-h4UJj6lSZ05-szYQXU3cukkHl1aSJmVK6hOJxtEv22MVK0fpIoi_4IzAuUFjcbrPsN8Lk5wisWCxnzfZ50AkrINXEQ4mMHZFwUQzRQ6zAakwyxH7gsGjU-0zkxyCIWg917Kpbp4MlVqOuUpDXcHJbh_-qduZ7jDTmP3zl7xg" + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..60881555c0fd63ac559f40a8b0599a73f01f8e0e --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt @@ -0,0 +1,63 @@ +package de.rki.coronawarnapp.datadonation.storage + +import android.content.Context +import com.google.gson.Gson +import de.rki.coronawarnapp.datadonation.OneTimePassword +import de.rki.coronawarnapp.datadonation.survey.SurveySettings +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.preferences.MockSharedPreferences +import java.util.UUID + +class OTPRepositoryTest : BaseTest() { + + @MockK lateinit var context: Context + @MockK lateinit var surveySettings: SurveySettings + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + @Test + fun `last otp is read from preferences`() { + val uuid = UUID.fromString("e103c755-0975-4588-a639-d0cd1ba421a0") + val time = Instant.ofEpochMilli(1612381131014) + every { surveySettings.oneTimePassword } returns OneTimePassword(uuid, time) + val lastOTP = OTPRepository(surveySettings).lastOTP + lastOTP shouldNotBe null + lastOTP!!.apply { + uuid shouldBe uuid + time.millis shouldBe 1612381131014 + } + } + + @Test + fun `otp is stored upon creation`() { + every { context.getSharedPreferences("survey_localdata", Context.MODE_PRIVATE) } returns MockSharedPreferences() + val settings = SurveySettings(context, Gson()) + settings.oneTimePassword shouldBe null + val generated = OTPRepository(settings).generateOTP() + generated shouldBe settings.oneTimePassword + } + + @Test + fun `no last otp`() { + every { surveySettings.oneTimePassword } returns null + OTPRepository(surveySettings).lastOTP shouldBe null + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..d7564fe8fefadc5e3a47f44ce8fa3be6d0696d58 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt @@ -0,0 +1,91 @@ +package de.rki.coronawarnapp.datadonation.survey + +import android.content.Context +import com.google.gson.Gson +import de.rki.coronawarnapp.datadonation.OneTimePassword +import de.rki.coronawarnapp.util.serialization.SerializationModule +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.preferences.MockSharedPreferences +import java.util.UUID + +class SurveySettingsTest : BaseTest() { + + @MockK lateinit var context: Context + val preferences = MockSharedPreferences() + lateinit var baseGson: Gson + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + baseGson = SerializationModule().baseGson().newBuilder().apply { + setPrettyPrinting() + }.create() + every { context.getSharedPreferences("survey_localdata", Context.MODE_PRIVATE) } returns preferences + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + @Test + fun `load and deserialize json`() { + val instance = SurveySettings(context, baseGson) + instance.oneTimePassword shouldBe null + + preferences.edit().putString( + "one_time_password", + """ + { + "uuid":"e103c755-0975-4588-a639-d0cd1ba421a1", + "time": 1612381217442 + } + """.trimIndent() + ).apply() + + val value = instance.oneTimePassword + value shouldNotBe null + value!!.uuid.toString() shouldBe "e103c755-0975-4588-a639-d0cd1ba421a1" + value.time.millis shouldBe 1612381217442 + } + + @Test + fun `parsing error`() { + val instance = SurveySettings(context, baseGson) + instance.oneTimePassword shouldBe null + + preferences + .edit() + .putString("one_time_password", "invalid value") + .apply() + + instance.oneTimePassword shouldBe null + } + + @Test + fun `save and serialize json`() { + val uuid = UUID.fromString("e103c755-0975-4588-a639-d0cd1ba421a0") + val time = Instant.ofEpochMilli(1612381567242) + + val instance = SurveySettings(context, baseGson) + instance.oneTimePassword = OneTimePassword(uuid, time) + + val value = preferences.getString("one_time_password", null) + value shouldBe """ + { + "uuid": "e103c755-0975-4588-a639-d0cd1ba421a0", + "time": 1612381567242 + } + """.trimIndent() + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt index f2827cb3c90c7eafa930879a06d1d725b530f5fb..b00775a9457596d55715a8fe2facb583e21a6b2e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/environment/EnvironmentSetupTest.kt @@ -116,10 +116,10 @@ class EnvironmentSetupTest : BaseTest() { EnvironmentSetup.Type.PRODUCTION.rawKey shouldBe "PROD" EnvironmentSetup.Type.DEV.rawKey shouldBe "DEV" EnvironmentSetup.Type.INT.rawKey shouldBe "INT" - EnvironmentSetup.Type.INT_FED.rawKey shouldBe "INT-FED" EnvironmentSetup.Type.WRU.rawKey shouldBe "WRU" EnvironmentSetup.Type.WRU_XA.rawKey shouldBe "WRU-XA" EnvironmentSetup.Type.WRU_XD.rawKey shouldBe "WRU-XD" + EnvironmentSetup.Type.LOCAL.rawKey shouldBe "LOCAL" EnvironmentSetup.Type.values().size shouldBe 7 EnvironmentSetup.EnvKey.USE_EUR_KEY_PKGS.rawKey shouldBe "USE_EUR_KEY_PKGS" @@ -136,9 +136,9 @@ class EnvironmentSetupTest : BaseTest() { private const val BAD_JSON = "{ environmentType: {\n \"SUBMISSION_CDN_U" private val ENVS_WITH_EUR_PKGS = listOf( EnvironmentSetup.Type.PRODUCTION, - EnvironmentSetup.Type.INT_FED, EnvironmentSetup.Type.WRU_XD, - EnvironmentSetup.Type.WRU_XA + EnvironmentSetup.Type.WRU_XA, + EnvironmentSetup.Type.LOCAL ) private const val GOOD_JSON = """ { @@ -169,15 +169,6 @@ class EnvironmentSetupTest : BaseTest() { "SAFETYNET_API_KEY": "placeholder-INT", "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-INT" }, - "INT-FED": { - "USE_EUR_KEY_PKGS" : true, - "SUBMISSION_CDN_URL": "https://submission-INT-FED", - "DOWNLOAD_CDN_URL": "https://download-INT-FED", - "VERIFICATION_CDN_URL": "https://verification-INT-FED", - "DATA_DONATION_CDN_URL": "https://placeholder-INT-FED", - "SAFETYNET_API_KEY": "placeholder-INT-FED", - "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-INT-FED" - }, "WRU": { "USE_EUR_KEY_PKGS" : false, "SUBMISSION_CDN_URL": "https://submission-WRU", @@ -204,6 +195,15 @@ class EnvironmentSetupTest : BaseTest() { "DATA_DONATION_CDN_URL": "https://placeholder-WRU-XA", "SAFETYNET_API_KEY": "placeholder-WRU-XA", "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-WRU-XA" + }, + "LOCAL": { + "USE_EUR_KEY_PKGS" : true, + "SUBMISSION_CDN_URL": "https://submission-LOCAL", + "DOWNLOAD_CDN_URL": "https://download-LOCAL", + "VERIFICATION_CDN_URL": "https://verification-LOCAL", + "DATA_DONATION_CDN_URL": "https://placeholder-LOCAL", + "SAFETYNET_API_KEY": "placeholder-LOCAL", + "PUB_KEYS_SIGNATURE_VERIFICATION": "12345678-LOCAL" } } """ diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/CWASettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/CWASettingsTest.kt index 437ba8b217cbf197d8a3985f130b2042ef9d2a1c..eb223628fbcc9e1b059d3a035494c243228203db 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/CWASettingsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/CWASettingsTest.kt @@ -1,10 +1,12 @@ package de.rki.coronawarnapp.main import android.content.Context +import de.rki.coronawarnapp.appconfig.ConfigData import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK +import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -37,4 +39,46 @@ class CWASettingsTest : BaseTest() { preferences.dataMapPeek["devicetime.incorrect.acknowledged"] shouldBe false } } + + @Test + fun `update first reliable device time`() { + createInstance().apply { + preferences.dataMapPeek.isEmpty() shouldBe true + + firstReliableDeviceTime shouldBe Instant.EPOCH + firstReliableDeviceTime = Instant.ofEpochMilli(12345L) + preferences.dataMapPeek["devicetime.correct.first"] shouldBe 12345L + + firstReliableDeviceTime = Instant.ofEpochMilli(9999L) + preferences.dataMapPeek["devicetime.correct.first"] shouldBe 9999L + } + } + + @Test + fun `update last device time state change timestamp`() { + createInstance().apply { + preferences.dataMapPeek.isEmpty() shouldBe true + + lastDeviceTimeStateChangeAt shouldBe Instant.EPOCH + lastDeviceTimeStateChangeAt = Instant.ofEpochMilli(1111L) + preferences.dataMapPeek["devicetime.laststatechange.timestamp"] shouldBe 1111L + + lastDeviceTimeStateChangeAt = Instant.ofEpochMilli(2222L) + preferences.dataMapPeek["devicetime.laststatechange.timestamp"] shouldBe 2222L + } + } + + @Test + fun `update last device time state change state`() { + createInstance().apply { + preferences.dataMapPeek.isEmpty() shouldBe true + + lastDeviceTimeStateChangeState shouldBe ConfigData.DeviceTimeState.INCORRECT + lastDeviceTimeStateChangeState = ConfigData.DeviceTimeState.CORRECT + preferences.dataMapPeek["devicetime.laststatechange.state"] shouldBe "CORRECT" + + lastDeviceTimeStateChangeState = ConfigData.DeviceTimeState.ASSUMED_CORRECT + preferences.dataMapPeek["devicetime.laststatechange.state"] shouldBe "ASSUMED_CORRECT" + } + } } 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 10efd2034d8863d12170bfd6443ea0c5384580d1..6fb7cf9737f5433564462c87b09bf8127c79fc90 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 @@ -1,6 +1,8 @@ package de.rki.coronawarnapp.main +import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings import de.rki.coronawarnapp.environment.EnvironmentSetup +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.main.MainActivityViewModel import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.device.BackgroundModeStatus @@ -24,12 +26,17 @@ class MainActivityViewModelTest : BaseTest() { @MockK lateinit var environmentSetup: EnvironmentSetup @MockK lateinit var backgroundModeStatus: BackgroundModeStatus + @MockK lateinit var diarySettings: ContactDiarySettings @BeforeEach fun setup() { MockKAnnotations.init(this) + mockkObject(LocalData) mockkObject(CWADebug) + + every { LocalData.isBackgroundCheckDone() } returns true + every { environmentSetup.currentEnvironment } returns EnvironmentSetup.Type.WRU } @AfterEach @@ -40,7 +47,8 @@ class MainActivityViewModelTest : BaseTest() { private fun createInstance(): MainActivityViewModel = MainActivityViewModel( dispatcherProvider = TestDispatcherProvider(), environmentSetup = environmentSetup, - backgroundModeStatus = backgroundModeStatus + backgroundModeStatus = backgroundModeStatus, + contactDiarySettings = diarySettings ) @Test @@ -69,4 +77,20 @@ class MainActivityViewModelTest : BaseTest() { val vm = createInstance() vm.showEnvironmentHint.value shouldBe null } + + @Test + fun `User is not onboarded when settings returns NOT_ONBOARDED `() { + every { diarySettings.onboardingStatus } returns ContactDiarySettings.OnboardingStatus.NOT_ONBOARDED + val vm = createInstance() + vm.onBottomNavSelected() + vm.isOnboardingDone.value shouldBe false + } + + @Test + fun `User is onboarded when settings returns RISK_STATUS_1_12 `() { + every { diarySettings.onboardingStatus } returns ContactDiarySettings.OnboardingStatus.RISK_STATUS_1_12 + val vm = createInstance() + vm.onBottomNavSelected() + vm.isOnboardingDone.value shouldBe true + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt index f0361f63d6ede3955ee581c3358ac21d04908e3c..2c06be47afc1095049636f2238b486ea8355b5f1 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt @@ -2,9 +2,12 @@ package de.rki.coronawarnapp.main.home import android.content.Context import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.ShareTestResultNotificationService +import de.rki.coronawarnapp.risk.TimeVariables import de.rki.coronawarnapp.statistics.source.StatisticsProvider +import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.submission.ui.homecards.SubmissionDone @@ -14,6 +17,7 @@ import de.rki.coronawarnapp.tracing.GeneralTracingStatus.Status import de.rki.coronawarnapp.tracing.states.LowRisk import de.rki.coronawarnapp.tracing.states.TracingStateProvider import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState +import de.rki.coronawarnapp.ui.main.home.HomeFragmentEvents import de.rki.coronawarnapp.ui.main.home.HomeFragmentViewModel import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE_TELETAN @@ -27,6 +31,7 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import io.mockk.mockkObject import io.mockk.verify import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow @@ -40,6 +45,7 @@ import testhelpers.BaseTest import testhelpers.TestDispatcherProvider import testhelpers.extensions.CoroutinesTestExtension import testhelpers.extensions.InstantExecutorExtension +import testhelpers.extensions.getOrAwaitValue import testhelpers.extensions.observeForTesting @ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class) @@ -169,4 +175,35 @@ class HomeFragmentViewModelTest : BaseTest() { } } } + + @Test + fun `test correct order of displaying delta onboarding, release notes and popups`() { + + mockkObject(LocalData) + every { LocalData.isInteroperabilityShownAtLeastOnce } returns false andThen true + + mockkObject(BuildConfigWrap) + every { BuildConfigWrap.VERSION_CODE } returns 1120004 + every { cwaSettings.lastChangelogVersion.value } returns 1L andThen 1120004 + + every { LocalData.tracingExplanationDialogWasShown() } returns false andThen true + mockkObject(TimeVariables) + coEvery { TimeVariables.getActiveTracingDaysInRetentionPeriod() } coAnswers { 1 } + + every { errorResetTool.isResetNoticeToBeShown } returns false andThen true + + with(createInstance()) { + showPopUpsOrNavigate() + popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowInteropDeltaOnboarding + + showPopUpsOrNavigate() + popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowNewReleaseFragment + + showPopUpsOrNavigate() + popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowTracingExplanation(1) + + showPopUpsOrNavigate() + popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowErrorResetDialog + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/release/NewReleaseInfoViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/release/NewReleaseInfoViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6bc5deee8229f2e495d10ad67cbb1f73377c5fee --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/release/NewReleaseInfoViewModelTest.kt @@ -0,0 +1,64 @@ +package de.rki.coronawarnapp.release + +import de.rki.coronawarnapp.main.CWASettings +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension + +@ExtendWith(InstantExecutorExtension::class) +class NewReleaseInfoViewModelTest { + + @MockK lateinit var settings: CWASettings + lateinit var viewModel: NewReleaseInfoViewModel + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + every { settings.lastChangelogVersion.update(any()) } just Runs + viewModel = NewReleaseInfoViewModel( + TestDispatcherProvider(), + settings + ) + } + + @Test + fun testOnNextButtonClick() { + viewModel.onNextButtonClick() + viewModel.routeToScreen.value shouldBe NewReleaseInfoNavigationEvents.CloseScreen + } + + @Test + fun testGetInfoItem() { + val item1 = NewReleaseInfoItem("title", "body") + val item2 = NewReleaseInfoItem("title2", "body2") + val titles = arrayOf(item1.title, item2.title) + val bodies = arrayOf(item1.body, item2.body) + viewModel.getItems(titles, bodies) shouldBe listOf(item1, item2) + } + + @Test + fun testGetInfoItemTitleMissing() { + val item1 = NewReleaseInfoItem("title", "body") + val item2 = NewReleaseInfoItem("title2", "body2") + val titles = arrayOf(item1.title) + val bodies = arrayOf(item1.body, item2.body) + viewModel.getItems(titles, bodies) shouldBe listOf(item1) + } + + @Test + fun testGetInfoItemBodyMissing() { + val item1 = NewReleaseInfoItem("title", "body") + val item2 = NewReleaseInfoItem("title2", "body2") + val titles = arrayOf(item1.title, item2.title) + val bodies = arrayOf(item1.body) + viewModel.getItems(titles, bodies) shouldBe listOf(item1) + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt index 22fe1e1f8b75d3ad14166597f280c8a7aa1bb870..d148b17682f2c063a4e92a99ae4f40959f7d8a62 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryStorage import de.rki.coronawarnapp.task.TaskController +import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppInjector @@ -157,4 +158,73 @@ class SubmissionRepositoryTest { submissionSettings.clear() } } + + @Test + fun `ui state is SUBMITTED_FINAL when submission was done`() = runBlockingTest { + coEvery { LocalData.submissionWasSuccessful() } returns true + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe + NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL) + } + + @Test + fun `ui state is UNPAIRED when no token is present`() = runBlockingTest { + coEvery { LocalData.submissionWasSuccessful() } returns false + coEvery { LocalData.registrationToken() } returns null + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe + NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED) + } + + @Test + fun `ui state is PAIRED_POSITIVE when allowed to submit`() = runBlockingTest { + coEvery { LocalData.submissionWasSuccessful() } returns false + coEvery { LocalData.registrationToken() } returns "token" + coEvery { LocalData.isAllowedToSubmitDiagnosisKeys() } returns true + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe + NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_POSITIVE) + } + + @Test + fun `refresh when state is PAIRED_NO_RESULT`() = runBlockingTest { + coEvery { LocalData.submissionWasSuccessful() } returns false + coEvery { LocalData.registrationToken() } returns "token" + coEvery { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false + coEvery { submissionService.asyncRequestTestResult(any()) } returns TestResult.PENDING + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe + NetworkRequestWrapper.RequestSuccessful(DeviceUIState.PAIRED_NO_RESULT) + coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) } + } + + @Test + fun `refresh when state is UNPAIRED`() = runBlockingTest { + coEvery { LocalData.submissionWasSuccessful() } returns false + coEvery { LocalData.registrationToken() } returns null + coEvery { LocalData.isAllowedToSubmitDiagnosisKeys() } returns false + coEvery { submissionService.asyncRequestTestResult(any()) } returns TestResult.PENDING + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe + NetworkRequestWrapper.RequestSuccessful(DeviceUIState.UNPAIRED) + coEvery { LocalData.registrationToken() } returns "token" + submissionRepository.refreshDeviceUIState() + coVerify(exactly = 1) { submissionService.asyncRequestTestResult(any()) } + } + + @Test + fun `no refresh when state is SUBMITTED_FINAL`() = runBlockingTest { + coEvery { LocalData.submissionWasSuccessful() } returns true + val submissionRepository = createInstance(scope = this) + submissionRepository.refreshDeviceUIState() + submissionRepository.deviceUIStateFlow.first() shouldBe + NetworkRequestWrapper.RequestSuccessful(DeviceUIState.SUBMITTED_FINAL) + submissionRepository.refreshDeviceUIState() + coVerify(exactly = 0) { submissionService.asyncRequestTestResult(any()) } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a75261abf4332067b01099d1c0ee30bf93b4a81 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/submission/testresult/pending/SubmissionTestResultPendingViewModelTest.kt @@ -0,0 +1,71 @@ +package de.rki.coronawarnapp.submission.testresult.pending + +import de.rki.coronawarnapp.exception.http.CwaWebException +import de.rki.coronawarnapp.notification.ShareTestResultNotificationService +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingViewModel +import de.rki.coronawarnapp.util.DeviceUIState +import de.rki.coronawarnapp.util.NetworkRequestWrapper +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.TestCoroutineScope +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.BaseTest +import testhelpers.asDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension + +@ExtendWith(InstantExecutorExtension::class) +class SubmissionTestResultPendingViewModelTest : BaseTest() { + + @MockK lateinit var shareTestResultNotificationService: ShareTestResultNotificationService + @MockK lateinit var submissionRepository: SubmissionRepository + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + submissionRepository.apply { + every { hasGivenConsentToSubmission } returns emptyFlow() + every { deviceUIStateFlow } returns emptyFlow() + every { testResultReceivedDateFlow } returns emptyFlow() + } + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + fun createInstance(scope: CoroutineScope = TestCoroutineScope()) = SubmissionTestResultPendingViewModel( + dispatcherProvider = scope.asDispatcherProvider(), + shareTestResultNotificationService = shareTestResultNotificationService, + submissionRepository = submissionRepository + ) + + @Test + fun `web exception handling`() { + val expectedType = NetworkRequestWrapper.RequestFailed<DeviceUIState, CwaWebException>( + CwaWebException(statusCode = 1, message = "message") + ) + val unexpectedType = + NetworkRequestWrapper.RequestFailed<DeviceUIState, Throwable>(UnsupportedOperationException()) + val deviceUI = MutableStateFlow<NetworkRequestWrapper<DeviceUIState, Throwable>>(expectedType) + every { submissionRepository.deviceUIStateFlow } returns deviceUI + createInstance().apply { + cwaWebExceptionLiveData.observeForever {} + cwaWebExceptionLiveData.value shouldBe expectedType.error + + deviceUI.value = unexpectedType + cwaWebExceptionLiveData.value shouldBe unexpectedType.error + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..14d885d7e2156452b2d102e41efcbb8e6e8d9bce --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/states/IncreasedRiskTest.kt @@ -0,0 +1,74 @@ +package de.rki.coronawarnapp.tracing.states + +import android.content.Context +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.risk.RiskState +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.slot +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +internal class IncreasedRiskTest { + + @MockK private lateinit var context: Context + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + } + + @AfterEach + fun tearDown() { + clearAllMocks() + } + + @Test + fun `getRiskContactLast() should return null when lastEncounterAt == null`() { + val risk = defaultRisk.copy(lastEncounterAt = null) + risk.getRiskContactLast(context) shouldBe null + } + + @Test + fun `getRiskContactLast() should return correct string when daysWithEncounters == 1`() { + val slot = slot<String>() + every { + context.getString( + R.string.risk_card_high_risk_most_recent_body_encounter_on_single_day, + capture(slot) + ) + } answers { "On ${slot.captured}" } + + val risk = defaultRisk.copy(daysWithEncounters = 1) + risk.getRiskContactLast(context) shouldBe "On ${slot.captured}" + } + + @Test + fun `getRiskContactLast() should return correct string when daysWithEncounters is greater 1`() { + val slot = slot<String>() + every { + context.getString( + R.string.risk_card_high_risk_most_recent_body_encounters_on_more_than_one_day, + capture(slot) + ) + } answers { "Most recently on ${slot.captured}" } + + val risk = defaultRisk.copy(daysWithEncounters = 2) + risk.getRiskContactLast(context) shouldBe "Most recently on ${slot.captured}" + } + + private val defaultRisk = IncreasedRisk( + riskState = RiskState.INCREASED_RISK, + isInDetailsMode = false, + lastExposureDetectionTime = Instant.now(), + allowManualUpdate = false, + daysWithEncounters = 1, + activeTracingDays = 1, + lastEncounterAt = Instant.now() + ) +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..2a8aa6e4a768e9972e8bc72eb1a1b2d09f682a04 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/tracing/ui/details/TracingDetailsItemProviderTest.kt @@ -0,0 +1,335 @@ +package de.rki.coronawarnapp.tracing.ui.details + +import android.content.Context +import android.content.res.Resources +import com.google.android.gms.nearby.exposurenotification.ExposureWindow +import de.rki.coronawarnapp.risk.ProtoRiskLevel +import de.rki.coronawarnapp.risk.RiskLevelTaskResult +import de.rki.coronawarnapp.risk.result.AggregatedRiskResult +import de.rki.coronawarnapp.risk.storage.RiskLevelStorage +import de.rki.coronawarnapp.storage.TracingRepository +import de.rki.coronawarnapp.tracing.GeneralTracingStatus +import de.rki.coronawarnapp.tracing.ui.details.items.additionalinfos.AdditionalInfoLowRiskBox +import de.rki.coronawarnapp.tracing.ui.details.items.behavior.BehaviorIncreasedRiskBox +import de.rki.coronawarnapp.tracing.ui.details.items.behavior.BehaviorNormalRiskBox +import de.rki.coronawarnapp.tracing.ui.details.items.periodlogged.PeriodLoggedBox +import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsFailedCalculationBox +import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsIncreasedRiskBox +import de.rki.coronawarnapp.tracing.ui.details.items.riskdetails.DetailsLowRiskBox +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import org.joda.time.Instant +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.coroutines.test + +class TracingDetailsItemProviderTest : BaseTest() { + + @MockK(relaxed = true) lateinit var context: Context + @MockK(relaxed = true) lateinit var resources: Resources + @MockK(relaxed = true) lateinit var aggregatedRiskResult: AggregatedRiskResult + + @MockK lateinit var tracingStatus: GeneralTracingStatus + @MockK lateinit var tracingRepository: TracingRepository + @MockK lateinit var riskLevelStorage: RiskLevelStorage + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + every { context.resources } returns resources + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createInstance() = TracingDetailsItemProvider( + tracingStatus = tracingStatus, + tracingRepository = tracingRepository, + riskLevelStorage = riskLevelStorage + ) + + private fun prepare( + status: GeneralTracingStatus.Status, + riskLevel: ProtoRiskLevel, + matchedKeyCount: Int + ) { + every { tracingStatus.generalStatus } returns flowOf(status) + every { tracingRepository.activeTracingDaysInRetentionPeriod } returns flowOf(0) + every { aggregatedRiskResult.totalRiskLevel } returns riskLevel + + if (riskLevel == ProtoRiskLevel.LOW) { + every { aggregatedRiskResult.isLowRisk() } returns true + } else if (riskLevel == ProtoRiskLevel.HIGH) { + every { aggregatedRiskResult.isIncreasedRisk() } returns true + } + + val exposureWindow: ExposureWindow = mockk() + + val riskLevelResult = RiskLevelTaskResult( + calculatedAt = Instant.EPOCH, + aggregatedRiskResult = aggregatedRiskResult, + exposureWindows = listOf(exposureWindow) + ) + every { riskLevelResult.matchedKeyCount } returns matchedKeyCount + every { riskLevelStorage.latestAndLastSuccessful } returns flowOf(listOf(riskLevelResult)) + } + + @Test + fun `additional info low risk box`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 1 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe true + } + + @Test + fun `no additional info low risk box due to matched key count`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + } + + @Test + fun `no additional info low risk box due to high risk`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.HIGH, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + } + + @Test + fun `increased risk box and no normal risk box`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.HIGH, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe false + } + + @Test + fun `normal risk box and no increased risk box`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + } + + @Test + fun `period logged box with low risk`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe true + } + + @Test + fun `period logged box with high risk`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.HIGH, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe true + } + + @Test + fun `no period logged box due to failed calculation`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.UNRECOGNIZED, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe false + } + + @Test + fun `no period logged box due to inactive tracing`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_INACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe false + } + + @Test + fun `failed calculation box due to inactive tracing`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_INACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsFailedCalculationBox.Item } shouldBe true + testCollector.latestValue!!.any { it is DetailsLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsIncreasedRiskBox.Item } shouldBe false + } + + @Test + fun `failed calculation box due to failed calculation`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.UNRECOGNIZED, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsFailedCalculationBox.Item } shouldBe true + testCollector.latestValue!!.any { it is DetailsLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsIncreasedRiskBox.Item } shouldBe false + } + + @Test + fun `low risk box no high risk box`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.LOW, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe true + testCollector.latestValue!!.any { it is DetailsFailedCalculationBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsLowRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is DetailsIncreasedRiskBox.Item } shouldBe false + } + + @Test + fun `high risk box no low risk box`() = runBlockingTest { + + prepare( + status = GeneralTracingStatus.Status.TRACING_ACTIVE, + riskLevel = ProtoRiskLevel.HIGH, + matchedKeyCount = 0 + ) + + val instance = createInstance() + val testCollector = instance.state.test(startOnScope = this) + + testCollector.latestValue!!.size.shouldBeGreaterThan(0) + testCollector.latestValue!!.any { it is AdditionalInfoLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is BehaviorIncreasedRiskBox.Item } shouldBe true + testCollector.latestValue!!.any { it is BehaviorNormalRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is PeriodLoggedBox.Item } shouldBe true + testCollector.latestValue!!.any { it is DetailsFailedCalculationBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsLowRiskBox.Item } shouldBe false + testCollector.latestValue!!.any { it is DetailsIncreasedRiskBox.Item } shouldBe true + } +} diff --git a/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt b/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt index 321a733ae210c6fa0dd2600912032fdbb4d08159..fb66d2327ce2a0ad7044fb74adce0dd7f10fc631 100644 --- a/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt +++ b/Corona-Warn-App/src/test/java/testhelpers/gms/MockGMSTask.kt @@ -5,6 +5,7 @@ import com.google.android.gms.tasks.OnSuccessListener import com.google.android.gms.tasks.Task import io.mockk.every import io.mockk.mockk +import timber.log.Timber object MockGMSTask { fun <T> forError(error: Exception): Task<T> = mockk<Task<T>>().apply { @@ -24,4 +25,13 @@ object MockGMSTask { } every { addOnFailureListener(any()) } returns this } + + fun <T> timeout(): Task<T> = mockk<Task<T>>().apply { + every { addOnSuccessListener(any()) } answers { + val listener = arg<OnSuccessListener<T>>(0) + Timber.v("Listener set, but we will time it out: %s", listener) + this@apply + } + every { addOnFailureListener(any()) } returns this + } } diff --git a/Gemfile.lock b/Gemfile.lock index 5be70a69121e37ed0c5e9dfe8e30fc6d0a5bc200..b8511d468096a4e8182ec963b7209cb912ca7165 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,15 +4,16 @@ GEM CFPropertyList (3.0.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) + artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.414.0) - aws-sdk-core (3.110.0) + aws-partitions (1.422.0) + aws-sdk-core (3.111.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.40.0) + aws-sdk-kms (1.41.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-s3 (1.87.0) @@ -43,13 +44,14 @@ GEM faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday-net_http (1.0.0) + faraday-net_http (1.0.1) faraday_middleware (1.0.0) faraday (~> 1.0) fastimage (2.2.1) - fastlane (2.171.0) + fastlane (2.172.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) + artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) @@ -92,20 +94,35 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.12) + google-apis-core (0.2.1) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.14) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + rexml + signet (~> 0.14) + webrick + google-apis-iamcredentials_v1 (0.1.0) + google-apis-core (~> 0.1) + google-apis-storage_v1 (0.1.0) + google-apis-core (~> 0.1) google-cloud-core (1.5.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) google-cloud-env (1.4.0) faraday (>= 0.17.3, < 2.0) google-cloud-errors (1.0.1) - google-cloud-storage (1.29.2) + google-cloud-storage (1.30.0) addressable (~> 2.5) digest-crc (~> 0.4) - google-api-client (~> 0.33) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.14.0) + googleauth (0.15.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -125,7 +142,7 @@ GEM multi_json (1.15.0) multipart-post (2.0.0) nanaimo (0.3.0) - naturally (2.2.0) + naturally (2.2.1) os (1.1.1) plist (3.6.0) public_suffix (4.0.6) @@ -135,11 +152,12 @@ GEM declarative-option (< 0.2.0) uber (< 0.2.0) retriable (3.1.2) + rexml (3.2.4) rouge (2.0.7) - ruby2_keywords (0.0.2) + ruby2_keywords (0.0.4) rubyzip (2.3.0) security (0.1.3) - signet (0.14.0) + signet (0.14.1) addressable (~> 2.3) faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) @@ -160,6 +178,7 @@ GEM unf_ext unf_ext (0.0.7.7) unicode-display_width (1.7.0) + webrick (1.7.0) word_wrap (1.0.0) xcodeproj (1.19.0) CFPropertyList (>= 2.3.3, < 4.0) diff --git a/build.gradle b/build.gradle index 387bd93f213f0794611951c16d3e4dea2af20a16..34ff6a9fee950ffd3ce760ab0beda2188a274b01 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,10 @@ buildscript { } } dependencies { + // https://issuetracker.google.com/issues/176381203 + // Fixed in bleeding-edge + // Can be upgraded once new Android version is released or + // the specific version is available via maven classpath 'com.android.tools:r8:2.0.88' classpath 'com.android.tools.build:gradle:4.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/prod_environments.json b/prod_environments.json index 94a8913e4f6de10b77706507ce6549c1608d2990..314c1c69483fa3a3f609a7913150a6b449efa95b 100644 --- a/prod_environments.json +++ b/prod_environments.json @@ -4,7 +4,7 @@ "SUBMISSION_CDN_URL": "https://submission.coronawarn.app", "DOWNLOAD_CDN_URL": "https://svc90.main.px.t-online.de", "VERIFICATION_CDN_URL": "https://verification.coronawarn.app", - "DATA_DONATION_CDN_URL": "https://placeholder", + "DATA_DONATION_CDN_URL": "https://data.coronawarn.app", "SAFETYNET_API_KEY": "placeholder", "PUB_KEYS_SIGNATURE_VERIFICATION": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEc7DEstcUIRcyk35OYDJ95/hTg3UVhsaDXKT0zK7NhHPXoyzipEnOp3GyNXDVpaPi3cAfQmxeuFMZAIX2+6A5Xg==" }