Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Create Check-in confirmation screen (EXPOSUREAPP-5424) #2536

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7076b64
Refactoring
mtwalli Mar 4, 2021
fbb77fb
Rename
mtwalli Mar 4, 2021
bc09900
Add check in tab
mtwalli Mar 4, 2021
05fb833
lint
mtwalli Mar 4, 2021
1067a02
Connect scan fragment
mtwalli Mar 4, 2021
977feb8
Navigate to confirm event
mtwalli Mar 5, 2021
0a25643
Fix import
mtwalli Mar 5, 2021
85083d6
lint
mtwalli Mar 5, 2021
2f0b503
Add FAB text
mtwalli Mar 7, 2021
70c4cb5
Update MDC version
mtwalli Mar 7, 2021
2478b69
Catch error
mtwalli Mar 7, 2021
1ba716e
Animate transition
mtwalli Mar 7, 2021
c01c31c
Add space
mtwalli Mar 7, 2021
267a3d3
Connect check-in flow
mtwalli Mar 7, 2021
df7857e
Parse signed event
mtwalli Mar 7, 2021
915abd9
Merge branch 'feature/5062-event-registration-main' into feature/5424…
mtwalli Mar 7, 2021
20235be
Import SingleLiveData
mtwalli Mar 7, 2021
00d24a0
Add test
mtwalli Mar 7, 2021
c1e3101
Clean-up
mtwalli Mar 7, 2021
498b447
Delete ConfirmCheckInViewModel.kt
mtwalli Mar 8, 2021
b18f5f7
Support new deeplink host and requirements
mtwalli Mar 8, 2021
1a1c028
Validate uri
mtwalli Mar 8, 2021
a84ffa6
Update LauncherActivityTest.kt
mtwalli Mar 8, 2021
d13213e
Renaming
mtwalli Mar 9, 2021
92ffe42
Trace location times are in seconds
mtwalli Mar 9, 2021
95da841
Merge branch 'feature/5062-event-registration-main' into feature/5424…
mtwalli Mar 9, 2021
5d783bc
Delete redundants
mtwalli Mar 9, 2021
102a093
Merge branch 'feature/5062-event-registration-main' into feature/5424…
mtwalli Mar 10, 2021
94f5bb4
Remove destinations from main graph
mtwalli Mar 10, 2021
690dbc7
Merge branch 'feature/5062-event-registration-main' into feature/5424…
mtwalli Mar 10, 2021
2e6fba1
Use hard coded string
mtwalli Mar 10, 2021
42f9625
Verify uri
mtwalli Mar 10, 2021
8e388ac
Pass QRCodeVerifyResult directly
mtwalli Mar 10, 2021
acda89f
lint
mtwalli Mar 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Corona-Warn-App/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ dependencies {
def nav_version = "2.3.3"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin

import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult
import de.rki.coronawarnapp.eventregistration.common.decodeBase32
import de.rki.coronawarnapp.server.protocols.internal.evreg.SignedEventOuterClass
import io.kotest.assertions.throwables.shouldNotThrowAny
import io.kotest.matchers.shouldBe
import org.joda.time.Instant
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import testhelpers.BaseTestInstrumentation

@RunWith(JUnit4::class)
class VerifiedTraceLocationKtTest : BaseTestInstrumentation() {

@Test
fun testVerifiedTraceLocationMapping() {
shouldNotThrowAny {
val signedTraceLocation =
SignedEventOuterClass.SignedEvent.parseFrom(DECODED_TRACE_LOCATION.decodeBase32().toByteArray())
val verifiedTraceLocation =
QRCodeVerifyResult(singedTraceLocation = signedTraceLocation).toVerifiedTraceLocation()
verifiedTraceLocation shouldBe VerifiedTraceLocation(
guid = "Yc48RFi/hfyXKlF4DEDs/w==",
start = Instant.parse("1970-02-01T02:39:15.000Z"),
end = Instant.parse("1970-02-01T02:39:51.000Z"),
defaultCheckInLengthInMinutes = 30,
description = "CWA Launch Party"
)
}
}

companion object {
private const val DECODED_TRACE_LOCATION =
"BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUDBOJ2HSGGTQ6SACIHXQ6SAC" +
"KA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFFBU2SQCEEAJAUCJSQJ7WDM6" +
"75MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,7 @@ class LauncherActivityTest : BaseUITest() {

@Test
fun testDeepLinkLowercase() {
val uri = Uri.parse("https://coronawarn.app/E1/SOME_PATH_GOES_HERE")
launchActivity<LauncherActivity>(getIntent(uri))
}

@Test
fun testDeepLinkLowercaseWww() {
val uri = Uri.parse("https://www.coronawarn.app/E1/SOME_PATH_GOES_HERE")
val uri = Uri.parse("https://e.coronawarn.app/c1/SOME_PATH_GOES_HERE")
launchActivity<LauncherActivity>(getIntent(uri))
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package de.rki.coronawarnapp.test.eventregistration.ui.qrcode

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View
import android.widget.Toast
Expand All @@ -21,6 +22,7 @@ class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creati
private val viewModel: QrCodeCreationTestViewModel by cwaViewModels { viewModelFactory }
private val binding: FragmentTestQrcodeCreationBinding by viewBindingLazy()

@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

viewModel.sharingIntent.observe2(this) {
Expand All @@ -36,6 +38,11 @@ class QrCodeCreationTestFragment : Fragment(R.layout.fragment_test_qrcode_creati
Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show()
}

binding.qrCodeText.setText(
"HTTPS://E.CORONAWARN.APP/C1/BIYAUEDBZY6EIWF7QX6JOKSRPAGEB3H7CIIEGV2BEBGGC5LOMNUCAUD" +
"BOJ2HSGGTQ6SACIHXQ6SACKA6CJEDARQCEEAPHGEZ5JI2K2T422L5U3SMZY5DGCPUZ2RQACAYEJ3HQYMAFF" +
"BU2SQCEEAJAUCJSQJ7WDM675MCMOD3L2UL7ECJU7TYERH23B746RQTABO3CTI="
)
binding.generateQrCode.setOnClickListener {
viewModel.createQrCode(binding.qrCodeText.text.toString())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
android:id="@+id/qrCodeText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/qr_code_input"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
tools:layout="@layout/fragment_test_qrcode_creation" />
<fragment
android:id="@+id/scanCheckInQrCodeFragmentTest"
android:name="de.rki.coronawarnapp.ui.eventregistration.scan.ScanCheckInQrCodeFragment"
android:name="de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment"
android:label="ScanCheckInQrCodeFragment"
tools:layout="@layout/fragment_submission_qr_code_scan" />

Expand Down
4 changes: 0 additions & 4 deletions Corona-Warn-App/src/deviceForTesters/res/values/strings.xml

This file was deleted.

7 changes: 1 addition & 6 deletions Corona-Warn-App/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,7 @@
<category android:name="android.intent.category.BROWSABLE" />

<data
android:host="coronawarn.app"
android:pathPrefix="/"
android:scheme="https" />

<data
android:host="www.coronawarn.app"
android:host="e.coronawarn.app"
mtwalli marked this conversation as resolved.
Show resolved Hide resolved
android:pathPrefix="/"
android:scheme="https" />
</intent-filter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import dagger.Module
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.DefaultQRCodeVerifier
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier

@Suppress("EmptyClassBlock")
@Module
abstract class EventRegistrationModule {
@Binds
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.rki.coronawarnapp.eventregistration.checkins.qrcode

import java.net.URI

private const val SCHEME = "https"
private const val AUTHORITY = "e.coronawarn.app"
private const val PATH_PREFIX = "/c1"
private const val SIGNED_TRACE_LOCATION_BASE_32_REGEX =
"^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?\$"

/**
* Validate that QRCode scanned uri matches the following formulas:
* https://e.coronawarn.app/c1/SIGNED_TRACE_LOCATION_BASE32
* HTTPS://E.CORONAWARN.APP/C1/SIGNED_TRACE_LOCATION_BASE32
*/
fun String.isValidQRCodeUri(): Boolean =
URI.create(this).run {
scheme.equals(SCHEME, true) &&
authority.equals(AUTHORITY, true) &&
path.substringBeforeLast("/")
.equals(PATH_PREFIX, true) &&
path.substringAfterLast("/")
.matches(Regex(SIGNED_TRACE_LOCATION_BASE_32_REGEX))
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ fun BottomNavigationView.setupWithNavController2(
// For destinations that always show the bottom bar
val inShowList = destination.id in listOf(
R.id.mainFragment,
R.id.checkInsFragment,
R.id.contactDiaryOverviewFragment
)
// For destinations that can show or hide the bottom bar in different cases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package de.rki.coronawarnapp.ui.eventregistration

import dagger.Module
import dagger.android.ContributesAndroidInjector
import de.rki.coronawarnapp.ui.eventregistration.checkin.ConfirmCheckInFragment
import de.rki.coronawarnapp.ui.eventregistration.checkin.ConfirmCheckInModule
import de.rki.coronawarnapp.ui.eventregistration.scan.ScanCheckInQrCodeFragment
import de.rki.coronawarnapp.ui.eventregistration.scan.ScanCheckInQrCodeModule
import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsFragment
import de.rki.coronawarnapp.ui.eventregistration.attendee.checkin.CheckInsModule
import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInFragment
import de.rki.coronawarnapp.ui.eventregistration.attendee.confirm.ConfirmCheckInModule
import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeFragment
import de.rki.coronawarnapp.ui.eventregistration.attendee.scan.ScanCheckInQrCodeModule

@Module
internal abstract class EventRegistrationUIModule {
Expand All @@ -15,4 +17,7 @@ internal abstract class EventRegistrationUIModule {

@ContributesAndroidInjector(modules = [ConfirmCheckInModule::class])
abstract fun confirmCheckInFragment(): ConfirmCheckInFragment

@ContributesAndroidInjector(modules = [CheckInsModule::class])
abstract fun checkInsFragment(): CheckInsFragment
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin

import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.View
import androidx.core.net.toUri
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.transition.Hold
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentCheckInsBinding
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.doNavigate
import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.viewBindingLazy
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
import javax.inject.Inject

class CheckInsFragment : Fragment(R.layout.fragment_check_ins), AutoInject {
mtwalli marked this conversation as resolved.
Show resolved Hide resolved

@Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
private val viewModel: CheckInsViewModel by cwaViewModels { viewModelFactory }
private val binding: FragmentCheckInsBinding by viewBindingLazy()

// Encoded uri is a one-time use data and then cleared
private val uri: String?
get() = navArgs<CheckInsFragmentArgs>().value
.uri
.also { arguments?.clear() }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
exitTransition = Hold()
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

with(binding.scanCheckinQrcodeFab) {
setOnClickListener {
findNavController().navigate(
R.id.action_checkInsFragment_to_scanCheckInQrCodeFragment,
null,
null,
FragmentNavigatorExtras(this to transitionName)
)
}
}

uri?.let { viewModel.verifyUri(it) }
viewModel.verifyResult.observe2(this) {
doNavigate(
CheckInsFragmentDirections
.actionCheckInsFragmentToConfirmCheckInFragment(it.toVerifiedTraceLocation())
)
}
}

companion object {
fun uri(rootUri: String): Uri = "coronawarnapp://check-ins/$rootUri".toUri()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin

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 CheckInsModule {
@Binds
@IntoMap
@CWAViewModelKey(CheckInsViewModel::class)
abstract fun checkInsFragment(
factory: CheckInsViewModel.Factory
): CWAViewModelFactory<out CWAViewModel>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package de.rki.coronawarnapp.ui.eventregistration.attendee.checkin

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifyResult
import de.rki.coronawarnapp.eventregistration.checkins.qrcode.isValidQRCodeUri
import de.rki.coronawarnapp.exception.ExceptionCategory
import de.rki.coronawarnapp.exception.reporting.report
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import timber.log.Timber

class CheckInsViewModel @AssistedInject constructor(
dispatcherProvider: DispatcherProvider,
private val qrCodeVerifier: QRCodeVerifier
) : CWAViewModel(dispatcherProvider) {

private val verifyResultData = MutableLiveData<QRCodeVerifyResult>()
val verifyResult: LiveData<QRCodeVerifyResult> = verifyResultData

fun verifyUri(uri: String) = launch {
try {
Timber.i("uri: $uri")
if (!uri.isValidQRCodeUri())
throw IllegalArgumentException("Invalid uri: $uri")

val encodedEvent = uri.substringAfterLast("/")
val verifyResult = qrCodeVerifier.verify(encodedEvent)
Timber.i("verifyResult: $verifyResult")
verifyResultData.postValue(verifyResult)
} catch (e: Exception) {
Timber.d(e, "TraceLocation verification failed")
mtwalli marked this conversation as resolved.
Show resolved Hide resolved
e.report(ExceptionCategory.INTERNAL)
}
}

@AssistedFactory
interface Factory : SimpleCWAViewModelFactory<CheckInsViewModel>
}
Loading