diff --git a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json index 208aa627204f37a0c2d0e6e84affa74de049ea33..f5c14553bb62386c785fe0f4f551332968cc8ee0 100644 --- a/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json +++ b/Corona-Warn-App/schemas/de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "244eed2a4665a1c60d0792de5945888b", + "identityHash": "48d71b0a00d0c7fe01ffe05d5fecb512", "entities": [ { "tableName": "checkin", @@ -101,12 +101,80 @@ }, "indices": [], "foreignKeys": [] + }, + { + "tableName": "traceLocations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`guid` TEXT NOT NULL, `version` INTEGER NOT NULL, `type` INTEGER NOT NULL, `description` TEXT NOT NULL, `address` TEXT NOT NULL, `startDate` TEXT, `endDate` TEXT, `defaultCheckInLengthInMinutes` INTEGER, `signature` TEXT NOT NULL, PRIMARY KEY(`guid`))", + "fields": [ + { + "fieldPath": "guid", + "columnName": "guid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "address", + "columnName": "address", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "startDate", + "columnName": "startDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "endDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "defaultCheckInLengthInMinutes", + "columnName": "defaultCheckInLengthInMinutes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "guid" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] } ], "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '244eed2a4665a1c60d0792de5945888b')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '48d71b0a00d0c7fe01ffe05d5fecb512')" ] } } \ No newline at end of file diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt new file mode 100644 index 0000000000000000000000000000000000000000..2797a59ee206d546406dc7f7e460bb2839dfe756 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/CheckInDatabaseData.kt @@ -0,0 +1,40 @@ +package de.rki.coronawarnapp.eventregistration.storage + +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity +import org.joda.time.Instant + +object CheckInDatabaseData { + + val testCheckIn = TraceLocationCheckInEntity( + guid = "testGuid1", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER.value, + description = "testDescription1", + address = "testAddress1", + traceLocationStart = Instant.parse("2021-01-01T12:00:00.000Z"), + traceLocationEnd = Instant.parse("2021-01-01T15:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "Signature", + checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"), + checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z"), + targetCheckInEnd = Instant.parse("2021-01-01T12:45:00.000Z"), + createJournalEntry = true + ) + + val testCheckInWithoutCheckOutTime = TraceLocationCheckInEntity( + guid = "testGuid2", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER.value, + description = "testDescription2", + address = "testAddress2", + traceLocationStart = null, + traceLocationEnd = null, + defaultCheckInLengthInMinutes = null, + signature = "Signature", + checkInStart = Instant.parse("2021-01-01T12:30:00.000Z"), + checkInEnd = null, + targetCheckInEnd = Instant.parse("2021-01-01T12:45:00.000Z"), + createJournalEntry = true + ) +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..60223bea5e51e2caba73607eb59fa01dabca3b01 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationCheckInDaoTest.kt @@ -0,0 +1,85 @@ +package de.rki.coronawarnapp.eventregistration.storage + +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import de.rki.coronawarnapp.eventregistration.storage.CheckInDatabaseData.testCheckIn +import de.rki.coronawarnapp.eventregistration.storage.CheckInDatabaseData.testCheckInWithoutCheckOutTime +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.joda.time.Instant +import org.junit.After +import org.junit.Test +import testhelpers.BaseTestInstrumentation + +class TraceLocationCheckInDaoTest : BaseTestInstrumentation() { + + private val traceLocationDatabase = Room.inMemoryDatabaseBuilder( + ApplicationProvider.getApplicationContext(), + TraceLocationDatabase::class.java + ).build() + + private val checkInDao = traceLocationDatabase.eventCheckInDao() + + @After + fun tearDown() { + traceLocationDatabase.clearAllTables() + } + + @Test + fun traceLocationCheckInDaoShouldReturnNoEntriesInitially() = runBlocking { + val checkInsFlow = checkInDao.allEntries() + + checkInsFlow.first() shouldBe emptyList() + } + + @Test + fun traceLocationCheckInDaoShouldSuccessfullyInsertCheckIn() = runBlocking { + val checkInsFlow = checkInDao.allEntries() + + val generatedId = checkInDao.insert(testCheckIn) + + checkInsFlow.first() shouldBe listOf(testCheckIn.copy(id = generatedId)) + } + + @Test + fun traceLocationCheckInDaoShouldSuccessfullyInsertMultipleCheckIns() = runBlocking { + val checkInsFlow = checkInDao.allEntries() + + val testCheckInGeneratedId = checkInDao.insert(testCheckIn) + val testCheckInWithoutCheckOutTimeGeneratedId = checkInDao.insert(testCheckInWithoutCheckOutTime) + + checkInsFlow.first() shouldBe listOf( + testCheckIn.copy(id = testCheckInGeneratedId), + testCheckInWithoutCheckOutTime.copy(id = testCheckInWithoutCheckOutTimeGeneratedId) + ) + } + + @Test + fun traceLocationCheckInDaoShouldSuccessfullyUpdateCheckIn() = runBlocking { + val checkInsFlow = checkInDao.allEntries() + + val testCheckInGeneratedId = checkInDao.insert(testCheckInWithoutCheckOutTime) + + val updatedCheckIn = testCheckInWithoutCheckOutTime.copy( + id = testCheckInGeneratedId, + checkInEnd = Instant.parse("2021-01-01T14:00:00.000Z") + ) + + checkInDao.update(updatedCheckIn) + + checkInsFlow.first() shouldBe listOf(updatedCheckIn) + } + + @Test + fun traceLocationCheckInDaoShouldSuccessfullyDeleteAllCheckIns() = runBlocking { + val checkInsFlow = checkInDao.allEntries() + + checkInDao.insert(testCheckIn) + checkInDao.insert(testCheckInWithoutCheckOutTime) + + checkInDao.deleteAll() + + checkInsFlow.first() shouldBe emptyList() + } +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDaoTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDaoTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ab29881320b63e7230c4aea57350bbe554ba4dad --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDaoTest.kt @@ -0,0 +1,72 @@ +package de.rki.coronawarnapp.eventregistration.storage + +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabaseData.testTraceLocation1 +import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabaseData.testTraceLocation2 +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Test +import testhelpers.BaseTestInstrumentation + +class TraceLocationDaoTest : BaseTestInstrumentation() { + + private val traceLocationDatabase = Room.inMemoryDatabaseBuilder( + ApplicationProvider.getApplicationContext(), + TraceLocationDatabase::class.java + ).build() + + private val traceLocationDao = traceLocationDatabase.traceLocationDao() + + @After + fun tearDown() { + traceLocationDatabase.clearAllTables() + } + + @Test + fun traceLocationDaoShouldReturnNoEntriesInitially() = runBlocking { + val traceLocationsFlow = traceLocationDao.allEntries() + + traceLocationsFlow.first() shouldBe emptyList() + } + + @Test + fun traceLocationDaoShouldSuccessfullyInsertTraceLocation() = runBlocking { + val traceLocationsFlow = traceLocationDao.allEntries() + + traceLocationDao.insert(testTraceLocation1) + + traceLocationsFlow.first() shouldBe listOf(testTraceLocation1) + } + + @Test + fun traceLocationDaoShouldSuccessfullyInsertMultipleTraceLocations() = runBlocking { + val traceLocationsFlow = traceLocationDao.allEntries() + + traceLocationDao.insert(testTraceLocation1) + traceLocationDao.insert(testTraceLocation2) + + traceLocationsFlow.first() shouldBe listOf(testTraceLocation1, testTraceLocation2) + } + + @Test + fun traceLocationDaoShouldSuccessfullyDeleteSingleTraceLocation() = runBlocking { + val traceLocationsFlow = traceLocationDao.allEntries() + + traceLocationDao.insert(testTraceLocation1) + traceLocationDao.delete(testTraceLocation1) + traceLocationsFlow.first() shouldBe emptyList() + } + + @Test + fun traceLocationDaoShouldSuccessfullyDeleteAllTraceLocations() = runBlocking { + val traceLocationsFlow = traceLocationDao.allEntries() + + traceLocationDao.insert(testTraceLocation1) + traceLocationDao.insert(testTraceLocation2) + traceLocationDao.deleteAll() + traceLocationsFlow.first() shouldBe emptyList() + } +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabaseData.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabaseData.kt new file mode 100644 index 0000000000000000000000000000000000000000..3891b6bdfd0e2d397f6208a0381a67b9ae360cce --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabaseData.kt @@ -0,0 +1,32 @@ +package de.rki.coronawarnapp.eventregistration.storage + +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity +import org.joda.time.Instant + +object TraceLocationDatabaseData { + + val testTraceLocation1 = TraceLocationEntity( + guid = "TestGuid1", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation1", + address = "TestTraceLocationAddress1", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = null, + signature = "signature1" + ) + + val testTraceLocation2 = TraceLocationEntity( + guid = "TestGuid2", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation2", + address = "TestTraceLocationAddress2", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = 15, + signature = "signature2" + ) +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt index a31bd5154b3b372791f7495cc945caf6efadbdf2..7ccb3476a11a996eb1c0d6e3bf04f54b4b1c9646 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/EventRegistrationTestFragment.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentTestEventregistrationBinding import de.rki.coronawarnapp.test.menu.ui.TestMenuItem @@ -23,6 +24,8 @@ class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregis private val binding: FragmentTestEventregistrationBinding by viewBindingLazy() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { scanCheckInQrCode.setOnClickListener { doNavigate( @@ -37,6 +40,14 @@ class EventRegistrationTestFragment : Fragment(R.layout.fragment_test_eventregis .actionEventRegistrationTestFragmentToTestQrCodeCreationFragment() ) } + + createEventButton.setOnClickListener { + findNavController().navigate(R.id.createEventTestFragment) + } + + showEventsButton.setOnClickListener { + findNavController().navigate(R.id.showStoredEventsTestFragment) + } } } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..3e6daef237373951dc50bf59d214b44f3b72232b --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragment.kt @@ -0,0 +1,88 @@ +package de.rki.coronawarnapp.test.eventregistration.ui.createevent + +import android.os.Bundle +import android.view.View +import android.widget.ArrayAdapter +import android.widget.AutoCompleteTextView +import androidx.core.widget.doAfterTextChanged +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.contactdiary.util.hideKeyboard +import de.rki.coronawarnapp.databinding.FragmentTestCreateeventBinding +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 timber.log.Timber +import javax.inject.Inject + +class CreateEventTestFragment : Fragment(R.layout.fragment_test_createevent), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: CreateEventTestViewModel by cwaViewModels { viewModelFactory } + + private val binding: FragmentTestCreateeventBinding by viewBindingLazy() + + private val eventString = "Event" + private val locationString = "Location" + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initSpinner() + initOnCreateEventClicked() + observeViewModelResult() + } + + private fun observeViewModelResult() { + vm.result.observe2(this) { + when (it) { + is CreateEventTestViewModel.Result.Success -> + binding.resultText.text = "Successfully stored: ${it.eventEntity}" + is CreateEventTestViewModel.Result.Error -> + binding.resultText.text = "There is something wrong with your input values, please check again." + } + } + } + + private fun initOnCreateEventClicked() = with(binding) { + createEventButton.setOnClickListener { + vm.createEvent( + eventOrLocationSpinner.editText!!.text.toString(), + eventDescription.text.toString(), + eventAddress.text.toString(), + eventStartEditText.text.toString(), + eventEndEditText.text.toString(), + eventDefaultCheckinLengthInMinutes.text.toString() + ) + it.hideKeyboard() + } + } + + private fun initSpinner() { + val items = listOf(eventString, locationString) + with(binding.eventOrLocationSpinner.editText as AutoCompleteTextView) { + setText(items.first(), false) + setAdapter(ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, items)) + doAfterTextChanged { } + doOnTextChanged { text, start, before, count -> + Timber.d("text: $text, start: $start, before: $before, count: $count") + + when (text.toString()) { + eventString -> { + binding.eventStart.visibility = View.VISIBLE + binding.eventEnd.visibility = View.VISIBLE + } + locationString -> { + binding.eventStart.visibility = View.GONE + binding.eventEnd.visibility = View.GONE + binding.eventStartEditText.text = null + binding.eventEndEditText.text = null + } + } + } + } + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragmentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..258a96c7ad463471d8cac73695fcb3e546f3ae5a --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestFragmentModule.kt @@ -0,0 +1,19 @@ +package de.rki.coronawarnapp.test.eventregistration.ui.createevent + +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 CreateEventTestFragmentModule { + + @Binds + @IntoMap + @CWAViewModelKey(CreateEventTestViewModel::class) + abstract fun testCreateEventFragment( + factory: CreateEventTestViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..3668a469297caae2efb4ecde3da605eb74d23d8e --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/createevent/CreateEventTestViewModel.kt @@ -0,0 +1,75 @@ +package de.rki.coronawarnapp.test.eventregistration.ui.createevent + +import androidx.lifecycle.MutableLiveData +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.eventregistration.events.DefaultTraceLocation +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository +import de.rki.coronawarnapp.util.TimeAndDateExtensions.seconds +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import org.joda.time.DateTime +import org.joda.time.format.DateTimeFormat +import timber.log.Timber +import java.util.UUID + +class CreateEventTestViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, + private val traceLocationRepository: TraceLocationRepository +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<CreateEventTestViewModel> + + val result = MutableLiveData<Result>() + + fun createEvent( + type: String, + description: String, + address: String, + start: String, + end: String, + defaultCheckInLengthInMinutes: String + ) { + try { + val startDate = + if (start.isBlank()) null else DateTime.parse(start, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm")) + val endDate = + if (end.isBlank()) null else DateTime.parse(end, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm")) + + /* TODO: wait for new protobuf messages 'TraceLocation' and perform network request to get + 'SignedTraceLocation' */ + + // Backend needs UNIX timestamp in Seconds, not milliseconds + val startTimeStampSeconds = startDate?.toInstant()?.seconds ?: 0 + val endTimeStampSeconds = endDate?.toInstant()?.seconds ?: 0 + + val traceLocationType = + if (type == "Event") TraceLocation.Type.TEMPORARY_OTHER else TraceLocation.Type.PERMANENT_OTHER + + val traceLocation = DefaultTraceLocation( + UUID.randomUUID().toString(), // will be provided by the server when the endpoint is ready + traceLocationType, + description, + address, + startDate?.toInstant(), + endDate?.toInstant(), + defaultCheckInLengthInMinutes.toInt(), + "ServerSignature" + ) + + traceLocationRepository.addTraceLocation(traceLocation) + result.postValue(Result.Success(traceLocation)) + } catch (exception: Exception) { + Timber.d("Something went wrong when trying to create an event: $exception") + result.postValue(Result.Error) + } + } + + sealed class Result { + object Error : Result() + data class Success(val eventEntity: TraceLocation) : Result() + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..62bd7b04808a57d947b01fc3693ff8b70acdbbf4 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragment.kt @@ -0,0 +1,48 @@ +package de.rki.coronawarnapp.test.eventregistration.ui.showevents + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.databinding.FragmentTestShowstoredeventsBinding +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +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 ShowStoredEventsTestFragment : Fragment(R.layout.fragment_test_showstoredevents), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val vm: ShowStoredEventsTestViewModel by cwaViewModels { viewModelFactory } + + private val binding: FragmentTestShowstoredeventsBinding by viewBindingLazy() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + vm.storedEvents.observe2(this) { events -> + binding.storedEvents.text = events.joinToString(separator = "\n\n") { it.getSimpleUIString() } + } + + binding.deleteAllEvents.setOnClickListener { + vm.deleteAllEvents() + } + } + + private fun TraceLocation.getSimpleUIString(): String { + return listOf( + "guid = $guid", + "type = $type", + "description = $description", + "location = $address", + "startTime = $startDate", + "endTime = $endDate", + "defaultCheckInLengthInMinutes = $defaultCheckInLengthInMinutes", + "signature = $signature", + "version = $version" + ).joinToString(separator = "\n") + } +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragmentModule.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragmentModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..45e3c4763e437a8cc30a17c144e77d7e1af7174c --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestFragmentModule.kt @@ -0,0 +1,19 @@ +package de.rki.coronawarnapp.test.eventregistration.ui.showevents + +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 ShowStoredEventsTestFragmentModule { + + @Binds + @IntoMap + @CWAViewModelKey(ShowStoredEventsTestViewModel::class) + abstract fun testStoredEventsFragment( + factory: ShowStoredEventsTestViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> +} diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..f85513da0a4ec166c8df5c5e13973a0b7a2acfc5 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/eventregistration/ui/showevents/ShowStoredEventsTestViewModel.kt @@ -0,0 +1,24 @@ +package de.rki.coronawarnapp.test.eventregistration.ui.showevents + +import androidx.lifecycle.asLiveData +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory + +class ShowStoredEventsTestViewModel @AssistedInject constructor( + dispatcherProvider: DispatcherProvider, + private val traceLocationRepository: TraceLocationRepository +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + @AssistedFactory + interface Factory : SimpleCWAViewModelFactory<ShowStoredEventsTestViewModel> + + val storedEvents = traceLocationRepository.allTraceLocations.asLiveData() + + fun deleteAllEvents() { + traceLocationRepository.deleteAllTraceLocations() + } +} 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 2511ed84bfa519e79094702aaafcae0bdff0fde4..e0ecef22020975d24885f70fd2549a8b96656083 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 @@ -16,8 +16,12 @@ import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaOnboardingFragmentModul import de.rki.coronawarnapp.test.deltaonboarding.ui.DeltaonboardingFragment import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragment import de.rki.coronawarnapp.test.eventregistration.ui.EventRegistrationTestFragmentModule +import de.rki.coronawarnapp.test.eventregistration.ui.createevent.CreateEventTestFragment +import de.rki.coronawarnapp.test.eventregistration.ui.createevent.CreateEventTestFragmentModule import de.rki.coronawarnapp.test.eventregistration.ui.qrcode.QrCodeCreationTestFragment import de.rki.coronawarnapp.test.eventregistration.ui.qrcode.QrCodeCreationTestFragmentModule +import de.rki.coronawarnapp.test.eventregistration.ui.showevents.ShowStoredEventsTestFragment +import de.rki.coronawarnapp.test.eventregistration.ui.showevents.ShowStoredEventsTestFragmentModule import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragment import de.rki.coronawarnapp.test.keydownload.ui.KeyDownloadTestFragmentModule import de.rki.coronawarnapp.test.menu.ui.TestMenuFragment @@ -75,4 +79,10 @@ abstract class MainActivityTestModule { @ContributesAndroidInjector(modules = [QrCodeCreationTestFragmentModule::class]) abstract fun qrCodeCreation(): QrCodeCreationTestFragment + + @ContributesAndroidInjector(modules = [CreateEventTestFragmentModule::class]) + abstract fun createEvent(): CreateEventTestFragment + + @ContributesAndroidInjector(modules = [ShowStoredEventsTestFragmentModule::class]) + abstract fun showStoredEvents(): ShowStoredEventsTestFragment } diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_createevent.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_createevent.xml new file mode 100644 index 0000000000000000000000000000000000000000..afd82d5c5bb5f5f2b149d587eae2549a9a3204ac --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_createevent.xml @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/spacing_normal"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + style="@style/headline5" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_normal" + android:text="Create new event" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="HardcodedText" /> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/eventOrLocationSpinner" + style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu" + android:layout_marginBottom="@dimen/spacing_tiny" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Type"> + + <AutoCompleteTextView + android:enabled="false" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="none" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_tiny"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/event_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Description" + android:maxLines="1" + android:padding="@dimen/spacing_tiny" + tools:ignore="HardcodedText" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_tiny"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/event_address" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Address" + android:maxLines="1" + android:padding="@dimen/spacing_tiny" + tools:ignore="HardcodedText" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/event_start" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_tiny"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/event_start_edit_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Start (yyyy-MM-dd HH:mm)" + android:maxLines="1" + android:padding="@dimen/spacing_tiny" + tools:ignore="HardcodedText" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/event_end" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_tiny"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/event_end_edit_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="End (yyyy-MM-dd HH:mm)" + android:maxLines="1" + android:padding="@dimen/spacing_tiny" + tools:ignore="HardcodedText" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + + android:layout_marginBottom="@dimen/spacing_normal"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/event_default_checkin_length_in_minutes" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="Default Check-In Lenght in Minutes - multiple of 10" + android:maxLines="1" + android:padding="@dimen/spacing_tiny" + android:inputType="numberDecimal" + tools:ignore="HardcodedText" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.button.MaterialButton + android:id="@+id/create_event_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Create Event" + tools:ignore="HardcodedText" /> + + <TextView + android:id="@+id/resultText" + style="@style/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_normal" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="HardcodedText" /> + + </LinearLayout> + + </ScrollView> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml index 84c36e862bef0376e61bf9dad335fb246b55c8ec..390245bc42225eea7e9109b18285697cd8bcb503 100644 --- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_eventregistration.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" tools:ignore="HardcodedText"> <LinearLayout @@ -57,5 +58,57 @@ </LinearLayout> + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/event_container" + style="@style/Card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/spacing_tiny"> + + <TextView + android:id="@+id/event_title" + style="@style/headline6" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:text="Events" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/events_body" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacing_tiny" + android:textIsSelectable="true" + android:text="After creating an event in the app, it is sent to the server and returned together with a guid and a signature." + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/event_title" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/create_event_button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:text="Create Event" + app:layout_constraintEnd_toStartOf="@+id/show_events_button" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/events_body" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/show_events_button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:text="Show stored Events" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/create_event_button" + app:layout_constraintTop_toBottomOf="@id/events_body" /> + </androidx.constraintlayout.widget.ConstraintLayout> + + </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_showstoredevents.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_showstoredevents.xml new file mode 100644 index 0000000000000000000000000000000000000000..3521aafdae703228d9a98da5f2297dfdb2ef9a14 --- /dev/null +++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_showstoredevents.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/spacing_normal"> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@+id/storedEvents" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/spacing_normal" + android:text="Show stored Events" + tools:ignore="HardcodedText" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/deleteAllEvents" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Delete All Events" /> + + </LinearLayout> + + </ScrollView> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file 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 2793401c8fe4bdc56e747b61e164d29959bd322b..5f3d417bb90e61a607e9fb9a7950d147a8b2f30f 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 @@ -49,7 +49,6 @@ <action android:id="@+id/action_test_menu_fragment_to_eventRegistrationTestFragment" app:destination="@id/eventRegistrationTestFragment" /> - </fragment> <fragment @@ -139,6 +138,12 @@ <action android:id="@+id/action_eventRegistrationTestFragment_to_scanCheckInQrCodeFragment" app:destination="@id/scanCheckInQrCodeFragmentTest" /> + <action + android:id="@+id/action_eventRegistrationTestFragment_to_CreateEventTestFragment" + app:destination="@id/createEventTestFragment" /> + <action + android:id="@+id/action_eventRegistrationTestFragment_to_ShowStoredEventsTestFragment" + app:destination="@id/showStoredEventsTestFragment" /> </fragment> <fragment @@ -152,4 +157,16 @@ android:label="ScanCheckInQrCodeFragment" tools:layout="@layout/fragment_submission_qr_code_scan" /> + <fragment + android:id="@+id/createEventTestFragment" + android:name="de.rki.coronawarnapp.test.eventregistration.ui.createevent.CreateEventTestFragment" + android:label="CreateEventTestFragment" + tools:layout="@layout/fragment_test_createevent" /> + <fragment + android:id="@+id/showStoredEventsTestFragment" + android:name="de.rki.coronawarnapp.test.eventregistration.ui.showevents.ShowStoredEventsTestFragment" + android:label="ShowStoredEventsTestFragment" + tools:layout="@layout/fragment_test_showstoredevents" /> + + </navigation> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt index 65ab558e1255f418cd5563828d1e2ad19a7f1993..2a082136720b9aaa1cc5007295b39be1345e57c3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/EventRegistrationModule.kt @@ -4,9 +4,16 @@ import dagger.Binds import dagger.Module import de.rki.coronawarnapp.eventregistration.checkins.qrcode.DefaultQRCodeVerifier import de.rki.coronawarnapp.eventregistration.checkins.qrcode.QRCodeVerifier +import de.rki.coronawarnapp.eventregistration.storage.repo.DefaultTraceLocationRepository +import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository @Module abstract class EventRegistrationModule { + @Binds abstract fun qrCodeVerifier(qrCodeVerifier: DefaultQRCodeVerifier): QRCodeVerifier + + @Binds + abstract fun traceLocationRepository(defaultTraceLocationRepo: DefaultTraceLocationRepository): + TraceLocationRepository } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocation.kt new file mode 100644 index 0000000000000000000000000000000000000000..f792ebf9ea52339be007226803ba26598b307ce8 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocation.kt @@ -0,0 +1,45 @@ +package de.rki.coronawarnapp.eventregistration.events + +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity +import org.joda.time.Instant + +const val TRACE_LOCATION_VERSION = 1 + +data class DefaultTraceLocation( + override val guid: String, + override val type: TraceLocation.Type, + override val description: String, + override val address: String, + override val startDate: Instant?, + override val endDate: Instant?, + override val defaultCheckInLengthInMinutes: Int?, + override val signature: String, + override val version: Int = TRACE_LOCATION_VERSION, +) : TraceLocation + +fun List<TraceLocationEntity>.toTraceLocations() = this.map { it.toTraceLocation() } + +fun TraceLocationEntity.toTraceLocation() = DefaultTraceLocation( + guid = guid, + type = type, + description = description, + address = address, + startDate = startDate, + endDate = endDate, + defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes, + signature = signature, + version = version +) + +/*fun SignedEventOuterClass.SignedEvent.toHostedEvent(): TraceLocation = + DefaultTraceLocation( + guid = event.guid.toString(), + type = enumValues<TraceLocation.Type>()[type], + description = event.description, + address = "hardcodedLocation", // event.location, + // backend needs UNIX timestamp in seconds, so we have to multiply it by 1000 to get milliseconds + startDate = Instant.ofEpochMilli(event.start.toLong() * 1000), + endDate = Instant.ofEpochMilli(event.end.toLong() * 1000), + defaultCheckInLengthInMinutes = event.defaultCheckInLengthInMinutes, + signature = signature.toString() + )*/ diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/HostedEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/HostedEvent.kt deleted file mode 100644 index df0f5db9dca3f09ff65b1c13a3a86b36326474d0..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/HostedEvent.kt +++ /dev/null @@ -1,18 +0,0 @@ -package de.rki.coronawarnapp.eventregistration.events - -import org.joda.time.Duration -import org.joda.time.Instant - -/** - * A verified event that the host has been registered. - * If you are a host, this is the event you created successfully. - */ -interface HostedEvent { - - val guid: String - val description: String - val startTime: Instant? - val endTime: Instant? - val defaultCheckInLength: Duration? - val signature: String -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/HostedEventRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/HostedEventRepository.kt deleted file mode 100644 index aa8a385a52a4f0e0d9c289f9e6de82f769353c9d..0000000000000000000000000000000000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/HostedEventRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package de.rki.coronawarnapp.eventregistration.events - -import kotlinx.coroutines.flow.Flow - -interface HostedEventRepository { - - val allHostedEvents: Flow<List<HostedEvent>> - - fun addHostedEvent(event: HostedEvent) - - fun deleteHostedEvent(event: HostedEvent) -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/TraceLocation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/TraceLocation.kt new file mode 100644 index 0000000000000000000000000000000000000000..1316ea7996b67e139cf70ffe404017bf91c1e23c --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/events/TraceLocation.kt @@ -0,0 +1,21 @@ +package de.rki.coronawarnapp.eventregistration.events + +import org.joda.time.Instant + +interface TraceLocation { + val guid: String + val version: Int + val type: Type + val description: String + val address: String + val startDate: Instant? + val endDate: Instant? + val defaultCheckInLengthInMinutes: Int? + val signature: String + + enum class Type(val value: Int) { + UNSPECIFIED(0), + PERMANENT_OTHER(1), + TEMPORARY_OTHER(2) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt index fe4ac20c10544994ab10f1d9ce5a475a9882a767..30e508cf4c45d0a9145c9551e64523c457f20a99 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/TraceLocationDatabase.kt @@ -6,26 +6,31 @@ import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import de.rki.coronawarnapp.eventregistration.storage.dao.CheckInDao +import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationCheckInEntity +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationConverters +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity import de.rki.coronawarnapp.util.database.CommonConverters import de.rki.coronawarnapp.util.di.AppContext import javax.inject.Inject @Database( entities = [ - TraceLocationCheckInEntity::class + TraceLocationCheckInEntity::class, + TraceLocationEntity::class ], version = 1, exportSchema = true ) -@TypeConverters(CommonConverters::class) +@TypeConverters(CommonConverters::class, TraceLocationConverters::class) abstract class TraceLocationDatabase : RoomDatabase() { abstract fun eventCheckInDao(): CheckInDao + abstract fun traceLocationDao(): TraceLocationDao class Factory @Inject constructor(@AppContext private val context: Context) { - fun create(databaseName: String = TRACE_LOCATIONS_DATABASE_NAME): TraceLocationDatabase = Room - .databaseBuilder(context, TraceLocationDatabase::class.java, databaseName) + fun create() = Room + .databaseBuilder(context, TraceLocationDatabase::class.java, TRACE_LOCATIONS_DATABASE_NAME) .build() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/TraceLocationDao.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/TraceLocationDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..888f5e3c4bc76f34eead4cf92d0ef1c10e72498b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/dao/TraceLocationDao.kt @@ -0,0 +1,25 @@ +package de.rki.coronawarnapp.eventregistration.storage.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity +import kotlinx.coroutines.flow.Flow + +@Dao +@Suppress("UnnecessaryAbstractClass") +abstract class TraceLocationDao { + + @Query("SELECT * FROM traceLocations") + abstract fun allEntries(): Flow<List<TraceLocationEntity>> + + @Insert + abstract suspend fun insert(traceLocation: TraceLocationEntity) + + @Delete + abstract suspend fun delete(traceLocation: TraceLocationEntity) + + @Query("DELETE FROM traceLocations") + abstract suspend fun deleteAll() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConverters.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConverters.kt new file mode 100644 index 0000000000000000000000000000000000000000..89546329e0ea2ac472ad58100bb89d733ec818dd --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConverters.kt @@ -0,0 +1,13 @@ +package de.rki.coronawarnapp.eventregistration.storage.entity + +import androidx.room.TypeConverter +import de.rki.coronawarnapp.eventregistration.events.TraceLocation + +class TraceLocationConverters { + + @TypeConverter + fun toTraceLocationType(value: Int) = enumValues<TraceLocation.Type>().single { it.value == value } + + @TypeConverter + fun fromTraceLocationType(type: TraceLocation.Type) = type.value +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntity.kt new file mode 100644 index 0000000000000000000000000000000000000000..c681d3a92acf6c123ba70af40d17a86447ae178b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntity.kt @@ -0,0 +1,35 @@ +package de.rki.coronawarnapp.eventregistration.storage.entity + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import org.joda.time.Instant + +@Entity(tableName = "traceLocations") +data class TraceLocationEntity( + + @PrimaryKey @ColumnInfo(name = "guid") val guid: String, + @ColumnInfo(name = "version") val version: Int, + @ColumnInfo(name = "type") val type: TraceLocation.Type, + @ColumnInfo(name = "description") val description: String, + @ColumnInfo(name = "address") val address: String, + @ColumnInfo(name = "startDate") val startDate: Instant?, + @ColumnInfo(name = "endDate") val endDate: Instant?, + @ColumnInfo(name = "defaultCheckInLengthInMinutes") val defaultCheckInLengthInMinutes: Int?, + @ColumnInfo(name = "signature") val signature: String + +) + +fun TraceLocation.toTraceLocationEntity(): TraceLocationEntity = + TraceLocationEntity( + guid = guid, + type = type, + description = description, + address = address, + startDate = startDate, + endDate = endDate, + defaultCheckInLengthInMinutes = defaultCheckInLengthInMinutes, + signature = signature, + version = version + ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..ab931ee3c3f22c898054b1e4391b34213c6a6eae --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/DefaultTraceLocationRepository.kt @@ -0,0 +1,56 @@ +package de.rki.coronawarnapp.eventregistration.storage.repo + +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import de.rki.coronawarnapp.eventregistration.events.toTraceLocations +import de.rki.coronawarnapp.eventregistration.storage.TraceLocationDatabase +import de.rki.coronawarnapp.eventregistration.storage.dao.TraceLocationDao +import de.rki.coronawarnapp.eventregistration.storage.entity.toTraceLocationEntity +import de.rki.coronawarnapp.util.coroutine.AppScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class DefaultTraceLocationRepository @Inject constructor( + traceLocationDatabaseFactory: TraceLocationDatabase.Factory, + @AppScope private val appScope: CoroutineScope +) : TraceLocationRepository { + + private val traceLocationDatabase: TraceLocationDatabase by lazy { + traceLocationDatabaseFactory.create() + } + + private val traceLocationDao: TraceLocationDao by lazy { + traceLocationDatabase.traceLocationDao() + } + + override val allTraceLocations: Flow<List<TraceLocation>> + get() = traceLocationDao.allEntries().map { it.toTraceLocations() } + + override fun addTraceLocation(event: TraceLocation) { + appScope.launch { + Timber.d("Add hosted event: $event") + val eventEntity = event.toTraceLocationEntity() + traceLocationDao.insert(eventEntity) + } + } + + override fun deleteTraceLocation(event: TraceLocation) { + appScope.launch { + Timber.d("Delete hosted event: $event") + val eventEntity = event.toTraceLocationEntity() + traceLocationDao.delete(eventEntity) + } + } + + override fun deleteAllTraceLocations() { + appScope.launch { + Timber.d("Delete all hosted events.") + traceLocationDao.deleteAll() + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..88576a9cfc9eca4563f129d2f2820c7ab1e9ebee --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/eventregistration/storage/repo/TraceLocationRepository.kt @@ -0,0 +1,15 @@ +package de.rki.coronawarnapp.eventregistration.storage.repo + +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import kotlinx.coroutines.flow.Flow + +interface TraceLocationRepository { + + val allTraceLocations: Flow<List<TraceLocation>> + + fun addTraceLocation(event: TraceLocation) + + fun deleteTraceLocation(event: TraceLocation) + + fun deleteAllTraceLocations() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt index d78661d2b7a2a4ac2d2152c02ade522a4c19440c..83729d06cdd034b7d6fe33ae0e7208543d37fbe3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt @@ -11,6 +11,7 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.datadonation.survey.SurveySettings import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysSettings import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository +import de.rki.coronawarnapp.eventregistration.storage.repo.TraceLocationRepository import de.rki.coronawarnapp.main.CWASettings import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.risk.storage.RiskLevelStorage @@ -45,6 +46,7 @@ class DataReset @Inject constructor( private val surveySettings: SurveySettings, private val analyticsSettings: AnalyticsSettings, private val analytics: Analytics, + private val traceLocationRepository: TraceLocationRepository, private val bugReportingSettings: BugReportingSettings ) { @@ -84,6 +86,8 @@ class DataReset @Inject constructor( bugReportingSettings.clear() + traceLocationRepository.deleteAllTraceLocations() + Timber.w("CWA LOCAL DATA DELETION COMPLETED.") } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocationKtTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocationKtTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7aefa48ae7e14c6714a058884d6b3c1747d9cb24 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/events/DefaultTraceLocationKtTest.kt @@ -0,0 +1,111 @@ +package de.rki.coronawarnapp.eventregistration.events + +import de.rki.coronawarnapp.eventregistration.storage.entity.TraceLocationEntity +import io.kotest.matchers.shouldBe +import org.joda.time.Instant +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +internal class DefaultTraceLocationKtTest : BaseTest() { + + @Test + fun `toTraceLocation() should map to correct object when providing all arguments`() { + TraceLocationEntity( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "signature" + ).toTraceLocation() shouldBe DefaultTraceLocation( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "signature" + ) + } + + @Test + fun `toTraceLocation() should map to correct object when providing only arguments that are required`() { + TraceLocationEntity( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = null, + signature = "signature" + ).toTraceLocation() shouldBe DefaultTraceLocation( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = null, + signature = "signature" + ) + } + + @Test + fun `toTraceLocations() should map a list of TraceLocationEntities correctly`() { + listOf( + TraceLocationEntity( + guid = "TestGuid1", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation1", + address = "TestTraceLocationAddress1", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "signature" + ), + TraceLocationEntity( + guid = "TestGuid2", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation2", + address = "TestTraceLocationAddress2", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = null, + signature = "signature" + ) + ).toTraceLocations() shouldBe listOf( + DefaultTraceLocation( + guid = "TestGuid1", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation1", + address = "TestTraceLocationAddress1", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "signature" + ), + DefaultTraceLocation( + guid = "TestGuid2", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation2", + address = "TestTraceLocationAddress2", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = null, + signature = "signature" + ) + ) + } +} \ No newline at end of file diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConvertersTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConvertersTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b11ff01460ae53187c857905f50fe49f7e239a0c --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationConvertersTest.kt @@ -0,0 +1,29 @@ +package de.rki.coronawarnapp.eventregistration.storage.entity + +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +internal class TraceLocationConvertersTest : BaseTest() { + + private val converter = TraceLocationConverters() + + @Test + fun `toTraceLocationType() should convert different integer values to correct TraceLocation Types`() { + with(converter) { + toTraceLocationType(0) shouldBe TraceLocation.Type.UNSPECIFIED + toTraceLocationType(1) shouldBe TraceLocation.Type.PERMANENT_OTHER + toTraceLocationType(2) shouldBe TraceLocation.Type.TEMPORARY_OTHER + } + } + + @Test + fun `fromTraceLocationType() should convert TraceLocation Types to correct integer values`() { + with(converter) { + fromTraceLocationType(TraceLocation.Type.UNSPECIFIED) shouldBe 0 + fromTraceLocationType(TraceLocation.Type.PERMANENT_OTHER) shouldBe 1 + fromTraceLocationType(TraceLocation.Type.TEMPORARY_OTHER) shouldBe 2 + } + } +} \ No newline at end of file diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntityTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntityTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..e796c1838e3def3d812cdad83e767645c88b016b --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/eventregistration/storage/entity/TraceLocationEntityTest.kt @@ -0,0 +1,61 @@ +package de.rki.coronawarnapp.eventregistration.storage.entity + +import de.rki.coronawarnapp.eventregistration.events.DefaultTraceLocation +import de.rki.coronawarnapp.eventregistration.events.TraceLocation +import io.kotest.matchers.shouldBe +import org.joda.time.Instant +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +internal class TraceLocationEntityTest : BaseTest() { + + @Test + fun `toTraceLocationEntity() should map to TraceLocationEntity correctly with all arguments`() { + DefaultTraceLocation( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "signature" + ).toTraceLocationEntity() shouldBe TraceLocationEntity( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.TEMPORARY_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = Instant.parse("2021-01-01T12:00:00.000Z"), + endDate = Instant.parse("2021-01-01T18:00:00.000Z"), + defaultCheckInLengthInMinutes = 15, + signature = "signature" + ) + } + + @Test + fun `toTraceLocationEntity() should map to TraceLocationEntity correctly with some arguments as null`() { + DefaultTraceLocation( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = null, + signature = "signature" + ).toTraceLocationEntity() shouldBe TraceLocationEntity( + guid = "TestGuid", + version = 1, + type = TraceLocation.Type.PERMANENT_OTHER, + description = "TestTraceLocation", + address = "TestTraceLocationAddress", + startDate = null, + endDate = null, + defaultCheckInLengthInMinutes = null, + signature = "signature" + ) + } +} \ No newline at end of file