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

Badge consistency (EXPOSUREAPP-13144) #5237

Merged
merged 11 commits into from
May 30, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ data class DccWalletInfo(
@get:JsonIgnore
val validUntilInstant: Instant
get() = Instant.parse(validUntil)

@get:JsonIgnore
val hasReissuance: Boolean
get() = certificateReissuance != null &&
certificateReissuance.reissuanceDivision.visible
}

@JsonTypeInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.covidcertificate.person.core

import dagger.Reusable
import de.rki.coronawarnapp.ccl.dccwalletinfo.model.BoosterNotification
import de.rki.coronawarnapp.ccl.dccwalletinfo.model.DccWalletInfo
import de.rki.coronawarnapp.ccl.dccwalletinfo.storage.DccWalletInfoRepository
import de.rki.coronawarnapp.covidcertificate.common.certificate.CertificatePersonIdentifier
import de.rki.coronawarnapp.covidcertificate.common.certificate.CertificateProvider
Expand Down Expand Up @@ -57,21 +58,21 @@ class PersonCertificatesProvider @Inject constructor(
settings
)

val hasBooster = settings.hasBoosterBadge(dccWalletInfo?.boosterNotification)
val hasDccReissuance = settings?.showDccReissuanceBadge ?: false
val hasNewAdmissionState = settings?.showAdmissionStateChangedBadge ?: false
val hasBoosterBadge = settings.hasBoosterBadge(dccWalletInfo?.boosterNotification)
val hasDccReissuanceBadge = settings.hasReissuanceBadge(dccWalletInfo)
val hasNewAdmissionStateBadge = settings.hasAdmissionStateChangedBadge()
val badgeCount = certs.count { it.hasNotificationBadge } +
hasBooster.toInt() + hasDccReissuance.toInt() + hasNewAdmissionState.toInt()
hasBoosterBadge.toInt() + hasDccReissuanceBadge.toInt() + hasNewAdmissionStateBadge.toInt()
Timber.tag(TAG).d("Person [code=%s, badgeCount=%s]", personIdentifier.codeSHA256, badgeCount)

PersonCertificates(
certificates = certs.toCertificateSortOrder(),
isCwaUser = certs.any { it.personIdentifier.belongsToSamePerson(cwaUser) },
badgeCount = badgeCount,
dccWalletInfo = dccWalletInfo,
hasBoosterBadge = hasBooster,
hasDccReissuanceBadge = hasDccReissuance,
hasNewAdmissionState = hasNewAdmissionState
hasBoosterBadge = hasBoosterBadge,
hasDccReissuanceBadge = hasDccReissuanceBadge,
hasNewAdmissionState = hasNewAdmissionStateBadge
)
}.toSet()
}.shareLatest(scope = appScope)
Expand All @@ -98,7 +99,7 @@ class PersonCertificatesProvider @Inject constructor(
}

private fun PersonSettings?.hasBoosterBadge(boosterNotification: BoosterNotification?): Boolean {
if (boosterNotification == null) return false
if (boosterNotification == null || !boosterNotification.visible) return false
return hasBoosterRuleNotYetSeen(this, boosterNotification)
}

Expand All @@ -107,6 +108,15 @@ class PersonCertificatesProvider @Inject constructor(
boosterNotification: BoosterNotification
) = personSettings?.lastSeenBoosterRuleIdentifier != boosterNotification.identifier

private fun PersonSettings?.hasReissuanceBadge(info: DccWalletInfo?): Boolean {
if (this == null || info == null) return false
return showDccReissuanceBadge && info.hasReissuance
}

private fun PersonSettings?.hasAdmissionStateChangedBadge(): Boolean {
return this?.showAdmissionStateChangedBadge ?: false
}

private fun Boolean?.toInt(): Int = if (this == true) 1 else 0

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,41 +81,41 @@ class PersonDetailsViewModel @AssistedInject constructor(
return UiState(name = "", emptyList())
}

val dccWalletInfo = personCertificates.dccWalletInfo
val certificateItems = mutableListOf<CertificateItem>().apply {
val color = if (priorityCertificate.isDisplayValid) colorShade else PersonColorShade.COLOR_INVALID
colorShadeData.postValue(color)
val color = if (priorityCertificate.isDisplayValid) colorShade else PersonColorShade.COLOR_INVALID
colorShadeData.postValue(color)

val certificateItems = mutableListOf<CertificateItem>()
personCertificates.dccWalletInfo?.let { info ->
// 1. Admission state tile
dccWalletInfo?.admissionState?.let { admissionState ->
if (admissionState.visible) add(admissionStateItem(admissionState, personCertificates))
}
if (info.admissionState.visible)
certificateItems.add(admissionStateItem(info.admissionState, personCertificates))
// 2. Dcc reissuance tile
dccWalletInfo?.certificateReissuance?.reissuanceDivision?.let { division ->
if (division.visible) add(dccReissuanceItem(division, personCertificates))
if (info.hasReissuance) info.certificateReissuance?.reissuanceDivision?.let { division ->
certificateItems.add(dccReissuanceItem(division, personCertificates))
}
// 3. Booster notification tile
dccWalletInfo?.boosterNotification?.let { boosterNotification ->
if (boosterNotification.visible) add(boosterItem(boosterNotification, personCertificates))
}
if (info.boosterNotification.visible)
certificateItems.add(boosterItem(info.boosterNotification, personCertificates))
// 4.Vaccination state tile
dccWalletInfo?.vaccinationState?.let { vaccinationState ->
if (vaccinationState.visible) add(vaccinationInfoItem(vaccinationState))
}
// Person details tile
add(cwaUserCard(personCertificates))
// Certificates tiles
personCertificates.certificates // `certificates` are already sorted by date
// Sorting by `whether it is high prio certificate` will bring this certificate to the top
.sortedByDescending { it.containerId == priorityCertificate.containerId }
.forEach { cwaCert ->
addCardItem(
certificate = cwaCert,
priorityCertificate = priorityCertificate,
isLoading = isLoading
)
}
if (info.vaccinationState.visible)
certificateItems.add(vaccinationInfoItem(info.vaccinationState))
}

// Person details tile
certificateItems.add(cwaUserCard(personCertificates))

// Certificates tiles
personCertificates.certificates // `certificates` are already sorted by date
// Sorting by `whether it is high prio certificate` will bring this certificate to the top
.sortedByDescending { it.containerId == priorityCertificate.containerId }
.forEach { cwaCert ->
certificateItems.addCardItem(
certificate = cwaCert,
priorityCertificate = priorityCertificate,
isLoading = isLoading
)
}

return UiState(
name = priorityCertificate.fullName,
certificateItems = certificateItems,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ class RecoveryCertificateDetailsFragment : Fragment(R.layout.fragment_recovery_c
}
}

override fun onStop() {
super.onStop()
viewModel.refreshCertState()
override fun onPause() {
viewModel.markAsSeen()
super.onPause()
}

private fun FragmentRecoveryCertificateDetailsBinding.onCertificateReady(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class RecoveryCertificateDetailsViewModel @AssistedInject constructor(
}
}

fun refreshCertState() = launch(scope = appScope) {
fun markAsSeen() = launch(scope = appScope) {
Timber.v("refreshCertState()")
recoveryCertificateRepository.acknowledgeState(containerId)
if (!fromScanner) recoveryCertificateRepository.markAsSeenByUser(containerId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ class TestCertificateDetailsFragment : Fragment(R.layout.fragment_test_certifica
}
}

override fun onStop() {
super.onStop()
viewModel.refreshCertState()
override fun onPause() {
viewModel.markAsSeen()
super.onPause()
}

private fun FragmentTestCertificateDetailsBinding.onError(error: Throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ class TestCertificateDetailsViewModel @AssistedInject constructor(
}
}

fun refreshCertState() = launch(scope = appScope) {
Timber.v("refreshCertState()")
fun markAsSeen() = launch(scope = appScope) {
Timber.v("markAsSeen()")
if (covidCertificate.value?.isNew == true && !fromScanner) {
testCertificateRepository.markCertificateAsSeenByUser(containerId)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ class VaccinationDetailsFragment : Fragment(R.layout.fragment_vaccination_detail
}
}

override fun onStop() {
super.onStop()
viewModel.refreshCertState()
override fun onPause() {
viewModel.markAsSeen()
super.onPause()
}

private fun FragmentVaccinationDetailsBinding.bindToolbar() = toolbar.apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class VaccinationDetailsViewModel @AssistedInject constructor(
fun openFullScreen() = qrCode?.let { events.postValue(VaccinationDetailsNavigation.FullQrCode(it)) }

fun recycleVaccinationCertificateConfirmed() = launch(scope = appScope) {
Timber.d("Recycling Vaccination Certificate=$containerId")
Timber.d("Move vaccination certificate=$containerId to bin")
vaccinationCertificateRepository.recycleCertificate(containerId)
events.postValue(VaccinationDetailsNavigation.ReturnToPersonDetailsAfterRecycling)
}
Expand All @@ -60,8 +60,7 @@ class VaccinationDetailsViewModel @AssistedInject constructor(
}
}

fun refreshCertState() = launch(scope = appScope) {
Timber.v("refreshCertState()")
fun markAsSeen() = launch(scope = appScope) {
vaccinationCertificateRepository.acknowledgeState(containerId)
if (!fromScanner) vaccinationCertificateRepository.markAsSeenByUser(containerId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,34 @@ class DccReissuanceNotificationService @Inject constructor(
oldWalletInfo: DccWalletInfo?,
newWalletInfo: DccWalletInfo
) {
val oldCertReissuance = oldWalletInfo?.certificateReissuance
val newCertReissuance = newWalletInfo.certificateReissuance
val oldIdentifier = oldWalletInfo?.certificateReissuance?.reissuanceDivision?.identifier
when {
newCertReissuance != null &&
newCertReissuance.reissuanceDivision.identifier !=
oldCertReissuance?.reissuanceDivision?.identifier -> {
Timber.tag(TAG).d("Notify person=%s about Dcc reissuance", personIdentifier.codeSHA256)
newWalletInfo.hasNewReissuance(oldIdentifier) -> {
Timber.tag(TAG).d("Notify person=%s about reissuance", personIdentifier.codeSHA256)
personNotificationSender.showNotification(
personIdentifier = personIdentifier,
type = notificationSenderType,
messageRes = R.string.notification_body_certificate
)
personCertificatesSettings.setDccReissuanceNotifiedAt(personIdentifier)
}
// New calculation says no Dcc Reissuance anymore
newCertReissuance == null -> {
Timber.tag(TAG).d("Person=%s shouldn't be notified about Dcc reissuance", personIdentifier.codeSHA256)
Timber.tag(TAG).d("Dismiss badge of Dcc reissuance for person=%s", personIdentifier.codeSHA256)
personCertificatesSettings.dismissReissuanceBadge(personIdentifier)
// no action needed
newWalletInfo.hasReissuance -> {
Timber.tag(TAG).d("Person=%s has no changes", personIdentifier.codeSHA256)
}
// Otherwise nothing
// no reissuance -> dismiss
else -> {
Timber.tag(TAG).d("Person=%s shouldn't be notified about Dcc reissuance", personIdentifier.codeSHA256)
Timber.tag(TAG).d("Dismiss badge for person=%s", personIdentifier.codeSHA256)
personCertificatesSettings.dismissReissuanceBadge(personIdentifier)
}
}
}

private fun DccWalletInfo.hasNewReissuance(oldIdentifier: String?): Boolean {
return this.hasReissuance &&
certificateReissuance?.reissuanceDivision?.identifier != oldIdentifier
}

companion object {
private val TAG = tag<DccReissuanceNotificationService>()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ internal class DccReissuanceNotificationServiceTest : BaseTest() {
coEvery { personCertificatesSettings.dismissReissuanceBadge(any()) } just Runs
every { personNotificationSender.showNotification(any(), any(), any()) } just Runs
every { oldReissuanceDivision.identifier } returns "renew"
every { oldReissuanceDivision.visible } returns true
every { newReissuanceDivision.identifier } returns "extend"
every { newReissuanceDivision.visible } returns true
every { oldCertificateReissuance.reissuanceDivision } returns oldReissuanceDivision
every { newCertificateReissuance.reissuanceDivision } returns newReissuanceDivision
every { newDccWalletInfo.certificateReissuance } returns newCertificateReissuance
every { newDccWalletInfo.hasReissuance } returns true
}

@Test
Expand Down Expand Up @@ -74,7 +77,10 @@ internal class DccReissuanceNotificationServiceTest : BaseTest() {
).notifyIfNecessary(
personIdentifier = personIdentifier,
oldWalletInfo = mockk<DccWalletInfo>()
.apply { every { certificateReissuance } returns newCertificateReissuance },
.apply {
every { certificateReissuance } returns newCertificateReissuance
every { hasReissuance } returns true
},
newWalletInfo = newDccWalletInfo,
)

Expand All @@ -92,7 +98,10 @@ internal class DccReissuanceNotificationServiceTest : BaseTest() {
).notifyIfNecessary(
personIdentifier = personIdentifier,
oldWalletInfo = mockk<DccWalletInfo>()
.apply { every { certificateReissuance } returns oldCertificateReissuance },
.apply {
every { certificateReissuance } returns oldCertificateReissuance
every { hasReissuance } returns true
},
newWalletInfo = newDccWalletInfo
)

Expand All @@ -110,7 +119,32 @@ internal class DccReissuanceNotificationServiceTest : BaseTest() {
).notifyIfNecessary(
personIdentifier = personIdentifier,
oldWalletInfo = newDccWalletInfo,
newWalletInfo = mockk<DccWalletInfo>().apply { every { certificateReissuance } returns null }
newWalletInfo = mockk<DccWalletInfo>().apply {
every { hasReissuance } returns false
}
)

coEvery {
personCertificatesSettings.dismissReissuanceBadge(personIdentifier)
}

coVerify(exactly = 0) {
personNotificationSender.showNotification(personIdentifier, any(), R.string.notification_body_certificate)
personCertificatesSettings.setDccReissuanceNotifiedAt(personIdentifier, any())
}
}

@Test
fun `dismiss the badge if the new dcc reissuance is invisible`() = runTest {
DccReissuanceNotificationService(
personCertificatesSettings = personCertificatesSettings,
personNotificationSender = personNotificationSender
).notifyIfNecessary(
personIdentifier = personIdentifier,
oldWalletInfo = newDccWalletInfo,
newWalletInfo = mockk<DccWalletInfo>().apply {
every { hasReissuance } returns false
}
)

coEvery {
Expand Down