diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt index 9e2b83d4b..5af9e79cb 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/FuzzyMatchingModule.kt @@ -29,11 +29,15 @@ val fuzzyMatchingModule = module { } factory { + fun getFormattedString(stringResId: Int, template: String): String { + return androidContext().getString(stringResId, template) + } SelectionDataUtilImpl( get(), get(), get(), androidContext().resources::getQuantityString, + ::getFormattedString, androidContext()::getString ) } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt index 3527990b8..8fb5207fa 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionFragment.kt @@ -56,19 +56,21 @@ class HolderNameSelectionFragment : Fragment(R.layout.fragment_holder_name_selec viewModel.itemsLiveData.observe(viewLifecycleOwner) { setItems(it, binding) } - binding.bottom.setButtonClick { - val selectedName = viewModel.selectedName() - if (selectedName == null) { + + viewModel.nameSelectionError.observe(viewLifecycleOwner) { noSelectionError -> + if (noSelectionError) { viewModel.nothingSelectedError() binding.bottom.showError() - } else { - viewModel.storeSelection { - navigateSafety( - actionSavedEventsSyncGreenCards( - selectedName = selectedName - ) + } + } + + binding.bottom.setButtonClick { + viewModel.storeSelection { selectedName -> + navigateSafety( + actionSavedEventsSyncGreenCards( + selectedName = selectedName ) - } + ) } } } @@ -184,7 +186,7 @@ class HolderNameSelectionFragment : Fragment(R.layout.fragment_holder_name_selec ) ) ) - }) { index -> + }) { binding.bottom.hideError() viewModel.onItemSelected(item.name) } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt index 755a3ed27..d4244241a 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewAdapterItem.kt @@ -17,7 +17,7 @@ import nl.rijksoverheid.ctr.holder.databinding.ItemHolderNameSelectionViewBindin class HolderNameSelectionViewAdapterItem( private val item: HolderNameSelectionItem.ListItem, private val onDetailsButtonClicked: () -> Unit, - private val onSelected: (Int) -> Unit + private val onSelected: () -> Unit ) : BindableItem( R.layout.item_holder_name_selection_view.toLong() ) { @@ -27,12 +27,14 @@ class HolderNameSelectionViewAdapterItem( if (item.nothingSelectedError) { viewBinding.radioButton.buttonTintList = ColorStateList.valueOf(viewBinding.radioButton.context.getColor(R.color.error)) + } else { + viewBinding.radioButton.isUseMaterialThemeColors = true } viewBinding.nameTextView.text = item.name viewBinding.eventsTextView.text = item.events viewBinding.removedEventTextView.isVisible = item.willBeRemoved viewBinding.root.setOnClickListener { - onSelected(position) + onSelected() } viewBinding.detailsButton.contentDescription = "${item.name} ${viewBinding.detailsButton.text}" viewBinding.detailsButton.setOnClickListener { diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt index a19c29003..263c915d7 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModel.kt @@ -21,10 +21,10 @@ abstract class HolderNameSelectionViewModel( greenCardUtil: GreenCardUtil ) : FuzzyMatchingBaseViewModel(holderDatabase, greenCardUtil) { val itemsLiveData: LiveData> = MutableLiveData() + val nameSelectionError: LiveData = MutableLiveData() abstract fun onItemSelected(selectedName: String) - abstract fun selectedName(): String? - abstract fun storeSelection(onStored: () -> Unit) + abstract fun storeSelection(onStored: (String) -> Unit) abstract fun nothingSelectedError() } @@ -43,24 +43,30 @@ class HolderNameSelectionViewModelImpl( } override fun onItemSelected(selectedName: String) { + (nameSelectionError as MutableLiveData).value = false updateItems(selectedName) } - override fun selectedName(): String? { + private fun selectedName(): String? { return itemsLiveData.value ?.filterIsInstance() ?.find { it.isSelected } ?.name } - override fun storeSelection(onStored: () -> Unit) { - val items = itemsLiveData.value?.filterIsInstance() ?: return - val itemSelected = items.find { it.isSelected } - if (itemSelected != null) { - viewModelScope.launch { - matchedEventsUseCase.selected(items.indexOf(itemSelected), matchingBlobIds) - onStored() + override fun storeSelection(onStored: (String) -> Unit) { + val selectedName = selectedName() + if (selectedName != null) { + val items = itemsLiveData.value?.filterIsInstance() ?: return + val itemSelected = items.find { it.isSelected } + if (itemSelected != null) { + viewModelScope.launch { + matchedEventsUseCase.selected(items.indexOf(itemSelected), matchingBlobIds) + onStored(selectedName) + } } + } else { + (nameSelectionError as MutableLiveData).value = true } } diff --git a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt index da71f6e36..abcde47d6 100644 --- a/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt +++ b/holder/src/main/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtil.kt @@ -10,6 +10,7 @@ import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_ import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_TEST import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_VACCINATION import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEvent.Companion.TYPE_VACCINATION_ASSESSMENT +import nl.rijksoverheid.ctr.holder.get_events.models.RemoteEventVaccination import nl.rijksoverheid.ctr.holder.your_events.utils.RemoteEventStringUtil import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil @@ -33,6 +34,7 @@ class SelectionDataUtilImpl( private val yourEventsFragmentUtil: YourEventsFragmentUtil, private val remoteEventStringUtil: RemoteEventStringUtil, private val getQuantityString: (Int, Int) -> String, + private val getFormattedString: (Int, String) -> String, private val getString: (Int) -> String ) : SelectionDataUtil { @@ -94,7 +96,19 @@ class SelectionDataUtilImpl( val data = remoteEvents.map { event -> val eventDate = event.getDate() SelectionDetailData( - type = remoteEventStringUtil.remoteEventTitle(event.javaClass), + type = "${remoteEventStringUtil.remoteEventTitle(event.javaClass)}${ + if (providerIdentifier.startsWith("dcc") && event is RemoteEventVaccination) { + val dose = event.vaccination?.doseNumber + val totalDoses = event.vaccination?.totalDoses + if (dose != null && totalDoses != null) { + " ${getFormattedString(R.string.your_vaccination_explanation_dose, "$dose/$totalDoses")}" + } else { + "" + } + } else { + "" + } + }", providerIdentifiers = listOf( yourEventsFragmentUtil.getProviderName( configProviders, diff --git a/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModelImplTest.kt b/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModelImplTest.kt index 92fd62ee1..3c113d198 100644 --- a/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModelImplTest.kt +++ b/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/HolderNameSelectionViewModelImplTest.kt @@ -19,6 +19,8 @@ import nl.rijksoverheid.ctr.holder.your_events.utils.YourEventsFragmentUtil import nl.rijksoverheid.ctr.persistence.database.HolderDatabase import nl.rijksoverheid.ctr.persistence.database.entities.EventGroupEntity import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -81,7 +83,6 @@ class HolderNameSelectionViewModelImplTest { val items = viewModel.itemsLiveData.getOrAwaitValue() - assertEquals("firstNameA2", viewModel.selectedName()) assertEquals(false, (items[1] as HolderNameSelectionItem.ListItem).isSelected) assertEquals(true, (items[1] as HolderNameSelectionItem.ListItem).willBeRemoved) assertEquals(false, (items[1] as HolderNameSelectionItem.ListItem).nothingSelectedError) @@ -139,6 +140,49 @@ class HolderNameSelectionViewModelImplTest { assertEquals(0, itemSlot.captured) } + @Test + fun `given no name is selected, when store selection, then nothing selected error`() = runTest { + mockEvents() + + val viewModel = HolderNameSelectionViewModelImpl( + matchedEventsUseCase, + getRemoteProtocolFromEventGroupUseCase, + selectionDataUtil, + yourEventsFragmentUtil, + holderDatabase, + greenCardUtil, + matchingBlobIds + ) + + viewModel.storeSelection { } + + assertTrue(viewModel.nameSelectionError.getOrAwaitValue()) + } + + @Test + fun `given name no name is selected yet, when select name and store selection, then no error`() = + runTest { + mockEvents() + coEvery { matchedEventsUseCase.selected(any(), any()) } just runs + + val viewModel = HolderNameSelectionViewModelImpl( + matchedEventsUseCase, + getRemoteProtocolFromEventGroupUseCase, + selectionDataUtil, + yourEventsFragmentUtil, + holderDatabase, + greenCardUtil, + matchingBlobIds + ) + + viewModel.onItemSelected("firstNameA1") + viewModel.storeSelection { + assertEquals("firstNameA1", it) + } + + assertFalse(viewModel.nameSelectionError.getOrAwaitValue()) + } + private suspend fun mockEvents() { coEvery { holderDatabase.eventGroupDao().getAll() } returns getEvents() val eventSlot = slot() diff --git a/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtilImplTest.kt b/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtilImplTest.kt index 327f75d4b..bb467254f 100644 --- a/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtilImplTest.kt +++ b/holder/src/test/java/nl/rijksoverheid/ctr/holder/fuzzy_matching/SelectionDataUtilImplTest.kt @@ -39,12 +39,16 @@ class SelectionDataUtilImplTest : AutoCloseKoinTest() { private val yourEventsFragmentUtil: YourEventsFragmentUtil by inject() private val remoteEventStringUtil: RemoteEventStringUtil by inject() + private fun getFormattedString(stringResId: Int, formattedString: String): String { + return getApplication().getString(stringResId, formattedString) + } private val selectionDataUtil by lazy { SelectionDataUtilImpl( cachedAppConfigUseCase, yourEventsFragmentUtil, remoteEventStringUtil, getApplication().resources::getQuantityString, + ::getFormattedString, getApplication()::getString ) } @@ -101,4 +105,15 @@ class SelectionDataUtilImplTest : AutoCloseKoinTest() { assertEquals(expectedData[index], data.type) } } + + @Test + fun `details string for dcc vaccinations display dose`() { + val actualData = selectionDataUtil.details("dcc_code1", listOf(RemoteEventVaccination(RemoteEvent.TYPE_VACCINATION, "", mockk { + every { date } returns LocalDate.now() + every { doseNumber } returns "2" + every { totalDoses } returns "2" + }))) + + assertEquals("Vaccinatie dosis 2/2", actualData.first().type) + } }