From 063442b64db92af21c273e6e51e670d8e0364d0d Mon Sep 17 00:00:00 2001 From: Chilja Gossow <49635654+chiljamgossow@users.noreply.github.com> Date: Thu, 17 Dec 2020 17:25:15 +0100 Subject: [PATCH] Contact diary edit screens (EXPOSUREAPP-4161 EXPOSUREAPP-4162) (#1908) * Merge branch 'feature/4152-contact-diary' of https://github.com/corona-warn-app/cwa-app-android into feature/4152-contact-diary # Conflicts: # Corona-Warn-App/schemas/de.rki.coronawarnapp.contactdiary.storage.ContactDiaryDatabase/1.json # Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt * clean up DI * add recycler view * clean up * new texts * clean up * clean up * background, dialog * klint * klint * klint * review comments * review comments * review comments * Merge branch 'feature/4152-contact-diary' into feature/4161-4162-edit-person-location # Conflicts: # Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt --- .../ui/ContactDiaryTestFragmentViewModel.kt | 1 - Corona-Warn-App/src/main/AndroidManifest.xml | 2 +- .../entity/ContactDiaryLocationEntity.kt | 5 +- .../entity/ContactDiaryPersonEntity.kt | 5 +- .../repo/DefaultContactDiaryRepository.kt | 2 +- .../contactdiary/ui/ContactDiaryUIModule.kt | 6 + ...tDiaryLocationBottomSheetDialogFragment.kt | 49 +++++++- ...DiaryLocationBottomSheetDialogViewModel.kt | 23 +++- ...actDiaryPersonBottomSheetDialogFragment.kt | 49 +++++++- ...ctDiaryPersonBottomSheetDialogViewModel.kt | 23 +++- .../edit/ContactDiaryEditLocationsFragment.kt | 113 ++++++++++++++++++ .../ContactDiaryEditLocationsViewModel.kt | 50 ++++++++ .../ui/edit/ContactDiaryEditModule.kt | 32 +++++ .../edit/ContactDiaryEditPersonsFragment.kt | 113 ++++++++++++++++++ .../edit/ContactDiaryEditPersonsViewModel.kt | 50 ++++++++ .../ContactDiaryOnboardingFragment.kt | 2 - .../ui/overview/ContactDiaryOverviewMenu.kt | 30 ++++- .../ui/main/home/HomeFragment.kt | 1 - .../main/res/drawable/ic_baseline_delete.xml | 5 + .../main/res/drawable/ic_baseline_edit_24.xml | 10 ++ .../layout/contact_diary_edit_list_item.xml | 36 ++++++ .../contact_diary_edit_locations_fragment.xml | 93 ++++++++++++++ .../contact_diary_edit_persons_fragment.xml | 93 ++++++++++++++ .../contact_diary_homescreen_card_include.xml | 3 +- ...t_diary_location_bottom_sheet_fragment.xml | 28 ++++- .../contact_diary_location_list_fragment.xml | 3 +- ...act_diary_person_bottom_sheet_fragment.xml | 17 ++- .../contact_diary_person_list_fragment.xml | 3 +- .../include_contact_diary_privacy_card.xml | 6 +- .../navigation/contact_diary_nav_graph.xml | 48 +++++++- .../src/main/res/values/strings.xml | 2 +- .../ContactDiaryEditLocationsViewModelTest.kt | 93 ++++++++++++++ .../ContactDiaryEditPersonsViewModelTest.kt | 94 +++++++++++++++ 33 files changed, 1046 insertions(+), 44 deletions(-) create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModel.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditModule.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModel.kt create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_baseline_delete.xml create mode 100644 Corona-Warn-App/src/main/res/drawable/ic_baseline_edit_24.xml create mode 100644 Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml create mode 100644 Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml create mode 100644 Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModelTest.kt diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragmentViewModel.kt index 9bf0a9e8d..44df7a141 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/contactdiary/ui/ContactDiaryTestFragmentViewModel.kt @@ -14,7 +14,6 @@ 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.LocalDate -import java.lang.StringBuilder import kotlin.random.Random class ContactDiaryTestFragmentViewModel @AssistedInject constructor( diff --git a/Corona-Warn-App/src/main/AndroidManifest.xml b/Corona-Warn-App/src/main/AndroidManifest.xml index e450de6d4..f4096417f 100644 --- a/Corona-Warn-App/src/main/AndroidManifest.xml +++ b/Corona-Warn-App/src/main/AndroidManifest.xml @@ -84,4 +84,4 @@ </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt index c424bfdee..4eceee3de 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryLocationEntity.kt @@ -1,15 +1,18 @@ package de.rki.coronawarnapp.contactdiary.storage.entity +import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import kotlinx.android.parcel.Parcelize +@Parcelize @Entity(tableName = "locations") data class ContactDiaryLocationEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "locationId") override val locationId: Long = 0L, @ColumnInfo(name = "locationName") override var locationName: String -) : ContactDiaryLocation { +) : ContactDiaryLocation, Parcelable { override val stableId: Long get() = locationId } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt index 0ea761f2c..62a8c194e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/entity/ContactDiaryPersonEntity.kt @@ -1,15 +1,18 @@ package de.rki.coronawarnapp.contactdiary.storage.entity +import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson +import kotlinx.android.parcel.Parcelize +@Parcelize @Entity(tableName = "persons") data class ContactDiaryPersonEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "personId") override val personId: Long = 0L, @ColumnInfo(name = "fullName") override var fullName: String -) : ContactDiaryPerson { +) : ContactDiaryPerson, Parcelable { override val stableId: Long get() = personId } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt index 5339351e8..8c63ce180 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/storage/repo/DefaultContactDiaryRepository.kt @@ -45,7 +45,7 @@ class DefaultContactDiaryRepository @Inject constructor( Timber.d("Updating location $contactDiaryLocation") val contactDiaryContactDiaryLocationEntity = contactDiaryLocation.toContactDiaryLocationEntity() executeWhenIdNotDefault(contactDiaryContactDiaryLocationEntity.locationId) { - contactDiaryLocationDao.insert(contactDiaryContactDiaryLocationEntity) + contactDiaryLocationDao.update(contactDiaryContactDiaryLocationEntity) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryUIModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryUIModule.kt index 0ddeb1ca0..154d38473 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryUIModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/ContactDiaryUIModule.kt @@ -12,12 +12,18 @@ import de.rki.coronawarnapp.contactdiary.ui.day.tabs.location.ContactDiaryLocati import de.rki.coronawarnapp.contactdiary.ui.day.tabs.location.ContactDiaryLocationListModule import de.rki.coronawarnapp.contactdiary.ui.day.tabs.person.ContactDiaryPersonListFragment import de.rki.coronawarnapp.contactdiary.ui.day.tabs.person.ContactDiaryPersonListModule +import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditModule import de.rki.coronawarnapp.contactdiary.ui.onboarding.ContactDiaryOnboardingFragment import de.rki.coronawarnapp.contactdiary.ui.onboarding.ContactDiaryOnboardingFragmentModule import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragment import de.rki.coronawarnapp.contactdiary.ui.overview.ContactDiaryOverviewFragmentModule @Module + ( + includes = [ + ContactDiaryEditModule::class + ] +) abstract class ContactDiaryUIModule { @ContributesAndroidInjector(modules = [ContactDiaryDayModule::class]) abstract fun contactDiaryDayFragment(): ContactDiaryDayFragment diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogFragment.kt index f480e75dd..dcb237780 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogFragment.kt @@ -4,9 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo import androidx.core.widget.doAfterTextChanged +import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.ContactDiaryLocationBottomSheetFragmentBinding +import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider @@ -20,6 +24,8 @@ class ContactDiaryLocationBottomSheetDialogFragment : BottomSheetDialogFragment( @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory private val viewModel: ContactDiaryLocationBottomSheetDialogViewModel by cwaViewModels { viewModelFactory } + private val navArgs: ContactDiaryLocationBottomSheetDialogFragmentArgs by navArgs() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = ContactDiaryLocationBottomSheetFragmentBinding.inflate(inflater) return binding.root @@ -28,18 +34,38 @@ class ContactDiaryLocationBottomSheetDialogFragment : BottomSheetDialogFragment( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.contactDiaryLocationBottomSheetCloseButton.buttonIcon.setOnClickListener { - viewModel.closePressed() + val location = navArgs.selectedLocation + if (location != null) { + binding.contactDiaryLocationBottomSheetTextInputEditText.setText(location.locationName) + binding.contactDiaryLocationBottomSheetDeleteButton.visibility = View.VISIBLE + binding.contactDiaryLocationBottomSheetDeleteButton.setOnClickListener { + DialogHelper.showDialog(deleteLocationConfirmationDialog) + } + binding.contactDiaryLocationBottomSheetSaveButton.setOnClickListener { + viewModel.updateLocation(location) + } + } else { + binding.contactDiaryLocationBottomSheetDeleteButton.visibility = View.GONE + binding.contactDiaryLocationBottomSheetSaveButton.setOnClickListener { + viewModel.addLocation() + } } - binding.contactDiaryLocationBottomSheetSaveButton.setOnClickListener { - viewModel.saveLocation() + binding.contactDiaryLocationBottomSheetCloseButton.buttonIcon.setOnClickListener { + viewModel.closePressed() } binding.contactDiaryLocationBottomSheetTextInputEditText.doAfterTextChanged { viewModel.textChanged(it.toString()) } + binding.contactDiaryLocationBottomSheetTextInputEditText.setOnEditorActionListener { v, actionId, event -> + return@setOnEditorActionListener when (actionId) { + EditorInfo.IME_ACTION_DONE -> false + else -> true + } + } + viewModel.shouldClose.observe2(this) { dismiss() } @@ -54,4 +80,19 @@ class ContactDiaryLocationBottomSheetDialogFragment : BottomSheetDialogFragment( super.onDestroyView() _binding = null } + + private val deleteLocationConfirmationDialog by lazy { + DialogHelper.DialogInstance( + requireActivity(), + R.string.contact_diary_delete_location_title, + R.string.contact_diary_delete_locations_message, + R.string.contact_diary_delete_button_positive, + R.string.contact_diary_delete_button_negative, + positiveButtonFunction = { + navArgs.selectedLocation?.let { + viewModel.deleteLocation(it) + } + } + ) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogViewModel.kt index 42f8dc3be..b81f23ab1 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/location/ContactDiaryLocationBottomSheetDialogViewModel.kt @@ -3,12 +3,14 @@ package de.rki.coronawarnapp.contactdiary.ui.day.sheets.location import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationEntity import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository 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.MutableStateFlow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map class ContactDiaryLocationBottomSheetDialogViewModel @AssistedInject constructor( @@ -27,7 +29,7 @@ class ContactDiaryLocationBottomSheetDialogViewModel @AssistedInject constructor text.value = locationName } - fun saveLocation() = launch { + fun addLocation() = launch { contactDiaryRepository.addLocation( DefaultContactDiaryLocation( locationName = text.value.take(MAX_LOCATION_NAME_LENGTH) @@ -36,6 +38,25 @@ class ContactDiaryLocationBottomSheetDialogViewModel @AssistedInject constructor shouldClose.postValue(null) } + fun updateLocation(location: ContactDiaryLocationEntity) = launch { + contactDiaryRepository.updateLocation( + DefaultContactDiaryLocation( + location.locationId, + locationName = text.value.take(MAX_LOCATION_NAME_LENGTH) + ) + ) + shouldClose.postValue(null) + } + + fun deleteLocation(location: ContactDiaryLocationEntity) = launch { + contactDiaryRepository.locationVisits.firstOrNull()?.forEach { + if (it.contactDiaryLocation.locationId == location.locationId) + contactDiaryRepository.deleteLocationVisit(it) + } + contactDiaryRepository.deleteLocation(location) + shouldClose.postValue(null) + } + fun closePressed() { shouldClose.postValue(null) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogFragment.kt index a96fdb12f..576668beb 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogFragment.kt @@ -4,9 +4,13 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo import androidx.core.widget.doAfterTextChanged +import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.ContactDiaryPersonBottomSheetFragmentBinding +import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.observe2 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider @@ -20,6 +24,8 @@ class ContactDiaryPersonBottomSheetDialogFragment : BottomSheetDialogFragment(), @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory private val viewModel: ContactDiaryPersonBottomSheetDialogViewModel by cwaViewModels { viewModelFactory } + private val navArgs: ContactDiaryPersonBottomSheetDialogFragmentArgs by navArgs() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = ContactDiaryPersonBottomSheetFragmentBinding.inflate(inflater) return binding.root @@ -28,18 +34,38 @@ class ContactDiaryPersonBottomSheetDialogFragment : BottomSheetDialogFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.contactDiaryPersonBottomSheetCloseButton.buttonIcon.setOnClickListener { - viewModel.closePressed() + val person = navArgs.selectedPerson + if (person != null) { + binding.contactDiaryPersonBottomSheetTextInputEditText.setText(person.fullName) + binding.contactDiaryPersonBottomSheetDeleteButton.visibility = View.VISIBLE + binding.contactDiaryPersonBottomSheetDeleteButton.setOnClickListener { + DialogHelper.showDialog(deletePersonConfirmationDialog) + } + binding.contactDiaryPersonBottomSheetSaveButton.setOnClickListener { + viewModel.updatePerson(person) + } + } else { + binding.contactDiaryPersonBottomSheetDeleteButton.visibility = View.GONE + binding.contactDiaryPersonBottomSheetSaveButton.setOnClickListener { + viewModel.addPerson() + } } - binding.contactDiaryPersonBottomSheetSaveButton.setOnClickListener { - viewModel.savePerson() + binding.contactDiaryPersonBottomSheetCloseButton.buttonIcon.setOnClickListener { + viewModel.closePressed() } binding.contactDiaryPersonBottomSheetTextInputEditText.doAfterTextChanged { viewModel.textChanged(it.toString()) } + binding.contactDiaryPersonBottomSheetTextInputEditText.setOnEditorActionListener { v, actionId, event -> + return@setOnEditorActionListener when (actionId) { + EditorInfo.IME_ACTION_DONE -> false + else -> true + } + } + viewModel.shouldClose.observe2(this) { dismiss() } @@ -54,4 +80,19 @@ class ContactDiaryPersonBottomSheetDialogFragment : BottomSheetDialogFragment(), super.onDestroyView() _binding = null } + + private val deletePersonConfirmationDialog by lazy { + DialogHelper.DialogInstance( + requireActivity(), + R.string.contact_diary_delete_person_title, + R.string.contact_diary_delete_persons_message, + R.string.contact_diary_delete_button_positive, + R.string.contact_diary_delete_button_negative, + positiveButtonFunction = { + navArgs.selectedPerson?.let { + viewModel.deletePerson(it) + } + } + ) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogViewModel.kt index 8ea2fe868..4cf4ec00a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/day/sheets/person/ContactDiaryPersonBottomSheetDialogViewModel.kt @@ -3,12 +3,14 @@ package de.rki.coronawarnapp.contactdiary.ui.day.sheets.person import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.contactdiary.model.DefaultContactDiaryPerson +import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEntity import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository 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.MutableStateFlow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map class ContactDiaryPersonBottomSheetDialogViewModel @AssistedInject constructor( @@ -27,7 +29,7 @@ class ContactDiaryPersonBottomSheetDialogViewModel @AssistedInject constructor( text.value = locationName } - fun savePerson() = launch { + fun addPerson() = launch { contactDiaryRepository.addPerson( DefaultContactDiaryPerson( fullName = text.value.take(MAX_PERSON_NAME_LENGTH) @@ -36,6 +38,25 @@ class ContactDiaryPersonBottomSheetDialogViewModel @AssistedInject constructor( shouldClose.postValue(null) } + fun updatePerson(person: ContactDiaryPersonEntity) = launch { + contactDiaryRepository.updatePerson( + DefaultContactDiaryPerson( + person.personId, + fullName = text.value.take(MAX_PERSON_NAME_LENGTH) + ) + ) + shouldClose.postValue(null) + } + + fun deletePerson(person: ContactDiaryPersonEntity) = launch { + contactDiaryRepository.personEncounters.firstOrNull()?.forEach { + if (it.contactDiaryPerson.personId == person.personId) + contactDiaryRepository.deletePersonEncounter(it) + } + contactDiaryRepository.deletePerson(person) + shouldClose.postValue(null) + } + fun closePressed() { shouldClose.postValue(null) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt new file mode 100644 index 000000000..0360880c1 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsFragment.kt @@ -0,0 +1,113 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditLocationsViewModel.NavigationEvent.ShowDeletionConfirmationDialog +import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditLocationsViewModel.NavigationEvent.ShowLocationDetailSheet +import de.rki.coronawarnapp.databinding.ContactDiaryEditLocationsFragmentBinding +import de.rki.coronawarnapp.util.DialogHelper +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.popBackStack +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 ContactDiaryEditLocationsFragment : Fragment(R.layout.contact_diary_edit_locations_fragment), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel: ContactDiaryEditLocationsViewModel by cwaViewModels { viewModelFactory } + private val binding: ContactDiaryEditLocationsFragmentBinding by viewBindingLazy() + + private val locationList: MutableList<ContactDiaryLocation> = mutableListOf() + private val listAdapter = ListAdapter() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.viewModel = viewModel + + setupRecyclerView() + + binding.toolbar.setNavigationOnClickListener { + popBackStack() + } + + viewModel.locationsLiveData.observe2(this) { + locationList.clear() + locationList.addAll(it) + listAdapter.notifyDataSetChanged() + } + + viewModel.navigationEvent.observe2(this) { + + when (it) { + ShowDeletionConfirmationDialog -> DialogHelper.showDialog(deleteAllLocationsConfirmationDialog) + is ShowLocationDetailSheet -> { + doNavigate( + ContactDiaryEditLocationsFragmentDirections + .actionContactDiaryEditLocationsFragmentToContactDiaryLocationBottomSheetDialogFragment( + it.location + ) + ) + } + } + } + } + + override fun onResume() { + super.onResume() + binding.contentContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) + } + + private fun setupRecyclerView() { + binding.locationsRecyclerView.adapter = listAdapter + } + + private val deleteAllLocationsConfirmationDialog by lazy { + DialogHelper.DialogInstance( + requireActivity(), + R.string.contact_diary_delete_locations_title, + R.string.contact_diary_delete_locations_message, + R.string.contact_diary_delete_button_positive, + R.string.contact_diary_delete_button_negative, + positiveButtonFunction = { + viewModel.onDeleteAllConfirmedClick() + } + ) + } + + inner class ListAdapter : RecyclerView.Adapter<ListAdapter.ViewHolder>() { + + inner class ViewHolder(listItemView: View) : RecyclerView.ViewHolder(listItemView) { + val nameTextView = itemView.findViewById<TextView>(R.id.name) + val itemContainerView = itemView.findViewById<View>(R.id.item_container) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListAdapter.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.contact_diary_edit_list_item, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(viewHolder: ListAdapter.ViewHolder, position: Int) { + val location = locationList[position] + viewHolder.nameTextView.text = location.locationName + viewHolder.itemContainerView.setOnClickListener { + viewModel.onEditLocationClick(location) + } + } + + override fun getItemCount(): Int { + return locationList.size + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModel.kt new file mode 100644 index 000000000..85b236d21 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModel.kt @@ -0,0 +1,50 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import androidx.lifecycle.asLiveData +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationEntity +import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationEntity +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +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.map + +class ContactDiaryEditLocationsViewModel @AssistedInject constructor( + private val contactDiaryRepository: ContactDiaryRepository, + dispatcherProvider: DispatcherProvider +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + val locationsLiveData = contactDiaryRepository.locations.asLiveData() + + val navigationEvent = SingleLiveEvent<NavigationEvent>() + + val isButtonEnabled = contactDiaryRepository.locations.map { !it.isNullOrEmpty() }.asLiveData() + + val isListVisible = contactDiaryRepository.locations.map { !it.isNullOrEmpty() }.asLiveData() + + fun onDeleteAllLocationsClick() { + navigationEvent.postValue(NavigationEvent.ShowDeletionConfirmationDialog) + } + + fun onDeleteAllConfirmedClick() { + launch { + contactDiaryRepository.deleteAllLocationVisits() + contactDiaryRepository.deleteAllLocations() + } + } + + fun onEditLocationClick(location: ContactDiaryLocation) { + navigationEvent.postValue(NavigationEvent.ShowLocationDetailSheet(location.toContactDiaryLocationEntity())) + } + + @AssistedInject.Factory + interface Factory : SimpleCWAViewModelFactory<ContactDiaryEditLocationsViewModel> + + sealed class NavigationEvent { + object ShowDeletionConfirmationDialog : NavigationEvent() + data class ShowLocationDetailSheet(val location: ContactDiaryLocationEntity) : NavigationEvent() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditModule.kt new file mode 100644 index 000000000..ce0499513 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditModule.kt @@ -0,0 +1,32 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import dagger.Binds +import dagger.Module +import dagger.android.ContributesAndroidInjector +import dagger.multibindings.IntoMap +import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory +import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey + +@Module +abstract class ContactDiaryEditModule { + @Binds + @IntoMap + @CWAViewModelKey(ContactDiaryEditLocationsViewModel::class) + abstract fun contactDiaryEditLocationsFragment( + factory: ContactDiaryEditLocationsViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> + + @ContributesAndroidInjector + abstract fun contactDiaryEditLocationsFragment(): ContactDiaryEditLocationsFragment + + @Binds + @IntoMap + @CWAViewModelKey(ContactDiaryEditPersonsViewModel::class) + abstract fun contactDiaryEditPersonsFragment( + factory: ContactDiaryEditPersonsViewModel.Factory + ): CWAViewModelFactory<out CWAViewModel> + + @ContributesAndroidInjector + abstract fun contactDiaryEditPersonsFragment(): ContactDiaryEditPersonsFragment +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt new file mode 100644 index 000000000..46d2d3b24 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsFragment.kt @@ -0,0 +1,113 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson +import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditPersonsViewModel.NavigationEvent.ShowDeletionConfirmationDialog +import de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditPersonsViewModel.NavigationEvent.ShowPersonDetailSheet +import de.rki.coronawarnapp.databinding.ContactDiaryEditPersonsFragmentBinding +import de.rki.coronawarnapp.util.DialogHelper +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.popBackStack +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 ContactDiaryEditPersonsFragment : Fragment(R.layout.contact_diary_edit_persons_fragment), AutoInject { + + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory + private val viewModel: ContactDiaryEditPersonsViewModel by cwaViewModels { viewModelFactory } + private val binding: ContactDiaryEditPersonsFragmentBinding by viewBindingLazy() + + private val personList: MutableList<ContactDiaryPerson> = mutableListOf() + private val listAdapter = ListAdapter() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.viewModel = viewModel + + setupRecyclerView() + + binding.toolbar.setNavigationOnClickListener { + popBackStack() + } + + viewModel.personsLiveData.observe2(this) { + personList.clear() + personList.addAll(it) + listAdapter.notifyDataSetChanged() + } + + viewModel.navigationEvent.observe2(this) { + + when (it) { + ShowDeletionConfirmationDialog -> DialogHelper.showDialog(deleteAllPersonsConfirmationDialog) + is ShowPersonDetailSheet -> { + doNavigate( + ContactDiaryEditPersonsFragmentDirections + .actionContactDiaryEditPersonsFragmentToContactDiaryPersonBottomSheetDialogFragment( + it.person + ) + ) + } + } + } + } + + override fun onResume() { + super.onResume() + binding.contentContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) + } + + private fun setupRecyclerView() { + binding.personsRecyclerView.adapter = listAdapter + } + + private val deleteAllPersonsConfirmationDialog by lazy { + DialogHelper.DialogInstance( + requireActivity(), + R.string.contact_diary_delete_persons_title, + R.string.contact_diary_delete_persons_message, + R.string.contact_diary_delete_button_positive, + R.string.contact_diary_delete_button_negative, + positiveButtonFunction = { + viewModel.onDeleteAllConfirmedClick() + } + ) + } + + inner class ListAdapter : RecyclerView.Adapter<ListAdapter.ViewHolder>() { + + inner class ViewHolder(listItemView: View) : RecyclerView.ViewHolder(listItemView) { + val nameTextView = itemView.findViewById<TextView>(R.id.name) + val itemContainerView = itemView.findViewById<View>(R.id.item_container) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListAdapter.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.contact_diary_edit_list_item, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(viewHolder: ListAdapter.ViewHolder, position: Int) { + val person = personList[position] + viewHolder.nameTextView.text = person.fullName + viewHolder.itemContainerView.setOnClickListener { + viewModel.onEditPersonClick(person) + } + } + + override fun getItemCount(): Int { + return personList.size + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModel.kt new file mode 100644 index 000000000..582879125 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModel.kt @@ -0,0 +1,50 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import androidx.lifecycle.asLiveData +import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson +import de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEntity +import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryPersonEntity +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +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.map + +class ContactDiaryEditPersonsViewModel @AssistedInject constructor( + private val contactDiaryRepository: ContactDiaryRepository, + dispatcherProvider: DispatcherProvider +) : CWAViewModel(dispatcherProvider = dispatcherProvider) { + + val personsLiveData = contactDiaryRepository.people.asLiveData() + + val navigationEvent = SingleLiveEvent<NavigationEvent>() + + val isButtonEnabled = contactDiaryRepository.people.map { !it.isNullOrEmpty() }.asLiveData() + + val isListVisible = contactDiaryRepository.people.map { !it.isNullOrEmpty() }.asLiveData() + + fun onDeleteAllPersonsClick() { + navigationEvent.postValue(NavigationEvent.ShowDeletionConfirmationDialog) + } + + fun onDeleteAllConfirmedClick() { + launch { + contactDiaryRepository.deleteAllPersonEncounters() + contactDiaryRepository.deleteAllPeople() + } + } + + fun onEditPersonClick(person: ContactDiaryPerson) { + navigationEvent.postValue(NavigationEvent.ShowPersonDetailSheet(person.toContactDiaryPersonEntity())) + } + + @AssistedInject.Factory + interface Factory : SimpleCWAViewModelFactory<ContactDiaryEditPersonsViewModel> + + sealed class NavigationEvent { + object ShowDeletionConfirmationDialog : NavigationEvent() + data class ShowPersonDetailSheet(val person: ContactDiaryPersonEntity) : NavigationEvent() + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt index a144e4f56..1dd44a3ee 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/onboarding/ContactDiaryOnboardingFragment.kt @@ -56,9 +56,7 @@ class ContactDiaryOnboardingFragment : Fragment(R.layout.contact_diary_onboardin } ContactDiaryOnboardingNavigationEvents.NavigateToOverviewFragment -> { - onboardingComplete() - doNavigate( ContactDiaryOnboardingFragmentDirections .actionContactDiaryOnboardingFragmentToContactDiaryOverviewFragment() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt index 07b6fe6ae..e2f3d0b2b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/contactdiary/ui/overview/ContactDiaryOverviewMenu.kt @@ -3,7 +3,10 @@ package de.rki.coronawarnapp.contactdiary.ui.overview import android.content.Context import android.view.View import android.widget.PopupMenu +import androidx.navigation.NavController +import androidx.navigation.fragment.findNavController import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.ui.doNavigate import de.rki.coronawarnapp.util.viewmodel.cwaViewModels import javax.inject.Inject @@ -12,21 +15,36 @@ class ContactDiaryOverviewMenu @Inject constructor( private val contactDiaryOverviewFragment: ContactDiaryOverviewFragment ) { private val context: Context = contactDiaryOverviewFragment.requireContext() + private val navController: NavController + get() = contactDiaryOverviewFragment.findNavController() private val vm: ContactDiaryOverviewViewModel by contactDiaryOverviewFragment.cwaViewModels { contactDiaryOverviewFragment.viewModelFactory } - // TODO(Move this to ContactDiaryOverviewFragment) fun showMenuFor(view: View) = PopupMenu(context, view).apply { inflate(R.menu.menu_contact_diary_overview) setOnMenuItemClickListener { when (it.itemId) { - R.id.menu_contact_diary_information -> { true } - R.id.menu_contact_diary_export_entries -> { - vm.onExportPress(context) + R.id.menu_contact_diary_information -> { + navController.doNavigate( + ContactDiaryOverviewFragmentDirections + .actionContactDiaryOverviewFragmentToContactDiaryOnboardingFragment() + ) + true + } + R.id.menu_contact_diary_export_entries -> { vm.onExportPress(context) + true } + R.id.menu_contact_diary_edit_persons -> { + navController.doNavigate( + ContactDiaryOverviewFragmentDirections + .actionContactDiaryOverviewFragmentToContactDiaryEditPersonsFragment()) + true + } + R.id.menu_contact_diary_edit_locations -> { + navController.doNavigate( + ContactDiaryOverviewFragmentDirections + .actionContactDiaryOverviewFragmentToContactDiaryEditLocationsFragment()) true } - R.id.menu_contact_diary_edit_persons -> { true } - R.id.menu_contact_diary_edit_locations -> { true } else -> contactDiaryOverviewFragment.onOptionsItemSelected(it) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt index 952f98013..731d5515c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragment.kt @@ -19,7 +19,6 @@ 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 kotlinx.android.synthetic.main.include_submission_status_card_ready.* import javax.inject.Inject /** diff --git a/Corona-Warn-App/src/main/res/drawable/ic_baseline_delete.xml b/Corona-Warn-App/src/main/res/drawable/ic_baseline_delete.xml new file mode 100644 index 000000000..6df11c698 --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_baseline_delete.xml @@ -0,0 +1,5 @@ +<vector android:height="18dp" android:tint="?attr/colorControlNormal" + android:viewportHeight="24" android:viewportWidth="24" + android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> +</vector> diff --git a/Corona-Warn-App/src/main/res/drawable/ic_baseline_edit_24.xml b/Corona-Warn-App/src/main/res/drawable/ic_baseline_edit_24.xml new file mode 100644 index 000000000..2844bafeb --- /dev/null +++ b/Corona-Warn-App/src/main/res/drawable/ic_baseline_edit_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> +</vector> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml new file mode 100644 index 000000000..73cebe4ab --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_list_item.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/item_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="4dp" + style="@style/cardGrey" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <TextView + style="@style/subtitle" + android:id="@+id/name" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:maxLines="1" + android:ellipsize="end" + android:padding="@dimen/spacing_small" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/edit_icon" + tools:text="Julia Maria" + /> + + <ImageView + android:id="@+id/edit_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_baseline_edit_24" + android:padding="@dimen/spacing_small" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + android:contentDescription="@string/contact_diary_edit_icon_content_description"/> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml new file mode 100644 index 000000000..745b56b84 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_locations_fragment.xml @@ -0,0 +1,93 @@ +<?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"> + + <data> + + <variable + name="viewModel" + type="de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditLocationsViewModel" /> + + </data> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/content_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + app:title="@string/contact_diary_edit_locations_title" + app:navigationIcon="@drawable/ic_close" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + /> + + <androidx.constraintlayout.widget.Group + android:id="@+id/contact_diary_location_list_no_items_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:gone="@{viewModel.isListVisible()}" + app:constraint_referenced_ids="contact_diary_location_list_no_items_image,contact_diary_location_list_no_items_title" /> + + <ImageView + android:id="@+id/contact_diary_location_list_no_items_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toTopOf="@id/contact_diary_location_list_no_items_title" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_illustration_no_locations" + android:contentDescription="@string/contact_diary_edit_locations_image_content_description"/> + + <TextView + android:id="@+id/contact_diary_location_list_no_items_title" + style="@style/subtitleMedium" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/spacing_huge" + android:layout_marginTop="@dimen/spacing_normal" + android:text="@string/contact_diary_location_list_no_items_title" + android:textAlignment="center" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/contact_diary_location_list_no_items_image" + app:layout_constraintBottom_toBottomOf="parent"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/locations_recycler_view" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="@dimen/spacing_small" + android:layout_marginEnd="@dimen/spacing_small" + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_normal" + android:importantForAccessibility="no" + android:scrollbars="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintBottom_toTopOf="@id/delete_button"/> + + <Button + android:id="@+id/delete_button" + style="@style/buttonReset" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:text="@string/contact_diary_remove_all_button" + android:textAllCaps="true" + android:onClick="@{ () -> viewModel.onDeleteAllLocationsClick()}" + android:enabled="@{viewModel.isButtonEnabled()}" + android:layout_margin="@dimen/spacing_normal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml new file mode 100644 index 000000000..3b284bff7 --- /dev/null +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_edit_persons_fragment.xml @@ -0,0 +1,93 @@ +<?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"> + + <data> + + <variable + name="viewModel" + type="de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditPersonsViewModel" /> + + </data> + + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/content_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:title="@string/contact_diary_edit_persons_title" + app:navigationIcon="@drawable/ic_close" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + /> + + <androidx.constraintlayout.widget.Group + android:id="@+id/contact_diary_person_list_no_items_group" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:gone="@{viewModel.isListVisible()}" + app:constraint_referenced_ids="contact_diary_person_list_no_items_image,contact_diary_person_list_no_items_title" /> + + <ImageView + android:id="@+id/contact_diary_person_list_no_items_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintBottom_toTopOf="@id/contact_diary_person_list_no_items_title" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" + app:srcCompat="@drawable/ic_illustration_no_people" + android:contentDescription="@string/contact_diary_edit_persons_image_content_description"/> + + <TextView + android:id="@+id/contact_diary_person_list_no_items_title" + style="@style/subtitleMedium" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:layout_marginHorizontal="@dimen/spacing_huge" + android:layout_marginTop="@dimen/spacing_normal" + android:text="@string/contact_diary_person_list_no_items_title" + android:textAlignment="center" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/contact_diary_person_list_no_items_image" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/persons_recycler_view" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="@dimen/spacing_small" + android:layout_marginEnd="@dimen/spacing_small" + android:layout_marginTop="@dimen/spacing_normal" + android:layout_marginBottom="@dimen/spacing_normal" + android:importantForAccessibility="no" + android:scrollbars="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/toolbar" + app:layout_constraintBottom_toTopOf="@id/delete_button"/> + + <Button + android:id="@+id/delete_button" + style="@style/buttonReset" + android:layout_width="@dimen/match_constraint" + android:layout_height="wrap_content" + android:text="@string/contact_diary_remove_all_button" + android:textAllCaps="true" + android:onClick="@{ () -> viewModel.onDeleteAllPersonsClick()}" + android:enabled="@{viewModel.isButtonEnabled()}" + android:layout_margin="@dimen/spacing_normal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> +</layout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_homescreen_card_include.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_homescreen_card_include.xml index 858c66976..87bd64046 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_homescreen_card_include.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_homescreen_card_include.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<layout xmlns:tools="http://schemas.android.com/tools" - xmlns:android="http://schemas.android.com/apk/res/android" +<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.constraintlayout.widget.ConstraintLayout diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_location_bottom_sheet_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_location_bottom_sheet_fragment.xml index 8a2c25860..9b05d3a6b 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_location_bottom_sheet_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_location_bottom_sheet_fragment.xml @@ -20,13 +20,27 @@ <TextView android:id="@+id/contact_diary_location_bottom_sheet_title" style="@style/headline6" - android:layout_width="wrap_content" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacing_normal" android:text="@string/contact_diary_location_bottom_sheet_title" - app:layout_constraintBottom_toBottomOf="@+id/contact_diary_location_bottom_sheet_close_button" - app:layout_constraintStart_toEndOf="@+id/contact_diary_location_bottom_sheet_close_button" - app:layout_constraintTop_toTopOf="@+id/contact_diary_location_bottom_sheet_close_button" /> + app:layout_constraintBottom_toBottomOf="@id/contact_diary_location_bottom_sheet_close_button" + app:layout_constraintStart_toEndOf="@id/contact_diary_location_bottom_sheet_close_button" + app:layout_constraintTop_toTopOf="@id/contact_diary_location_bottom_sheet_close_button" + app:layout_constraintEnd_toStartOf="@id/contact_diary_location_bottom_sheet_delete_button"/> + + <ImageView + android:id="@+id/contact_diary_location_bottom_sheet_delete_button" + android:layout_width="@dimen/circle_icon_big" + android:layout_height="@dimen/circle_icon_big" + android:layout_marginEnd="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_small" + android:padding="@dimen/circle_icon_big_padding" + android:src="@drawable/ic_baseline_delete" + android:contentDescription="@string/contact_diary_delete_icon_content_description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + style="@style/buttonIcon" /> <com.google.android.material.textfield.TextInputLayout android:id="@+id/contact_diary_location_bottom_sheet_text_input_layout" @@ -39,12 +53,14 @@ app:counterMaxLength="250" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/contact_diary_location_bottom_sheet_close_button"> + app:layout_constraintTop_toBottomOf="@id/contact_diary_location_bottom_sheet_close_button"> <com.google.android.material.textfield.TextInputEditText android:id="@+id/contact_diary_location_bottom_sheet_text_input_edit_text" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:inputType="text" + android:imeOptions="actionDone"/> </com.google.android.material.textfield.TextInputLayout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_fragment.xml index 9d491caba..402383d8c 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_location_list_fragment.xml @@ -32,7 +32,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" - app:srcCompat="@drawable/ic_illustration_no_locations" /> + app:srcCompat="@drawable/ic_illustration_no_locations" + android:contentDescription="@string/contact_diary_edit_locations_image_content_description"/> <TextView android:id="@+id/contact_diary_location_list_no_items_title" diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_person_bottom_sheet_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_person_bottom_sheet_fragment.xml index 8cfcbf0ff..146896254 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_person_bottom_sheet_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_person_bottom_sheet_fragment.xml @@ -28,6 +28,19 @@ app:layout_constraintStart_toEndOf="@+id/contact_diary_person_bottom_sheet_close_button" app:layout_constraintTop_toTopOf="@+id/contact_diary_person_bottom_sheet_close_button" /> + <ImageView + android:id="@+id/contact_diary_person_bottom_sheet_delete_button" + android:layout_width="@dimen/circle_icon_big" + android:layout_height="@dimen/circle_icon_big" + android:layout_marginEnd="@dimen/spacing_normal" + android:layout_marginTop="@dimen/spacing_small" + android:padding="@dimen/circle_icon_big_padding" + android:src="@drawable/ic_baseline_delete" + android:contentDescription="@string/contact_diary_delete_icon_content_description" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + style="@style/buttonIcon" /> + <com.google.android.material.textfield.TextInputLayout android:id="@+id/contact_diary_person_bottom_sheet_text_input_layout" android:layout_width="@dimen/match_constraint" @@ -44,7 +57,9 @@ <com.google.android.material.textfield.TextInputEditText android:id="@+id/contact_diary_person_bottom_sheet_text_input_edit_text" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:inputType="text" + android:imeOptions="actionDone"/> </com.google.android.material.textfield.TextInputLayout> diff --git a/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_fragment.xml b/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_fragment.xml index 47f1a6197..a5a7ce3bd 100644 --- a/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_fragment.xml +++ b/Corona-Warn-App/src/main/res/layout/contact_diary_person_list_fragment.xml @@ -32,7 +32,8 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" - app:srcCompat="@drawable/ic_illustration_no_people" /> + app:srcCompat="@drawable/ic_illustration_no_people" + android:contentDescription="@string/contact_diary_edit_persons_image_content_description"/> <TextView android:id="@+id/contact_diary_person_list_no_items_title" diff --git a/Corona-Warn-App/src/main/res/layout/include_contact_diary_privacy_card.xml b/Corona-Warn-App/src/main/res/layout/include_contact_diary_privacy_card.xml index be909a096..fa066d4d3 100644 --- a/Corona-Warn-App/src/main/res/layout/include_contact_diary_privacy_card.xml +++ b/Corona-Warn-App/src/main/res/layout/include_contact_diary_privacy_card.xml @@ -1,8 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<layout xmlns:tools2="http://schemas.android.com/tools" - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/apk/res-auto"> +<layout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/contact_diary_privacy_card" diff --git a/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml index c5ceb7671..cbea34864 100644 --- a/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/contact_diary_nav_graph.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/contact_diary_nav_graph" - app:startDestination="@id/contactDiaryOnboardingFragment"> + app:startDestination="@id/contactDiaryOverviewFragment"> <fragment android:id="@+id/contactDiaryDayFragment" android:name="de.rki.coronawarnapp.contactdiary.ui.day.ContactDiaryDayFragment" @@ -38,11 +38,23 @@ <dialog android:id="@+id/contactDiaryPersonBottomSheetDialogFragment" android:name="de.rki.coronawarnapp.contactdiary.ui.day.sheets.person.ContactDiaryPersonBottomSheetDialogFragment" - android:label="ContactDiaryPersonBottomSheetDialogFragment" /> + android:label="ContactDiaryPersonBottomSheetDialogFragment" > + <argument + android:name="selectedPerson" + app:nullable="true" + android:defaultValue="@null" + app:argType="de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryPersonEntity" /> + </dialog> <dialog android:id="@+id/contactDiaryLocationBottomSheetDialogFragment" android:name="de.rki.coronawarnapp.contactdiary.ui.day.sheets.location.ContactDiaryLocationBottomSheetDialogFragment" - android:label="ContactDiaryLocationBottomSheetDialogFragment" /> + android:label="ContactDiaryLocationBottomSheetDialogFragment" > + <argument + android:name="selectedLocation" + app:nullable="true" + android:defaultValue="@null" + app:argType="de.rki.coronawarnapp.contactdiary.storage.entity.ContactDiaryLocationEntity" /> + </dialog> <fragment android:id="@+id/contactDiaryOnboardingFragment" android:name="de.rki.coronawarnapp.contactdiary.ui.onboarding.ContactDiaryOnboardingFragment" @@ -52,7 +64,9 @@ app:destination="@id/contactDiaryInformationPrivacyFragment" /> <action android:id="@+id/action_contactDiaryOnboardingFragment_to_contactDiaryOverviewFragment" - app:destination="@id/contactDiaryOverviewFragment" /> + app:destination="@id/contactDiaryOverviewFragment" + app:popUpTo="@id/contact_diary_nav_graph" + app:popUpToInclusive="true"/> </fragment> <fragment android:id="@+id/contactDiaryInformationPrivacyFragment" @@ -66,5 +80,31 @@ <action android:id="@+id/action_contactDiaryOverviewFragment_to_contactDiaryDayFragment" app:destination="@id/contactDiaryDayFragment" /> + <action + android:id="@+id/action_contactDiaryOverviewFragment_to_contactDiaryEditLocationsFragment" + app:destination="@id/contactDiaryEditLocationsFragment" /> + <action + android:id="@+id/action_contactDiaryOverviewFragment_to_contactDiaryEditPersonsFragment" + app:destination="@id/contactDiaryEditPersonsFragment" /> + <action + android:id="@+id/action_contactDiaryOverviewFragment_to_contactDiaryOnboardingFragment" + app:destination="@id/contactDiaryOnboardingFragment" /> + </fragment> + <fragment + android:id="@+id/contactDiaryEditLocationsFragment" + android:name="de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditLocationsFragment" + android:label="ContactDiaryEditLocationsFragment"> + <action + android:id="@+id/action_contactDiaryEditLocationsFragment_to_contactDiaryLocationBottomSheetDialogFragment" + app:destination="@id/contactDiaryLocationBottomSheetDialogFragment" /> + </fragment> + <fragment + android:id="@+id/contactDiaryEditPersonsFragment" + android:name="de.rki.coronawarnapp.contactdiary.ui.edit.ContactDiaryEditPersonsFragment" + android:label="ContactDiaryEditPersonsFragment" > + <action + android:id="@+id/action_contactDiaryEditPersonsFragment_to_contactDiaryPersonBottomSheetDialogFragment" + app:destination="@id/contactDiaryPersonBottomSheetDialogFragment" /> </fragment> + </navigation> diff --git a/Corona-Warn-App/src/main/res/values/strings.xml b/Corona-Warn-App/src/main/res/values/strings.xml index 3f28d165d..0e80c1c78 100644 --- a/Corona-Warn-App/src/main/res/values/strings.xml +++ b/Corona-Warn-App/src/main/res/values/strings.xml @@ -1473,4 +1473,4 @@ <!-- XBUT: Abort button for test result positive no consent screen --> <string name="submission_test_result_positive_no_consent_button_abort">"Cancel"</string> -</resources> \ No newline at end of file +</resources> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt new file mode 100644 index 000000000..ea6e55c46 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditLocationsViewModelTest.kt @@ -0,0 +1,93 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryLocationEntity +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension + +@ExtendWith(InstantExecutorExtension::class) +class ContactDiaryEditLocationsViewModelTest { + + lateinit var viewModel: ContactDiaryEditLocationsViewModel + @MockK lateinit var contactDiaryRepository: ContactDiaryRepository + private val location = object : ContactDiaryLocation { + override val locationId = 1L + override var locationName = "Supermarket" + override val stableId = 1L + } + private val locationList = listOf(location) + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + } + + @Test + fun testOnDeleteAllLocationsClick() { + every { contactDiaryRepository.locations } returns MutableStateFlow(locationList) + viewModel = ContactDiaryEditLocationsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.navigationEvent.observeForever { } + viewModel.onDeleteAllLocationsClick() + viewModel.navigationEvent.value shouldBe ContactDiaryEditLocationsViewModel.NavigationEvent.ShowDeletionConfirmationDialog + } + + @Test + fun testOnDeleteAllConfirmedClick() { + coEvery { contactDiaryRepository.deleteAllLocationVisits() } just Runs + coEvery { contactDiaryRepository.deleteAllLocations() } just Runs + every { contactDiaryRepository.locations } returns MutableStateFlow(locationList) + viewModel = ContactDiaryEditLocationsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.onDeleteAllConfirmedClick() + coVerify(exactly = 1) { + contactDiaryRepository.deleteAllLocationVisits() + contactDiaryRepository.deleteAllLocations() + } + } + + @Test + fun testOnEditLocationClick() { + every { contactDiaryRepository.locations } returns MutableStateFlow(locationList) + viewModel = ContactDiaryEditLocationsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.navigationEvent.observeForever { } + viewModel.onEditLocationClick(location) + viewModel.navigationEvent.value shouldBe + ContactDiaryEditLocationsViewModel.NavigationEvent.ShowLocationDetailSheet(location.toContactDiaryLocationEntity()) + } + + @Test + fun testIsButtonEnabled() { + every { contactDiaryRepository.locations } returns MutableStateFlow(locationList) + viewModel = ContactDiaryEditLocationsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.isButtonEnabled.observeForever { } + viewModel.isButtonEnabled.value shouldBe true + } + + @Test + fun testIsButtonNotEnabledWhenListIsEmpty() { + every { contactDiaryRepository.locations } returns MutableStateFlow(emptyList()) + viewModel = ContactDiaryEditLocationsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.isButtonEnabled.observeForever { } + viewModel.isButtonEnabled.value shouldBe false + } + + @Test + fun testLocations() { + every { contactDiaryRepository.locations } returns MutableStateFlow(locationList) + viewModel = ContactDiaryEditLocationsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.locationsLiveData.observeForever { } + viewModel.locationsLiveData.value shouldBe locationList + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModelTest.kt new file mode 100644 index 000000000..183722bb6 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/contactdiary/ui/edit/ContactDiaryEditPersonsViewModelTest.kt @@ -0,0 +1,94 @@ +package de.rki.coronawarnapp.contactdiary.ui.edit + +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson +import de.rki.coronawarnapp.contactdiary.storage.entity.toContactDiaryPersonEntity +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import kotlinx.coroutines.flow.MutableStateFlow +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension + +@ExtendWith(InstantExecutorExtension::class) +class ContactDiaryEditPersonsViewModelTest { + + lateinit var viewModel: ContactDiaryEditPersonsViewModel + @MockK lateinit var contactDiaryRepository: ContactDiaryRepository + private val person = object : ContactDiaryPerson { + override val personId = 1L + override var fullName = "Julia" + override val stableId = 1L + } + + private val personList = listOf(person) + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + } + + @Test + fun testOnDeleteAllLocationsClick() { + every { contactDiaryRepository.people } returns MutableStateFlow(personList) + viewModel = ContactDiaryEditPersonsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.navigationEvent.observeForever { } + viewModel.onDeleteAllPersonsClick() + viewModel.navigationEvent.value shouldBe ContactDiaryEditPersonsViewModel.NavigationEvent.ShowDeletionConfirmationDialog + } + + @Test + fun testOnDeleteAllConfirmedClick() { + coEvery { contactDiaryRepository.deleteAllPeople() } just Runs + coEvery { contactDiaryRepository.deleteAllPersonEncounters() } just Runs + every { contactDiaryRepository.people } returns MutableStateFlow(personList) + viewModel = ContactDiaryEditPersonsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.onDeleteAllConfirmedClick() + coVerify(exactly = 1) { + contactDiaryRepository.deleteAllPeople() + contactDiaryRepository.deleteAllPersonEncounters() + } + } + + @Test + fun testOnEditLocationClick() { + every { contactDiaryRepository.people } returns MutableStateFlow(personList) + viewModel = ContactDiaryEditPersonsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.navigationEvent.observeForever { } + viewModel.onEditPersonClick(person) + viewModel.navigationEvent.value shouldBe + ContactDiaryEditPersonsViewModel.NavigationEvent.ShowPersonDetailSheet(person.toContactDiaryPersonEntity()) + } + + @Test + fun testIsButtonEnabled() { + every { contactDiaryRepository.people } returns MutableStateFlow(personList) + viewModel = ContactDiaryEditPersonsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.isButtonEnabled.observeForever { } + viewModel.isButtonEnabled.value shouldBe true + } + + @Test + fun testIsButtonNotEnabledWhenListIsEmpty() { + every { contactDiaryRepository.people } returns MutableStateFlow(emptyList()) + viewModel = ContactDiaryEditPersonsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.isButtonEnabled.observeForever { } + viewModel.isButtonEnabled.value shouldBe false + } + + @Test + fun testLocations() { + every { contactDiaryRepository.people } returns MutableStateFlow(personList) + viewModel = ContactDiaryEditPersonsViewModel(contactDiaryRepository, TestDispatcherProvider) + viewModel.personsLiveData.observeForever { } + viewModel.personsLiveData.value shouldBe personList + } +} -- GitLab