From 47f39c11d168b01cb9e3c9b77a17c0e95ff21cb6 Mon Sep 17 00:00:00 2001 From: Juraj Kusnier <jurajkusnier@users.noreply.github.com> Date: Tue, 23 Feb 2021 11:25:30 +0100 Subject: [PATCH] Add App launcher shortcut for contact journal (EXPOSUREAPP-4659) (#2384) * Add app shortcut for contact diary (#2130) * Fix ktlint issue (#2130) * Fix merge: ContactDiary Activity > Fragment * Introduce dynamic shortcuts * Update Test Menu (Onboarding) * Update Test Menu * Handle onboarding * Update strings * App shortcut improvements * Code formatting * Update tests * Use ShortcutManagerCompat instead of ShortcutManager * Update contact diary shortcut icon * Update test menu * Onboarding refactoring (EXPOSUREAPP-4659) (#2398) * Move NewRelease and DeltaOnboarding Fragments to OnboardingActivity * Update method names * Refresh shortcuts in non UI thread * Use deeplinks for navigation * Code cleaning and refactoring * Update shortcut name after UA Feedback Co-authored-by: PhilippNowak96 <dev@philipp-nowak.com> Co-authored-by: Ralf Gehrer <ralfgehrer@users.noreply.github.com> Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com> Co-authored-by: ralfgehrer <mail@ralfgehrer.com> --- .../coronawarnapp/ui/main/MainActivityTest.kt | 8 +- .../ui/main/home/HomeFragmentTest.kt | 8 +- ...bmissionTestResultAvailableFragmentTest.kt | 5 + .../ui/DeltaOnboardingFragmentModule.kt | 18 +++ .../ui/DeltaOnboardingFragmentViewModel.kt | 53 +++++++ .../ui/DeltaonboardingFragment.kt | 62 ++++++++ .../test/menu/ui/TestMenuFragmentViewModel.kt | 4 +- .../ui/main/MainActivityTestModule.kt | 5 + .../layout/fragment_test_deltaonboarding.xml | 150 ++++++++++++++++++ .../res/navigation/test_nav_graph.xml | 9 ++ .../ContactDiaryOnboardingFragment.kt | 23 ++- .../ui/launcher/LauncherActivity.kt | 6 +- .../ui/launcher/LauncherActivityViewModel.kt | 13 +- .../rki/coronawarnapp/ui/main/MainActivity.kt | 56 ++++++- .../ui/main/MainActivityActions.kt | 5 + .../ui/main/MainActivityViewModel.kt | 7 +- .../ui/main/home/HomeFragment.kt | 13 +- .../ui/main/home/HomeFragmentEvents.kt | 3 - .../ui/main/home/HomeFragmentViewModel.kt | 46 +++--- .../ui/onboarding/OnboardingActivity.kt | 15 +- .../ui/onboarding/OnboardingActivityModule.kt | 19 +++ ...OnboardingDeltaInteroperabilityFragment.kt | 4 +- .../onboarding/OnboardingLoadingFragment.kt | 53 +++++++ .../ui/onboarding/OnboardingLoadingModule.kt | 22 +++ .../onboarding/OnboardingLoadingViewModel.kt | 46 ++++++ .../SubmissionTestResultAvailableFragment.kt | 3 + .../de/rki/coronawarnapp/util/AppShortcuts.kt | 5 + .../util/shortcuts/AppShortcutsHelper.kt | 54 +++++++ .../ic_contact_diary_shortcut_icon.xml | 12 ++ .../res/layout/onboaring_loading_layout.xml | 15 ++ .../navigation/contact_diary_nav_graph.xml | 4 + .../src/main/res/navigation/nav_graph.xml | 6 - .../res/navigation/nav_graph_onboarding.xml | 61 ++++++- .../src/main/res/values-de/strings.xml | 4 + .../src/main/res/values/strings.xml | 3 + .../main/home/HomeFragmentViewModelTest.kt | 15 +- .../launcher/LauncherActivityViewModelTest.kt | 12 +- 37 files changed, 768 insertions(+), 79 deletions(-) create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentModule.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt create mode 100644 Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityActions.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingModule.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/AppShortcuts.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_contact_diary_shortcut_icon.xml create mode 100644 Corona-Warn-App/src/main/res/layout/onboaring_loading_layout.xml 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 index d457ee7eb..6f2fb1680 100644 --- 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 @@ -48,6 +48,7 @@ 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.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import io.mockk.MockKAnnotations @@ -89,6 +90,7 @@ class MainActivityTest : BaseUITest() { @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var statisticsProvider: StatisticsProvider @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler + @MockK lateinit var appShortcutsHelper: AppShortcutsHelper // MainActivity mocks @MockK lateinit var environmentSetup: EnvironmentSetup @@ -352,7 +354,8 @@ class MainActivityTest : BaseUITest() { submissionStateProvider = submissionStateProvider, cwaSettings = cwaSettings, statisticsProvider = statisticsProvider, - deadmanNotificationScheduler = deadmanNotificationScheduler + deadmanNotificationScheduler = deadmanNotificationScheduler, + appShortcutsHelper = appShortcutsHelper ) ) @@ -396,7 +399,8 @@ class MainActivityTest : BaseUITest() { every { showLoweredRiskLevelDialog } returns MutableLiveData() every { homeItems } returns MutableLiveData(emptyList()) every { popupEvents } returns SingleLiveEvent() - every { showPopUpsOrNavigate() } just Runs + every { showPopUps() } just Runs + every { restoreAppShortcuts() } just Runs } setupMockViewModel( 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 96c7b1e30..56559565e 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 @@ -16,6 +16,7 @@ import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.states.TracingStateProvider import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.SingleLiveEvent import io.mockk.MockKAnnotations import io.mockk.Runs @@ -47,6 +48,7 @@ class HomeFragmentTest : BaseUITest() { @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var statisticsProvider: StatisticsProvider @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler + @MockK lateinit var appShortcutsHelper: AppShortcutsHelper private lateinit var viewModel: HomeFragmentViewModel @@ -61,7 +63,8 @@ class HomeFragmentTest : BaseUITest() { every { showLoweredRiskLevelDialog } returns MutableLiveData() every { homeItems } returns MutableLiveData(emptyList()) every { popupEvents } returns SingleLiveEvent() - every { showPopUpsOrNavigate() } just Runs + every { showPopUps() } just Runs + every { restoreAppShortcuts() } just Runs } setupMockViewModel( @@ -96,7 +99,8 @@ class HomeFragmentTest : BaseUITest() { submissionStateProvider = submissionStateProvider, cwaSettings = cwaSettings, statisticsProvider = statisticsProvider, - deadmanNotificationScheduler = deadmanNotificationScheduler + deadmanNotificationScheduler = deadmanNotificationScheduler, + appShortcutsHelper = appShortcutsHelper ) ) } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt index a76ab1305..c099702b2 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt @@ -9,9 +9,12 @@ import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater_Factory_Impl import de.rki.coronawarnapp.ui.submission.resultavailable.SubmissionTestResultAvailableFragment import de.rki.coronawarnapp.ui.submission.resultavailable.SubmissionTestResultAvailableViewModel +import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper 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 io.mockk.unmockkAll import kotlinx.coroutines.flow.flowOf @@ -34,6 +37,7 @@ class SubmissionTestResultAvailableFragmentTest : BaseUITest() { @MockK lateinit var submissionRepository: SubmissionRepository @MockK lateinit var tekHistoryUpdaterFactory: TEKHistoryUpdater_Factory_Impl @MockK lateinit var autoSubmission: AutoSubmission + @MockK lateinit var appShortcutsHelper: AppShortcutsHelper @Rule @JvmField @@ -48,6 +52,7 @@ class SubmissionTestResultAvailableFragmentTest : BaseUITest() { every { submissionRepository.deviceUIStateFlow } returns flowOf() every { submissionRepository.testResultReceivedDateFlow } returns flowOf() + every { appShortcutsHelper.removeAppShortcut() } just Runs viewModel = spyk( SubmissionTestResultAvailableViewModel( diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentModule.kt new file mode 100644 index 000000000..16dacec6e --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentModule.kt @@ -0,0 +1,18 @@ +package de.rki.coronawarnapp.test.deltaonboarding.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 DeltaOnboardingFragmentModule { + @Binds + @IntoMap + @CWAViewModelKey(DeltaOnboardingFragmentViewModel::class) + abstract fun testTaskControllerFragment( + factory: DeltaOnboardingFragmentViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt new file mode 100644 index 000000000..6ca53f714 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaOnboardingFragmentViewModel.kt @@ -0,0 +1,53 @@ +package de.rki.coronawarnapp.test.deltaonboarding.ui + +import androidx.lifecycle.LiveData +import androidx.lifecycle.asLiveData +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory + +class DeltaOnboardingFragmentViewModel @AssistedInject constructor( + private val settings: CWASettings, + private val contactDiarySettings: ContactDiarySettings, + dispatcherProvider: DispatcherProvider +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + val changelogVersion: LiveData<Long> = + settings.lastChangelogVersion.flow.asLiveData(context = dispatcherProvider.Default) + + fun updateChangelogVersion(value: Long) { + settings.lastChangelogVersion.update { value } + } + + fun resetChangelogVersion() { + settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE } + } + + fun clearChangelogVersion() { + settings.lastChangelogVersion.update { 1 } + } + + fun isContactJournalOnboardingDone() = contactDiarySettings.isOnboardingDone + + fun setContactJournalOnboardingDone(value: Boolean) { + contactDiarySettings.onboardingStatus = if (value) + ContactDiarySettings.OnboardingStatus.RISK_STATUS_1_12 + else + ContactDiarySettings.OnboardingStatus.NOT_ONBOARDED + } + + fun isDeltaOnboardingDone() = LocalData.isInteroperabilityShownAtLeastOnce + + fun setDeltaOboardinDone(value: Boolean) { + LocalData.isInteroperabilityShownAtLeastOnce = value + } + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<DeltaOnboardingFragmentViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt new file mode 100644 index 000000000..1179e8e07 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/deltaonboarding/ui/DeltaonboardingFragment.kt @@ -0,0 +1,62 @@ +package de.rki.coronawarnapp.test.deltaonboarding.ui + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentTestDeltaonboardingBinding +import de.rki.coronawarnapp.test.menu.ui.TestMenuItem +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.viewBindingLazy +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import javax.inject.Inject + +@SuppressLint("SetTextI18n") +class DeltaonboardingFragment : Fragment(R.layout.fragment_test_deltaonboarding), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel: DeltaOnboardingFragmentViewModel by cwaViewModels { viewModelFactory } + + private val binding: FragmentTestDeltaonboardingBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.switchContactJournalOnboarding.isChecked = viewModel.isContactJournalOnboardingDone() + binding.switchDeltaOnboarding.isChecked = viewModel.isDeltaOnboardingDone() + viewModel.changelogVersion.observe(viewLifecycleOwner) { + binding.lastChangelogEdittext.setText(it.toString()) + } + + binding.buttonSet.setOnClickListener { + val value = binding.lastChangelogEdittext.text.toString().toLong() + viewModel.updateChangelogVersion(value) + } + + binding.buttonClear.setOnClickListener { + viewModel.clearChangelogVersion() + } + + binding.buttonReset.setOnClickListener { + viewModel.resetChangelogVersion() + } + + binding.switchContactJournalOnboarding.setOnCheckedChangeListener { _, value -> + viewModel.setContactJournalOnboardingDone(value) + } + + binding.switchDeltaOnboarding.setOnCheckedChangeListener { _, value -> + viewModel.setDeltaOboardinDone(value) + } + } + + companion object { + val MENU_ITEM = TestMenuItem( + title = "Onboarding options", + description = "Set delta onboarding or new release screen", + targetId = R.id.test_deltaonboarding_fragment + ) + } +} 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 45d99a414..6d09d4e6b 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 @@ -9,6 +9,7 @@ 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.deltaonboarding.ui.DeltaonboardingFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.playground.ui.PlaygroundFragment import de.rki.coronawarnapp.test.risklevel.ui.TestRiskLevelCalculationFragment @@ -32,7 +33,8 @@ class TestMenuFragmentViewModel @AssistedInject constructor() : CWAViewModel() { MiscInfoFragment.MENU_ITEM, ContactDiaryTestFragment.MENU_ITEM, PlaygroundFragment.MENU_ITEM, - DataDonationTestFragment.MENU_ITEM + DataDonationTestFragment.MENU_ITEM, + DeltaonboardingFragment.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 c0511503a..da9c43ddb 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 @@ -12,6 +12,8 @@ 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.deltaonboarding.ui.DeltaOnboardingFragmentModule +import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment @@ -60,4 +62,7 @@ abstract class MainActivityTestModule { @ContributesAndroidInjector(modules = [DataDonationTestFragmentModule::class]) abstract fun dataDonation(): DataDonationTestFragment + + @ContributesAndroidInjector(modules = [DeltaOnboardingFragmentModule::class]) + abstract fun deltaOnboarding(): DeltaonboardingFragment } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml new file mode 100644 index 000000000..374995566 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_deltaonboarding.xml @@ -0,0 +1,150 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.core.widget.NestedScrollView 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" + android:fillViewport="true" + tools:ignore="HardcodedText"> + + <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/delta_onboarding_title" + style="@style/headline6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="New release info" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/explanation_label" + style="@style/body2" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="By changing the last changelog version you may invoke the 'New release' screen in the home screen." + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/delta_onboarding_title" /> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/last_changelog_layout" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:hint="Last chnagelog version" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/explanation_label"> + + <EditText + android:id="@+id/last_changelog_edittext" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="number" /> + + </com.google.android.material.textfield.TextInputLayout> + + <Button + android:id="@+id/button_set" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Update" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/last_changelog_layout" /> + + <Button + android:id="@+id/button_clear" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Clear" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_set" /> + + <Button + android:id="@+id/button_reset" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:text="Reset" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/button_clear" /> + + + </androidx.constraintlayout.widget.ConstraintLayout> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/debug_container2" + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/contact_journal_onboarding_title" + style="@style/headline6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Contact Journal onboarding" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.switchmaterial.SwitchMaterial + android:id="@+id/switch_contact_journal_onboarding" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="Onboarding finished" + app:layout_constraintTop_toBottomOf="@id/contact_journal_onboarding_title" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/debug_container3" + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/cdelta_onboarding_title" + style="@style/headline6" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Delta onboarding" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.switchmaterial.SwitchMaterial + android:id="@+id/switch_delta_onboarding" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_small" + android:text="Onboarding finished" + app:layout_constraintTop_toBottomOf="@id/cdelta_onboarding_title" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </LinearLayout> +</androidx.core.widget.NestedScrollView> 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 efdf34765..3fd5c94a3 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 @@ -43,6 +43,9 @@ <action android:id="@+id/action_test_menu_fragment_to_dataDonationFragment" app:destination="@id/test_datadonation_fragment" /> + <action + android:id="@+id/action_test_menu_fragment_to_deltaonboardingFragment" + app:destination="@id/test_deltaonboarding_fragment" /> </fragment> <fragment @@ -116,4 +119,10 @@ tools:layout="@layout/fragment_test_datadonation" android:name="de.rki.coronawarnapp.test.datadonation.ui.DataDonationTestFragment" android:label="DataDonationFragment" /> + <fragment + android:id="@+id/test_deltaonboarding_fragment" + android:name="de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment" + android:label="DeltaonboardingFragment" + tools:layout="@layout/fragment_test_deltaonboarding" /> + </navigation> 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 e01721dc3..3155f696f 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 @@ -3,7 +3,9 @@ package de.rki.coronawarnapp.contactdiary.ui.onboarding import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent +import androidx.core.net.toUri import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings @@ -15,6 +17,7 @@ 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.joda.time.LocalDate import javax.inject.Inject class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboarding_fragment), AutoInject { @@ -31,7 +34,6 @@ class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboardin super.onViewCreated(view, savedInstanceState) binding.apply { contactDiaryOnboardingNextButton.setOnClickListener { - vm.onNextButtonClick() } if (!args.showBottomNav) { @@ -63,10 +65,17 @@ class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboardin ContactDiaryOnboardingNavigationEvents.NavigateToOverviewFragment -> { onboardingComplete() - doNavigate( - ContactDiaryOnboardingFragmentDirections - .actionContactDiaryOnboardingFragmentToContactDiaryOverviewFragment() - ) + if (arguments?.containsKey(OPEN_CURRENT_DAY) == true) { + findNavController().apply { + popBackStack(R.id.contactDiaryOnboardingFragment, true) + navigate("coronawarnapp://contact-journal/day/${LocalDate()}".toUri()) + } + } else { + doNavigate( + ContactDiaryOnboardingFragmentDirections + .actionContactDiaryOnboardingFragmentToContactDiaryOverviewFragment() + ) + } } } } @@ -80,4 +89,8 @@ class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboardin super.onResume() binding.contentContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) } + + companion object { + private const val OPEN_CURRENT_DAY = "goToDay" + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt index 8740ce471..90c3fe4bc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivity.kt @@ -9,6 +9,7 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.ui.onboarding.OnboardingActivity import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import javax.inject.Inject @@ -16,6 +17,7 @@ import javax.inject.Inject class LauncherActivity : AppCompatActivity() { @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: LauncherActivityViewModel by cwaViewModels( ownerProducer = { viewModelStore }, factoryProducer = { viewModelFactory } @@ -28,12 +30,12 @@ class LauncherActivity : AppCompatActivity() { vm.events.observe(this) { when (it) { LauncherEvent.GoToOnboarding -> { - OnboardingActivity.start(this) + OnboardingActivity.start(this, AppShortcutsHelper.getShortcutType(intent)) this.overridePendingTransition(0, 0) finish() } LauncherEvent.GoToMainActivity -> { - MainActivity.start(this) + MainActivity.start(this, AppShortcutsHelper.getShortcutType(intent)) this.overridePendingTransition(0, 0) finish() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt index d42ce56f0..acd7d63e6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModel.kt @@ -2,6 +2,8 @@ package de.rki.coronawarnapp.ui.launcher import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.update.UpdateChecker import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -11,7 +13,8 @@ import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory class LauncherActivityViewModel @AssistedInject constructor( private val updateChecker: UpdateChecker, - dispatcherProvider: DispatcherProvider + dispatcherProvider: DispatcherProvider, + private val cwaSettings: CWASettings, ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val events = SingleLiveEvent<LauncherEvent>() @@ -21,12 +24,16 @@ class LauncherActivityViewModel @AssistedInject constructor( val updateResult = updateChecker.checkForUpdate() when { updateResult.isUpdateNeeded -> LauncherEvent.ShowUpdateDialog(updateResult.updateIntent?.invoke()!!) - LocalData.isOnboarded() -> LauncherEvent.GoToMainActivity - else -> LauncherEvent.GoToOnboarding + isJustInstalledOrUpdated() -> LauncherEvent.GoToOnboarding + else -> LauncherEvent.GoToMainActivity }.let { events.postValue(it) } } } + private fun isJustInstalledOrUpdated() = + !LocalData.isOnboarded() || !LocalData.isInteroperabilityShownAtLeastOnce || + cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE + @AssistedFactory interface Factory : SimpleCWAViewModelFactory<LauncherActivityViewModel> } 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 a0c2547e9..f6984c21e 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 @@ -7,21 +7,25 @@ import android.os.Bundle import android.provider.Settings import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.navigation.NavController import androidx.navigation.NavGraph +import com.google.android.material.bottomnavigation.BottomNavigationView 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.contactdiary.ui.overview.ContactDiaryOverviewFragmentDirections import de.rki.coronawarnapp.databinding.ActivityMainBinding import de.rki.coronawarnapp.datadonation.analytics.worker.DataDonationAnalyticsScheduler import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.base.startActivitySafely import de.rki.coronawarnapp.ui.setupWithNavController2 +import de.rki.coronawarnapp.util.AppShortcuts import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.ConnectivityHelper import de.rki.coronawarnapp.util.DialogHelper @@ -31,6 +35,7 @@ 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 +import org.joda.time.LocalDate import javax.inject.Inject /** @@ -42,8 +47,24 @@ import javax.inject.Inject */ class MainActivity : AppCompatActivity(), HasAndroidInjector { companion object { - fun start(context: Context) { - context.startActivity(Intent(context, MainActivity::class.java)) + private const val EXTRA_DATA = "shortcut" + + fun start(context: Context, shortcut: AppShortcuts? = null) { + val intent = Intent(context, MainActivity::class.java).apply { + if (shortcut != null) { + putExtra(EXTRA_DATA, shortcut.toString()) + flags = flags or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK + } + } + context.startActivity(intent) + } + + private fun getShortcutFromIntent(intent: Intent): AppShortcuts? { + val extra = intent.getStringExtra(EXTRA_DATA) + if (extra != null) { + return AppShortcuts.valueOf(extra) + } + return null } } @@ -91,6 +112,37 @@ class MainActivity : AppCompatActivity(), HasAndroidInjector { vm.isOnboardingDone.observe(this) { isOnboardingDone -> startNestedGraphDestination(navController, isOnboardingDone) } + + if (savedInstanceState == null) { + processExtraParameters() + } + } + + private fun processExtraParameters() { + when (getShortcutFromIntent(intent)) { + AppShortcuts.CONTACT_DIARY -> { + goToContactJournal() + } + } + } + + private fun goToContactJournal() { + val navController = supportFragmentManager.findNavController(R.id.nav_host_fragment) + findViewById<BottomNavigationView>(R.id.main_bottom_navigation).selectedItemId = + R.id.contact_diary_nav_graph + val nestedGraph = navController.graph.findNode(R.id.contact_diary_nav_graph) as NavGraph + + if (vm.isOnboardingDone.value == true) { + nestedGraph.startDestination = R.id.contactDiaryOverviewFragment + navController.navigate( + ContactDiaryOverviewFragmentDirections.actionContactDiaryOverviewFragmentToContactDiaryDayFragment( + selectedDay = LocalDate().toString() + ) + ) + } else { + nestedGraph.startDestination = R.id.contactDiaryOnboardingFragment + navController.navigate("coronawarnapp://contact-journal/oboarding/?goToDay=true".toUri()) + } } private fun startNestedGraphDestination(navController: NavController, isOnboardingDone: Boolean) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityActions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityActions.kt new file mode 100644 index 000000000..f836e972d --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/MainActivityActions.kt @@ -0,0 +1,5 @@ +package de.rki.coronawarnapp.ui.main + +enum class MainActivityActions { + ADD_DIARY_ENTRY +} 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 f4c94877b..7db7bcbe8 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 @@ -1,5 +1,7 @@ package de.rki.coronawarnapp.ui.main +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.ui.ContactDiarySettings @@ -27,7 +29,8 @@ class MainActivityViewModel @AssistedInject constructor( val showBackgroundJobDisabledNotification = SingleLiveEvent<Unit>() val showEnergyOptimizedEnabledForBackground = SingleLiveEvent<Unit>() - val isOnboardingDone = SingleLiveEvent<Boolean>() + private val mutableIsOnboardingDone = MutableLiveData<Boolean>() + val isOnboardingDone: LiveData<Boolean> = mutableIsOnboardingDone init { if (CWADebug.isDeviceForTestersBuild) { @@ -64,7 +67,7 @@ class MainActivityViewModel @AssistedInject constructor( } fun onBottomNavSelected() { - isOnboardingDone.value = contactDiarySettings.isOnboardingDone + mutableIsOnboardingDone.value = contactDiarySettings.isOnboardingDone } private suspend fun checkForEnergyOptimizedEnabled() { 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 0689d0144..cf4fa1501 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 @@ -80,11 +80,6 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { vm.popupEvents.observe2(this) { event -> when (event) { - HomeFragmentEvents.ShowInteropDeltaOnboarding -> { - doNavigate( - HomeFragmentDirections.actionMainFragmentToOnboardingDeltaInteroperabilityFragment() - ) - } is HomeFragmentEvents.ShowTracingExplanation -> { tracingExplanationDialog.show(event.activeTracingDaysInRetentionPeriod) { vm.tracingExplanationWasShown() @@ -97,11 +92,6 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { ) } HomeFragmentEvents.ShowDeleteTestDialog -> showRemoveTestDialog() - - HomeFragmentEvents.ShowNewReleaseFragment -> doNavigate( - HomeFragmentDirections.actionMainFragmentToNewReleaseInfoFragment(false) - ) - HomeFragmentEvents.GoToStatisticsExplanation -> doNavigate( HomeFragmentDirections.actionMainFragmentToStatisticsExplanationFragment() ) @@ -111,7 +101,7 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { } } - vm.showPopUpsOrNavigate() + vm.showPopUps() vm.showLoweredRiskLevelDialog.observe2(this) { if (it) showRiskLevelLoweredDialog() @@ -127,6 +117,7 @@ class HomeFragment : Fragment(R.layout.home_fragment_layout), AutoInject { override fun onResume() { super.onResume() vm.refreshRequiredData() + vm.restoreAppShortcuts() binding.container.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) } 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 422dd904a..595e0aa4f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentEvents.kt @@ -1,7 +1,6 @@ package de.rki.coronawarnapp.ui.main.home sealed class HomeFragmentEvents { - object ShowInteropDeltaOnboarding : HomeFragmentEvents() data class ShowTracingExplanation( val activeTracingDaysInRetentionPeriod: Long @@ -13,7 +12,5 @@ sealed class HomeFragmentEvents { object ShowReactivateRiskCheckDialog : 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 bb3950f15..86309e8ec 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 @@ -7,7 +7,6 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler -import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.notification.ShareTestResultNotificationService import de.rki.coronawarnapp.risk.TimeVariables @@ -50,7 +49,6 @@ import de.rki.coronawarnapp.tracing.ui.homecards.TracingProgressCard import de.rki.coronawarnapp.tracing.ui.statusbar.TracingHeaderState 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.FAQCard import de.rki.coronawarnapp.ui.main.home.items.HomeItem @@ -59,6 +57,7 @@ import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory @@ -81,7 +80,8 @@ class HomeFragmentViewModel @AssistedInject constructor( private val cwaSettings: CWASettings, appConfigProvider: AppConfigProvider, statisticsProvider: StatisticsProvider, - private val deadmanNotificationScheduler: DeadmanNotificationScheduler + private val deadmanNotificationScheduler: DeadmanNotificationScheduler, + private val appShortcutsHelper: AppShortcutsHelper ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { private val tracingStateProvider by lazy { tracingStateProviderFactory.create(isDetailsMode = false) } @@ -95,29 +95,19 @@ class HomeFragmentViewModel @AssistedInject constructor( val popupEvents = SingleLiveEvent<HomeFragmentEvents>() - fun showPopUpsOrNavigate() { - when { - !LocalData.isInteroperabilityShownAtLeastOnce -> { - popupEvents.postValue(ShowInteropDeltaOnboarding) - } - cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE -> { - popupEvents.postValue(HomeFragmentEvents.ShowNewReleaseFragment) + fun showPopUps() { + launch { + if (!LocalData.tracingExplanationDialogWasShown()) { + popupEvents.postValue( + ShowTracingExplanation( + TimeVariables.getActiveTracingDaysInRetentionPeriod() + ) + ) } - else -> { - launch { - if (!LocalData.tracingExplanationDialogWasShown()) { - popupEvents.postValue( - ShowTracingExplanation( - TimeVariables.getActiveTracingDaysInRetentionPeriod() - ) - ) - } - } - launch { - if (errorResetTool.isResetNoticeToBeShown) { - popupEvents.postValue(ShowErrorResetDialog) - } - } + } + launch { + if (errorResetTool.isResetNoticeToBeShown) { + popupEvents.postValue(ShowErrorResetDialog) } } } @@ -307,6 +297,12 @@ class HomeFragmentViewModel @AssistedInject constructor( } } + fun restoreAppShortcuts() { + launch { + appShortcutsHelper.restoreAppShortcut() + } + } + fun tracingExplanationWasShown() { LocalData.tracingExplanationDialogWasShown(true) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt index a6b5218db..fe8a9252f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt @@ -15,6 +15,7 @@ import de.rki.coronawarnapp.environment.BuildConfigWrap import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.ui.main.MainActivity +import de.rki.coronawarnapp.util.AppShortcuts import de.rki.coronawarnapp.util.di.AppInjector import javax.inject.Inject @@ -26,11 +27,21 @@ import javax.inject.Inject class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInjector { companion object { private val TAG: String? = OnboardingActivity::class.simpleName + private const val EXTRA_DATA = "shortcut" - fun start(context: Context) { - val intent = Intent(context, OnboardingActivity::class.java) + fun start(context: Context, shortcut: AppShortcuts? = null) { + val intent = Intent(context, OnboardingActivity::class.java).apply { + putExtra(EXTRA_DATA, shortcut?.toString()) + } context.startActivity(intent) } + + fun getShortcutFromIntent(intent: Intent?): AppShortcuts? { + intent?.getStringExtra(EXTRA_DATA)?.let { + return AppShortcuts.valueOf(it) + } + return null + } } @Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Any> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivityModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivityModule.kt index 34bc9a8a6..3c52d5e7c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivityModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivityModule.kt @@ -4,6 +4,8 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.datadonation.analytics.ui.AnalyticsUIModule import de.rki.coronawarnapp.datadonation.analytics.ui.input.AnalyticsUserInputFragment +import de.rki.coronawarnapp.release.NewReleaseInfoFragment +import de.rki.coronawarnapp.release.NewReleaseInfoFragmentModule @Module internal abstract class OnboardingActivityModule { @@ -16,14 +18,31 @@ internal abstract class OnboardingActivityModule { @ContributesAndroidInjector(modules = [OnboardingTracingModule::class]) abstract fun onboardingScreen(): OnboardingTracingFragment + @ContributesAndroidInjector(modules = [OnboardingPrivacyModule::class]) abstract fun onboardingPrivacyFragment(): OnboardingPrivacyFragment + @ContributesAndroidInjector(modules = [OnboardingTestModule::class]) abstract fun onboardingTestFragment(): OnboardingTestFragment + @ContributesAndroidInjector(modules = [OnboardingNotificationsModule::class]) abstract fun onboardingNotificationsFragment(): OnboardingNotificationsFragment + @ContributesAndroidInjector(modules = [OnboardingAnalyticsModule::class]) abstract fun onboardingAnalyticsFragment(): OnboardingAnalyticsFragment + @ContributesAndroidInjector(modules = [AnalyticsUIModule::class]) abstract fun ppaUserInfoSelection(): AnalyticsUserInputFragment + + @ContributesAndroidInjector(modules = [OnboardingLoadingModule::class]) + abstract fun onboardingLoadingScreen(): OnboardingLoadingFragment + + @ContributesAndroidInjector(modules = [NewReleaseInfoFragmentModule::class]) + abstract fun newReleaseInfoFragment(): NewReleaseInfoFragment + + @ContributesAndroidInjector(modules = [OnboardingDeltaInteroperabilityModule::class]) + abstract fun onboardingDeltaInteroperabilityFragment(): OnboardingDeltaInteroperabilityFragment + + @ContributesAndroidInjector(modules = [OnboardingDeltaAnalyticsModule::class]) + abstract fun onboardingDeltaAnalyticsFragment(): OnboardingDeltaAnalyticsFragment } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragment.kt index 595e10ca9..527103651 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingDeltaInteroperabilityFragment.kt @@ -8,10 +8,10 @@ import androidx.navigation.fragment.findNavController import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentOnboardingDeltaInteroperabilityBinding import de.rki.coronawarnapp.ui.doNavigate -import de.rki.coronawarnapp.ui.main.MainActivity import de.rki.coronawarnapp.util.convertToHyperlink 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 @@ -53,7 +53,7 @@ class OnboardingDeltaInteroperabilityFragment : vm.navigateBack.observe2(this) { if (it) { - (requireActivity() as MainActivity).goBack() + popBackStack() } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt new file mode 100644 index 000000000..7d7c6475f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingFragment.kt @@ -0,0 +1,53 @@ +package de.rki.coronawarnapp.ui.onboarding + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.ui.main.MainActivity +import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.ui.doNavigate +import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider +import de.rki.coronawarnapp.util.viewmodel.cwaViewModels +import javax.inject.Inject + +class OnboardingLoadingFragment : Fragment(R.layout.onboaring_loading_layout), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel: OnboardingLoadingViewModel by cwaViewModels( + ownerProducer = { requireActivity().viewModelStore }, + factoryProducer = { viewModelFactory } + ) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewModel.navigationEvents.observe2(this) { event -> + when (event) { + OnboardingFragmentEvents.ShowInteropDeltaOnboarding -> + doNavigate( + OnboardingLoadingFragmentDirections + .actionLoadingFragmentToOnboardingDeltaInteroperabilityFragment() + ) + OnboardingFragmentEvents.ShowNewReleaseFragment -> + doNavigate( + OnboardingLoadingFragmentDirections + .actionLoadingFragmentToNewReleaseInfoFragment() + ) + OnboardingFragmentEvents.ShowOnboarding -> + doNavigate( + OnboardingLoadingFragmentDirections + .actionLoadingFragmentToOnboardingFragment() + ) + OnboardingFragmentEvents.OnboardingDone -> { + MainActivity.start(requireContext(), OnboardingActivity.getShortcutFromIntent(activity?.intent)) + activity?.overridePendingTransition(0, 0) + activity?.finish() + } + } + } + + viewModel.navigate() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingModule.kt new file mode 100644 index 000000000..40bb81564 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingModule.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.ui.onboarding + +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 OnboardingLoadingModule { + @Binds + @IntoMap + @CWAViewModelKey(OnboardingLoadingViewModel::class) + abstract fun onboardingLoadingVM( + factory: OnboardingLoadingViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> + + @ContributesAndroidInjector + abstract fun onboardingLoadingFragment(): OnboardingLoadingFragment +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt new file mode 100644 index 000000000..57dbcfc19 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingLoadingViewModel.kt @@ -0,0 +1,46 @@ +package de.rki.coronawarnapp.ui.onboarding + +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.main.CWASettings +import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.util.ui.SingleLiveEvent +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory + +class OnboardingLoadingViewModel @AssistedInject constructor(private val cwaSettings: CWASettings) : CWAViewModel() { + + val navigationEvents = SingleLiveEvent<OnboardingFragmentEvents>() + + fun navigate() { + when { + !LocalData.isOnboarded() -> { + navigationEvents.postValue(OnboardingFragmentEvents.ShowOnboarding) + } + !LocalData.isInteroperabilityShownAtLeastOnce -> { + navigationEvents.postValue(OnboardingFragmentEvents.ShowInteropDeltaOnboarding) + } + cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE -> { + navigationEvents.postValue(OnboardingFragmentEvents.ShowNewReleaseFragment) + } + else -> { + navigationEvents.postValue(OnboardingFragmentEvents.OnboardingDone) + } + } + } + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<OnboardingLoadingViewModel> +} + +sealed class OnboardingFragmentEvents { + + object ShowInteropDeltaOnboarding : OnboardingFragmentEvents() + + object ShowNewReleaseFragment : OnboardingFragmentEvents() + + object ShowOnboarding : OnboardingFragmentEvents() + + object OnboardingDone : OnboardingFragmentEvents() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt index 6a265ce44..c498a7942 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/resultavailable/SubmissionTestResultAvailableFragment.kt @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.tracing.ui.TracingConsentDialog import de.rki.coronawarnapp.ui.submission.SubmissionBlockingDialog import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject +import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.ui.viewBindingLazy @@ -26,6 +27,7 @@ import javax.inject.Inject */ class SubmissionTestResultAvailableFragment : Fragment(R.layout.fragment_submission_test_result_available), AutoInject { + @Inject lateinit var appShortcutsHelper: AppShortcutsHelper @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory private val vm: SubmissionTestResultAvailableViewModel by cwaViewModels { viewModelFactory } private val binding: FragmentSubmissionTestResultAvailableBinding by viewBindingLazy() @@ -88,6 +90,7 @@ class SubmissionTestResultAvailableFragment : Fragment(R.layout.fragment_submiss override fun onResume() { super.onResume() binding.submissionTestResultAvailableContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) + appShortcutsHelper.removeAppShortcut() } private fun showCloseDialog() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/AppShortcuts.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/AppShortcuts.kt new file mode 100644 index 000000000..b4b340aab --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/AppShortcuts.kt @@ -0,0 +1,5 @@ +package de.rki.coronawarnapp.util + +enum class AppShortcuts { + CONTACT_DIARY +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt new file mode 100644 index 000000000..0aeb1345d --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/shortcuts/AppShortcutsHelper.kt @@ -0,0 +1,54 @@ +package de.rki.coronawarnapp.util.shortcuts + +import android.content.Context +import android.content.Intent +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.ui.launcher.LauncherActivity +import de.rki.coronawarnapp.util.AppShortcuts +import de.rki.coronawarnapp.util.di.AppContext +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppShortcutsHelper @Inject constructor(@AppContext private val context: Context) { + + suspend fun restoreAppShortcut() = withContext(Dispatchers.IO) { + if (ShortcutManagerCompat.getDynamicShortcuts(context).size == 0) { + val shortcut = ShortcutInfoCompat.Builder(context, CONTACT_DIARY_SHORTCUT_ID) + .setShortLabel(context.getString(R.string.app_shortcut_contact_diary_title)) + .setLongLabel(context.getString(R.string.app_shortcut_contact_diary_title)) + .setIcon(IconCompat.createWithResource(context, R.drawable.ic_contact_diary_shortcut_icon)) + .setIntent(createContactDiaryIntent()) + .build() + + ShortcutManagerCompat.addDynamicShortcuts(context, listOf(shortcut)) + } + } + + fun removeAppShortcut() { + ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(CONTACT_DIARY_SHORTCUT_ID)) + } + + private fun createContactDiaryIntent() = Intent(context, LauncherActivity::class.java).apply { + action = Intent.ACTION_VIEW + putExtra(SHORTCUT_EXTRA_ID, AppShortcuts.CONTACT_DIARY.toString()) + } + + companion object { + private const val CONTACT_DIARY_SHORTCUT_ID = "contact_diary_id" + private const val SHORTCUT_EXTRA_ID = "shortcut_extra" + + fun getShortcutType(intent: Intent): AppShortcuts? { + intent.getStringExtra(SHORTCUT_EXTRA_ID)?.let { + return AppShortcuts.valueOf(it) + } + + return null + } + } +} diff --git a/Corona-Warn-App/src/main/res/drawable/ic_contact_diary_shortcut_icon.xml b/Corona-Warn-App/src/main/res/drawable/ic_contact_diary_shortcut_icon.xml new file mode 100644 index 000000000..b032e9572 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_contact_diary_shortcut_icon.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="34dp" + android:height="34dp" + android:viewportWidth="34" + android:viewportHeight="34"> + <path + android:pathData="M17,17m-17,0a17,17 0,1 1,34 0a17,17 0,1 1,-34 0" + android:fillColor="#F5F5F5"/> + <path + android:pathData="M24.3816,24.6345V10.1056C24.3816,8.8422 23.476,8 22.2126,8H11.9787C10.9164,8 9.9932,9.0445 10,10.1056C10,14.7983 10,19.7317 10,24.4238C10,25.6872 10.8425,26.5294 12.1546,26.5294H23.8971C23.8971,26.5294 24.3182,26.5294 24.3182,26.1083C24.3182,25.6872 24.3182,25.266 24.3182,25.266C24.3182,25.266 24.3182,24.8496 23.8971,24.8449C23.476,24.8402 12.948,24.8449 12.948,24.8449C12.948,24.8449 11.6845,24.8449 11.6845,23.5816C11.6845,23.0036 11.6845,22.7393 11.6845,22.7393C11.6845,21.4759 12.948,21.4759 12.948,21.4759C11.8333,21.4759 21.1642,21.4759 23.4759,21.4759C23.4759,21.4759 23.0548,22.3182 23.0548,23.1604C23.0548,23.6429 23.4759,24.6345 23.4759,24.6345C23.8971,24.6345 24.3816,24.6345 24.3816,24.6345Z" + android:fillColor="#777677"/> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/onboaring_loading_layout.xml b/Corona-Warn-App/src/main/res/layout/onboaring_loading_layout.xml new file mode 100644 index 000000000..ab53df91a --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/onboaring_loading_layout.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <ProgressBar + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:layout_width="32dp" + android:layout_height="32dp" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ 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 a11f6a236..97572c5b0 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 @@ -19,6 +19,7 @@ <action android:id="@+id/action_contactDiaryDayFragment_to_contactDiaryLocationBottomSheetDialogFragment" app:destination="@id/contactDiaryLocationBottomSheetDialogFragment" /> + <deepLink app:uri="coronawarnapp://contact-journal/day/{selectedDay}" /> </fragment> <fragment android:id="@+id/contactDiaryPersonListFragment" @@ -87,6 +88,9 @@ android:name="showBottomNav" android:defaultValue="true" app:argType="boolean" /> + <deepLink app:uri="coronawarnapp://contact-journal/oboarding/?goToDay={goToDay}" + app:popUpTo="@id/contact_diary_nav_graph" + app:popUpToInclusive="true" /> </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 5bfc4298e..b4fe423e3 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -49,18 +49,12 @@ <action android:id="@+id/action_mainFragment_to_submissionDispatcher" app:destination="@id/submissionDispatcherFragment" /> - <action - android:id="@+id/action_mainFragment_to_onboardingDeltaInteroperabilityFragment" - app:destination="@id/onboardingDeltaInteroperabilityFragment" /> <action android:id="@+id/action_mainFragment_to_test_nav_graph" app:destination="@id/test_nav_graph" /> <action android:id="@+id/action_mainFragment_to_submissionTestResultAvailableFragment" app:destination="@id/submissionTestResultAvailableFragment" /> - <action - android:id="@+id/action_mainFragment_to_newReleaseInfoFragment" - app:destination="@id/newReleaseInfoFragment" /> <action android:id="@+id/action_mainFragment_to_statisticsExplanationFragment" app:destination="@id/statisticsExplanationFragment" /> diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph_onboarding.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph_onboarding.xml index 0e2a1bc2b..3c128a61f 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph_onboarding.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph_onboarding.xml @@ -3,8 +3,23 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_graph_onboarding" - app:startDestination="@id/onboardingFragment"> + app:startDestination="@id/loadingFragment"> + <fragment + android:id="@+id/loadingFragment" + android:name="de.rki.coronawarnapp.ui.onboarding.OnboardingLoadingFragment" + android:label="LoadingFragment" + tools:layout="@layout/onboaring_loading_layout"> + <action + android:id="@+id/action_loadingFragment_to_newReleaseInfoFragment" + app:destination="@id/newReleaseInfoFragment" /> + <action + android:id="@+id/action_loadingFragment_to_onboardingDeltaInteroperabilityFragment" + app:destination="@id/onboardingDeltaInteroperabilityFragment2" /> + <action + android:id="@+id/action_loadingFragment_to_onboardingFragment" + app:destination="@id/onboardingFragment" /> + </fragment> <fragment android:id="@+id/onboardingFragment" android:name="de.rki.coronawarnapp.ui.onboarding.OnboardingFragment" @@ -76,4 +91,48 @@ android:name="de.rki.coronawarnapp.datadonation.analytics.ui.PpaMoreInfoFragment" android:label="PpaMoreInfoFragment" tools:layout="@layout/fragment_ppa_more_info" /> + + <!-- New Release --> + <fragment + android:id="@+id/newReleaseInfoFragment" + android:name="de.rki.coronawarnapp.release.NewReleaseInfoFragment" + tools:layout="@layout/new_release_info_screen_fragment" + android:label="NewReleaseInfoFragment"> + <argument + android:name="comesFromInfoScreen" + android:defaultValue="false" + app:argType="boolean" /> + <action + android:id="@+id/action_newReleaseInfoFragment_to_onboardingDeltaAnalyticsFragment" + app:destination="@id/onboardingDeltaAnalyticsFragment" + app:popUpTo="@id/loadingFragment" + app:popUpToInclusive="false" /> + </fragment> + <fragment + android:id="@+id/onboardingDeltaAnalyticsFragment" + android:name="de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaAnalyticsFragment" + android:label="OnboardingDeltaAnalyticsFragment" + tools:layout="@layout/fragment_onboarding_delta_ppa"> + <action + android:id="@+id/action_onboardingDeltaAnalyticsFragment_to_analyticsUserInputFragment" + app:destination="@id/analyticsUserInputFragment" /> + <action + android:id="@+id/action_onboardingDeltaAnalyticsFragment_to_ppaMoreInfoFragment" + app:destination="@id/ppaMoreInfoFragment" /> + </fragment> + <fragment + android:id="@+id/onboardingDeltaInteroperabilityFragment2" + android:name="de.rki.coronawarnapp.ui.onboarding.OnboardingDeltaInteroperabilityFragment" + android:label="OnboardingDeltaInteroperabilityFragment" + tools:layout="@layout/fragment_onboarding_delta_interoperability"> + <action + android:id="@+id/action_onboardingDeltaInteroperabilityFragment_to_informationTermsFragment" + app:destination="@id/informationTermsFragment" /> + </fragment> + + <fragment + android:id="@+id/informationTermsFragment" + android:name="de.rki.coronawarnapp.ui.information.InformationTermsFragment" + android:label="@layout/fragment_information_terms" + tools:layout="@layout/fragment_information_terms" /> </navigation> \ No newline at end of file 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 baa753516..21ad6bdfe 100644 --- a/Corona-Warn-App/src/main/res/values-de/strings.xml +++ b/Corona-Warn-App/src/main/res/values-de/strings.xml @@ -1755,6 +1755,9 @@ <!-- XBUT: Abort button for test result positive no consent screen --> <string name="submission_test_result_positive_no_consent_button_abort">Abbrechen</string> + <!-- XHED: Title of contact diary app shortcut --> + <string name="app_shortcut_contact_diary_title">"Eintrag hinzufügen"</string> + <!-- XTXT: Statistics information (Accessibilty) --> <string name="statistics_info_button">"Weitere Informationen"</string> <!-- XHED: Title for statistics reproduction card --> @@ -1878,4 +1881,5 @@ <string name="analytics_userinput_district_title">Ihr Kreis/Bezirk</string> <!-- XTXT: Analytics voluntary user input, district: UNSPECIFIED --> <string name="analytics_userinput_district_unspecified">keine Angabe</string> + </resources> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index de1bd2008..1971d65ab 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1770,6 +1770,9 @@ <!-- XBUT: Abort button for test result positive no consent screen --> <string name="submission_test_result_positive_no_consent_button_abort">"Cancel"</string> + <!-- XHED: Title of contact diary app shortcut --> + <string name="app_shortcut_contact_diary_title">"Eintrag hinzufügen"</string> + <!-- XTXT: Statistics information (Accessibilty) --> <string name="statistics_info_button">"Further information"</string> <!-- XHED: Title for statistics reproduction card --> 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 c7c651a71..0ffe699ce 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 @@ -24,6 +24,7 @@ import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE import de.rki.coronawarnapp.util.DeviceUIState.PAIRED_POSITIVE_TELETAN import de.rki.coronawarnapp.util.NetworkRequestWrapper import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import de.rki.coronawarnapp.util.shortcuts.AppShortcutsHelper import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.clearAllMocks @@ -65,6 +66,7 @@ class HomeFragmentViewModelTest : BaseTest() { @MockK lateinit var appConfigProvider: AppConfigProvider @MockK lateinit var statisticsProvider: StatisticsProvider @MockK lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler + @MockK lateinit var appShortcutsHelper: AppShortcutsHelper @BeforeEach fun setup() { @@ -100,7 +102,8 @@ class HomeFragmentViewModelTest : BaseTest() { cwaSettings = cwaSettings, appConfigProvider = appConfigProvider, statisticsProvider = statisticsProvider, - deadmanNotificationScheduler = deadmanNotificationScheduler + deadmanNotificationScheduler = deadmanNotificationScheduler, + appShortcutsHelper = appShortcutsHelper ) @Test @@ -196,16 +199,10 @@ class HomeFragmentViewModelTest : BaseTest() { every { errorResetTool.isResetNoticeToBeShown } returns false andThen true with(createInstance()) { - showPopUpsOrNavigate() - popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowInteropDeltaOnboarding - - showPopUpsOrNavigate() - popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowNewReleaseFragment - - showPopUpsOrNavigate() + showPopUps() popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowTracingExplanation(1) - showPopUpsOrNavigate() + showPopUps() popupEvents.getOrAwaitValue() shouldBe HomeFragmentEvents.ShowErrorResetDialog } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt index 63b120314..930986dbe 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/launcher/LauncherActivityViewModelTest.kt @@ -1,5 +1,7 @@ package de.rki.coronawarnapp.ui.launcher +import de.rki.coronawarnapp.environment.BuildConfigWrap +import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.update.UpdateChecker import io.kotest.matchers.shouldBe @@ -16,11 +18,13 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import testhelpers.TestDispatcherProvider import testhelpers.extensions.InstantExecutorExtension +import testhelpers.preferences.mockFlowPreference @ExtendWith(InstantExecutorExtension::class) class LauncherActivityViewModelTest { @MockK lateinit var updateChecker: UpdateChecker + @MockK lateinit var cwaSettings: CWASettings @BeforeEach fun setupFreshViewModel() { @@ -29,12 +33,16 @@ class LauncherActivityViewModelTest { mockkObject(LocalData) every { LocalData.isOnboarded() } returns false + mockkObject(BuildConfigWrap) + every { BuildConfigWrap.VERSION_CODE } returns 10L + coEvery { updateChecker.checkForUpdate() } returns UpdateChecker.Result(isUpdateNeeded = false) } private fun createViewModel() = LauncherActivityViewModel( updateChecker = updateChecker, - dispatcherProvider = TestDispatcherProvider() + dispatcherProvider = TestDispatcherProvider(), + cwaSettings = cwaSettings ) @Test @@ -59,6 +67,8 @@ class LauncherActivityViewModelTest { @Test fun `onboarding finished`() { every { LocalData.isOnboarded() } returns true + every { LocalData.isInteroperabilityShownAtLeastOnce } returns true + every { cwaSettings.lastChangelogVersion } returns mockFlowPreference(10L) val vm = createViewModel() -- GitLab