From 52152995cf0bd587a9442d2f2475bd632f94f666 Mon Sep 17 00:00:00 2001
From: AlexanderAlferov <64849422+AlexanderAlferov@users.noreply.github.com>
Date: Wed, 2 Dec 2020 14:52:34 +0300
Subject: [PATCH] New submission flow: Your consent screen (EXPOSUREAPP-3735)
 (#1767)

* All your strings are belong to us!

* Submission your consent screen

* Unit test fix

* Unit test fix

* Unit test fix

* Refactor view model, tests and events

* Fixed test

* Lint fix, added id to bottom divider
---
 .../viewmodel/SubmissionFragmentModule.kt     |   5 +
 .../SubmissionYourConsentEvents.kt            |   6 +
 .../SubmissionYourConsentFragment.kt          |  63 +++++++
 .../SubmissionYourConsentModule.kt            |  18 ++
 .../SubmissionYourConsentViewModel.kt         |  43 +++++
 .../fragment_submission_your_consent.xml      | 159 ++++++++++++++++++
 .../SubmissionYourConsentViewModelTest.kt     | 109 ++++++++++++
 7 files changed, 403 insertions(+)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentEvents.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentFragment.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentModule.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/res/layout/fragment_submission_your_consent.xml
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
index cc502e111..9fae7954f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
@@ -24,6 +24,8 @@ import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultFragmen
 import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultModule
 import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment
 import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningModule
+import de.rki.coronawarnapp.ui.submission.yourconsent.SubmissionYourConsentFragment
+import de.rki.coronawarnapp.ui.submission.yourconsent.SubmissionYourConsentModule
 
 @Module
 internal abstract class SubmissionFragmentModule {
@@ -67,6 +69,9 @@ internal abstract class SubmissionFragmentModule {
     @ContributesAndroidInjector(modules = [SubmissionConsentModule::class])
     abstract fun submissionConsentScreen(): SubmissionConsentFragment
 
+    @ContributesAndroidInjector(modules = [SubmissionYourConsentModule::class])
+    abstract fun submissionYourConsentScreen(): SubmissionYourConsentFragment
+
     @ContributesAndroidInjector(modules = [SubmissionTestResultAvailableModule::class])
     abstract fun submissionTestResultAvailableScreen(): SubmissionTestResultAvailableFragment
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentEvents.kt
new file mode 100644
index 000000000..44a9fb2f1
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentEvents.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.ui.submission.yourconsent
+
+sealed class SubmissionYourConsentEvents {
+    object GoBack : SubmissionYourConsentEvents()
+    object GoLegal : SubmissionYourConsentEvents()
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentFragment.kt
new file mode 100644
index 000000000..a93399ffe
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentFragment.kt
@@ -0,0 +1,63 @@
+package de.rki.coronawarnapp.ui.submission.yourconsent
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityEvent
+import androidx.fragment.app.Fragment
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.databinding.FragmentSubmissionYourConsentBinding
+import de.rki.coronawarnapp.ui.main.MainActivity
+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 javax.inject.Inject
+
+class SubmissionYourConsentFragment : Fragment(R.layout.fragment_submission_your_consent), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val vm: SubmissionYourConsentViewModel by cwaViewModels { viewModelFactory }
+    private val binding: FragmentSubmissionYourConsentBinding by viewBindingLazy()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+
+        vm.consent.observe2(this) {
+            binding.submissionYourConsentSwitch.status = it
+            binding.submissionYourConsentSwitch.settingsSwitchRowHeaderBody.setText(
+                if (it) {
+                    R.string.submission_your_consent_switch_status_on
+                } else {
+                    R.string.submission_your_consent_switch_status_off
+                }
+            )
+        }
+
+        vm.countryList.observe2(this) {
+            binding.submissionYourConsentAgreementCountryList.countries = it
+        }
+
+        vm.clickEvent.observe2(this) {
+            when (it) {
+                is SubmissionYourConsentEvents.GoBack -> (activity as MainActivity).goBack()
+                // TODO: Navigation: is YourConsentEvents.GoLegal -> doNavigate(YourConsentFragmentDirections.actionSubmissionYourConsentFragmentToInformationPrivacyFragment())
+            }
+        }
+
+        binding.apply {
+            submissionYourConsentTitle.headerButtonBack.buttonIcon.setOnClickListener { vm.goBack() }
+            submissionYourConsentSwitch.settingsSwitchRowSwitch.setOnCheckedChangeListener { view, _ ->
+                if (!view.isPressed) return@setOnCheckedChangeListener
+                vm.switchConsent()
+            }
+            submissionYourConsentSwitch.settingsSwitchRow.setOnClickListener { vm.switchConsent() }
+            // TODO: Navigation: submissionYourConsentLegalDetailsCard.setOnClickListener { vm.goLegal() }
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        binding.submissionYourConsentContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentModule.kt
new file mode 100644
index 000000000..048b8e408
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentModule.kt
@@ -0,0 +1,18 @@
+package de.rki.coronawarnapp.ui.submission.yourconsent
+
+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 SubmissionYourConsentModule {
+    @Binds
+    @IntoMap
+    @CWAViewModelKey(SubmissionYourConsentViewModel::class)
+    abstract fun yourConsentFragment(
+        factory: SubmissionYourConsentViewModel.Factory
+    ): CWAViewModelFactory<out CWAViewModel>
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt
new file mode 100644
index 000000000..1d8817763
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModel.kt
@@ -0,0 +1,43 @@
+package de.rki.coronawarnapp.ui.submission.yourconsent
+
+import androidx.lifecycle.asLiveData
+import com.squareup.inject.assisted.AssistedInject
+import de.rki.coronawarnapp.storage.SubmissionRepository
+import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
+import de.rki.coronawarnapp.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
+import kotlinx.coroutines.flow.first
+
+class SubmissionYourConsentViewModel @AssistedInject constructor(
+    val dispatcherProvider: DispatcherProvider,
+    interoperabilityRepository: InteroperabilityRepository,
+    val submissionRepository: SubmissionRepository
+) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
+
+    val clickEvent: SingleLiveEvent<SubmissionYourConsentEvents> = SingleLiveEvent()
+    val consent = submissionRepository.hasGivenConsentToSubmission.asLiveData()
+    val countryList = interoperabilityRepository.countryListFlow.asLiveData()
+
+    fun goBack() {
+        clickEvent.postValue(SubmissionYourConsentEvents.GoBack)
+    }
+
+    fun switchConsent() {
+        launch {
+            if (submissionRepository.hasGivenConsentToSubmission.first()) {
+                submissionRepository.revokeConsentToSubmission()
+            } else {
+                submissionRepository.giveConsentToSubmission()
+            }
+        }
+    }
+
+    fun goLegal() {
+        clickEvent.postValue(SubmissionYourConsentEvents.GoLegal)
+    }
+
+    @AssistedInject.Factory
+    interface Factory : SimpleCWAViewModelFactory<SubmissionYourConsentViewModel>
+}
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_submission_your_consent.xml b/Corona-Warn-App/src/main/res/layout/fragment_submission_your_consent.xml
new file mode 100644
index 000000000..25b2b9371
--- /dev/null
+++ b/Corona-Warn-App/src/main/res/layout/fragment_submission_your_consent.xml
@@ -0,0 +1,159 @@
+<?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/submission_your_consent_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:contentDescription="@string/submission_your_consent_title"
+        android:fillViewport="true"
+        tools:context=".ui.submission.yourconsent.SubmissionYourConsentFragment">
+
+        <include
+            android:id="@+id/submission_your_consent_title"
+            layout="@layout/include_header"
+            android:layout_width="@dimen/match_constraint"
+            android:layout_height="wrap_content"
+            app:icon="@{@drawable/ic_back}"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:title="@{@string/submission_your_consent_title}" />
+
+        <ScrollView
+            android:layout_width="@dimen/match_constraint"
+            android:layout_height="@dimen/match_constraint"
+            app:layout_constraintBottom_toBottomOf="@id/guideline_bottom"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="1.0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/submission_your_consent_title"
+            app:layout_constraintVertical_bias="1.0">
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:focusable="true">
+
+                <include
+                    android:id="@+id/submission_your_consent_switch"
+                    layout="@layout/include_settings_switch_row"
+                    android:layout_width="@dimen/match_constraint"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_small"
+                    app:enabled="@{true}"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    app:showDivider="@{true}"
+                    app:subtitle="@{@string/submission_your_consent_switch_subtitle}" />
+
+                <TextView
+                    android:id="@+id/submission_your_consent_about_text"
+                    style="@style/subtitle"
+                    android:layout_width="@dimen/match_constraint"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_small"
+                    android:text="@string/submission_your_consent_about_agreement"
+                    app:layout_constraintEnd_toEndOf="@id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@+id/submission_your_consent_switch" />
+
+                <LinearLayout
+                    android:id="@+id/submission_your_consent_agreement_card"
+                    style="@style/cardTracing"
+                    android:layout_width="@dimen/match_constraint"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_medium"
+                    android:focusable="true"
+                    android:orientation="vertical"
+                    app:layout_constraintEnd_toStartOf="@+id/guideline_card_end"
+                    app:layout_constraintStart_toStartOf="@+id/guideline_card_start"
+                    app:layout_constraintTop_toBottomOf="@+id/submission_your_consent_about_text">
+
+                    <TextView
+                        android:id="@+id/submission_your_consent_agreement_title"
+                        style="@style/headline6"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginEnd="@dimen/spacing_small"
+                        android:accessibilityHeading="true"
+                        android:contentDescription="@string/submission_your_consent_agreement_title"
+                        android:text="@string/submission_your_consent_agreement_title"/>
+
+                    <TextView
+                        android:id="@+id/submission_your_consent_agreement_share_test_results_text"
+                        style="@style/subtitle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/spacing_normal"
+                        android:text="@string/submission_your_consent_agreement_share_test_results"/>
+
+                    <de.rki.coronawarnapp.ui.view.CountryListView
+                        android:id="@+id/submission_your_consent_agreement_country_list"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/spacing_small" />
+
+                    <TextView
+                        android:id="@+id/submission_your_consent_agreement_share_symptoms_text"
+                        style="@style/subtitle"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_marginTop="@dimen/spacing_normal"
+                        android:text="@string/submission_your_consent_agreement_share_symptoms"/>
+
+                </LinearLayout>
+
+                <View
+                    android:id="@+id/submission_your_consent_agreement_details_divider_top"
+                    android:layout_width="@dimen/match_constraint"
+                    android:layout_height="@dimen/card_divider"
+                    android:layout_marginTop="@dimen/spacing_medium"
+                    android:background="@color/colorHairline"
+                    app:layout_constraintEnd_toEndOf="@+id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@+id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@+id/submission_your_consent_agreement_card" />
+
+                <TextView
+                    android:id="@+id/submission_your_consent_agreement_details_text"
+                    style="@style/subtitle"
+                    android:layout_width="@dimen/match_constraint"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="@dimen/spacing_small"
+                    android:text="@string/submission_your_consent_agreement_details"
+                    app:layout_constraintEnd_toEndOf="@id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@+id/submission_your_consent_agreement_details_divider_top" />
+
+                <View
+                    android:id="@+id/submission_your_consent_agreement_details_divider_bottom"
+                    android:layout_width="@dimen/match_constraint"
+                    android:layout_height="@dimen/card_divider"
+                    android:layout_marginTop="@dimen/spacing_small"
+                    android:background="@color/colorHairline"
+                    app:layout_constraintEnd_toEndOf="@+id/guideline_end"
+                    app:layout_constraintStart_toStartOf="@+id/guideline_start"
+                    app:layout_constraintTop_toBottomOf="@+id/submission_your_consent_agreement_details_text" />
+
+                <include layout="@layout/merge_guidelines_side" />
+                <include layout="@layout/merge_guidelines_card" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+        </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" />
+
+        <include layout="@layout/merge_guidelines_side" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</layout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt
new file mode 100644
index 000000000..56b4b2e06
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/yourconsent/SubmissionYourConsentViewModelTest.kt
@@ -0,0 +1,109 @@
+package de.rki.coronawarnapp.ui.submission.yourconsent
+
+import de.rki.coronawarnapp.storage.SubmissionRepository
+import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository
+import de.rki.coronawarnapp.ui.Country
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.clearAllMocks
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+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.TestDispatcherProvider
+import testhelpers.extensions.CoroutinesTestExtension
+import testhelpers.extensions.InstantExecutorExtension
+
+@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class)
+class SubmissionYourConsentViewModelTest : BaseTest() {
+
+    @MockK lateinit var submissionRepository: SubmissionRepository
+    @MockK lateinit var interoperabilityRepository: InteroperabilityRepository
+
+    private val countryList = Country.values().toList()
+
+    @BeforeEach
+    fun setUp() {
+        MockKAnnotations.init(this)
+        every { interoperabilityRepository.countryListFlow } returns MutableStateFlow(countryList)
+        every { submissionRepository.hasGivenConsentToSubmission } returns flowOf(true)
+        every { submissionRepository.giveConsentToSubmission() } just Runs
+        every { submissionRepository.revokeConsentToSubmission() } just Runs
+    }
+
+    private fun createViewModel(): SubmissionYourConsentViewModel = SubmissionYourConsentViewModel(
+        interoperabilityRepository = interoperabilityRepository,
+        submissionRepository = submissionRepository,
+        dispatcherProvider = TestDispatcherProvider
+    )
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    @Test
+    fun `country list`() {
+        val viewModel = createViewModel()
+
+        viewModel.countryList.observeForever { }
+        viewModel.countryList.value shouldBe countryList
+    }
+
+    @Test
+    fun `go back`() {
+        val viewModel = createViewModel()
+
+        viewModel.goBack()
+        viewModel.clickEvent.value shouldBe SubmissionYourConsentEvents.GoBack
+    }
+
+    @Test
+    fun `consent removed`() {
+        val viewModel = createViewModel()
+
+        coEvery { submissionRepository.hasGivenConsentToSubmission } returns flowOf(true)
+        viewModel.switchConsent()
+        verify(exactly = 1) { submissionRepository.revokeConsentToSubmission() }
+    }
+
+    @Test
+    fun `consent given`() {
+        val viewModel = createViewModel()
+
+        coEvery { submissionRepository.hasGivenConsentToSubmission } returns flowOf(false)
+        viewModel.switchConsent()
+        verify(exactly = 1) { submissionRepository.giveConsentToSubmission() }
+    }
+
+    @Test
+    fun `consent repository changed`() {
+        val consentMutable = MutableStateFlow(false)
+        every { submissionRepository.hasGivenConsentToSubmission } returns consentMutable
+
+        val viewModel = createViewModel()
+
+        viewModel.consent.observeForever { }
+        viewModel.consent.value shouldBe false
+
+        consentMutable.value = true
+        viewModel.consent.value shouldBe true
+    }
+
+    @Test
+    fun `go to legal page`() {
+        val viewModel = createViewModel()
+
+        viewModel.goLegal()
+        viewModel.clickEvent.value shouldBe SubmissionYourConsentEvents.GoLegal
+    }
+}
-- 
GitLab