Skip to content
Snippets Groups Projects
Unverified Commit 52152995 authored by AlexanderAlferov's avatar AlexanderAlferov Committed by GitHub
Browse files

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
parent 30a30abd
No related branches found
No related tags found
No related merge requests found
...@@ -24,6 +24,8 @@ import de.rki.coronawarnapp.ui.submission.testresult.SubmissionTestResultFragmen ...@@ -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.testresult.SubmissionTestResultModule
import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningFragment
import de.rki.coronawarnapp.ui.submission.warnothers.SubmissionResultPositiveOtherWarningModule 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 @Module
internal abstract class SubmissionFragmentModule { internal abstract class SubmissionFragmentModule {
...@@ -67,6 +69,9 @@ internal abstract class SubmissionFragmentModule { ...@@ -67,6 +69,9 @@ internal abstract class SubmissionFragmentModule {
@ContributesAndroidInjector(modules = [SubmissionConsentModule::class]) @ContributesAndroidInjector(modules = [SubmissionConsentModule::class])
abstract fun submissionConsentScreen(): SubmissionConsentFragment abstract fun submissionConsentScreen(): SubmissionConsentFragment
@ContributesAndroidInjector(modules = [SubmissionYourConsentModule::class])
abstract fun submissionYourConsentScreen(): SubmissionYourConsentFragment
@ContributesAndroidInjector(modules = [SubmissionTestResultAvailableModule::class]) @ContributesAndroidInjector(modules = [SubmissionTestResultAvailableModule::class])
abstract fun submissionTestResultAvailableScreen(): SubmissionTestResultAvailableFragment abstract fun submissionTestResultAvailableScreen(): SubmissionTestResultAvailableFragment
......
package de.rki.coronawarnapp.ui.submission.yourconsent
sealed class SubmissionYourConsentEvents {
object GoBack : SubmissionYourConsentEvents()
object GoLegal : SubmissionYourConsentEvents()
}
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)
}
}
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>
}
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>
}
<?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
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
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment