From ecec13d07d0f241de0d71d13bb5b3ed76f0f6574 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Mon, 27 Apr 2026 15:09:29 +0300 Subject: [PATCH] Add more container notifications --- .../ContainerNotificationType.kt | 12 ++++++ .../ui/component/signing/SigningNavigation.kt | 32 +++++++++++++++- .../ContainerNotificationsScreen.kt | 14 +++++++ .../ria/DigiDoc/viewmodel/SigningViewModel.kt | 9 +++++ app/src/main/res/values-et/strings.xml | 25 ++++++------ app/src/main/res/values/strings.xml | 23 ++++++----- .../libdigidoclib/SignedContainerTest.kt | 20 ++++++++++ .../DigiDoc/libdigidoclib/SignedContainer.kt | 38 +++++++++++++------ 8 files changed, 140 insertions(+), 33 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/domain/model/notifications/ContainerNotificationType.kt b/app/src/main/kotlin/ee/ria/DigiDoc/domain/model/notifications/ContainerNotificationType.kt index b4f0bd13e..8dd574b14 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/domain/model/notifications/ContainerNotificationType.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/domain/model/notifications/ContainerNotificationType.kt @@ -26,6 +26,10 @@ sealed class ContainerNotificationType { data object CadesFile : ContainerNotificationType() + data object DdocFile : ContainerNotificationType() + + data object EmptyFile : ContainerNotificationType() + data class UnknownSignatures( val count: Int, ) : ContainerNotificationType() @@ -33,4 +37,12 @@ sealed class ContainerNotificationType { data class InvalidSignatures( val count: Int, ) : ContainerNotificationType() + + data class UnknownTimestamps( + val count: Int, + ) : ContainerNotificationType() + + data class InvalidTimestamps( + val count: Int, + ) : ContainerNotificationType() } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt index 8f2599884..f3dc00be1 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt @@ -181,11 +181,17 @@ fun SigningNavigation( val isNestedContainer = sharedContainerViewModel.isNestedContainer(signedContainer) val isXadesContainer = signedContainer?.isXades() == true val isCadesContainer = signedContainer?.isCades() == true + val isDdocContainer = signedContainer?.isDdoc() == true + var isEmptyFileInContainer by remember { mutableStateOf(false) } var validSignaturesCount by remember { mutableIntStateOf(0) } var unknownSignaturesCount by remember { mutableIntStateOf(0) } var invalidSignaturesCount by remember { mutableIntStateOf(0) } + var validTimestampsCount by remember { mutableIntStateOf(0) } + var unknownTimestampsCount by remember { mutableIntStateOf(0) } + var invalidTimestampsCount by remember { mutableIntStateOf(0) } + var isSignaturesCountLoaded by remember { mutableStateOf(false) } val isParentContainerSivaConfirmed = sharedContainerViewModel.isSivaConfirmed.value == true @@ -571,10 +577,26 @@ fun SigningNavigation( invalidSignaturesCount = signatureCounts?.get(ValidatorInterface.Status.Invalid) ?: 0 + if (signedContainer?.containerMimetype() != ASICS_MIMETYPE) { + val timestampCounts = signedContainer?.getTimestampStatusCount() + validTimestampsCount = timestampCounts?.get(ValidatorInterface.Status.Valid) ?: 0 + unknownTimestampsCount = + timestampCounts?.get(ValidatorInterface.Status.Unknown) ?: 0 + invalidTimestampsCount = + timestampCounts?.get(ValidatorInterface.Status.Invalid) ?: 0 + } + + isEmptyFileInContainer = + signingViewModel.isEmptyFileInList( + signedContainer?.getDataFiles() ?: listOf(), + ) + sharedContainerViewModel.setContainerNotifications( listOfNotNull( ContainerNotificationType.XadesFile.takeIf { isXadesContainer }, ContainerNotificationType.CadesFile.takeIf { isCadesContainer }, + ContainerNotificationType.DdocFile.takeIf { isDdocContainer }, + ContainerNotificationType.EmptyFile.takeIf { isEmptyFileInContainer }, ContainerNotificationType .UnknownSignatures( unknownSignaturesCount, @@ -583,6 +605,14 @@ fun SigningNavigation( .InvalidSignatures( invalidSignaturesCount, ).takeIf { invalidSignaturesCount > 0 }, + ContainerNotificationType + .UnknownTimestamps( + unknownTimestampsCount, + ).takeIf { unknownTimestampsCount > 0 }, + ContainerNotificationType + .InvalidTimestamps( + invalidTimestampsCount, + ).takeIf { invalidTimestampsCount > 0 }, ), ) } @@ -948,7 +978,7 @@ fun SigningNavigation( signedContainer?.let { container -> container .getTimestamps() - ?.let { timestamps -> + .let { timestamps -> Row { SignatureComponent( modifier, diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/notifications/ContainerNotificationsScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/notifications/ContainerNotificationsScreen.kt index 9a1932048..ef0ea8b70 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/notifications/ContainerNotificationsScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/notifications/ContainerNotificationsScreen.kt @@ -190,6 +190,8 @@ fun ContainerNotificationsScreen( when (type) { ContainerNotificationType.XadesFile -> stringResource(R.string.xades_file_message) ContainerNotificationType.CadesFile -> stringResource(R.string.cades_file_message) + ContainerNotificationType.DdocFile -> stringResource(R.string.ddoc_file_message) + ContainerNotificationType.EmptyFile -> stringResource(R.string.empty_file_message) is ContainerNotificationType.UnknownSignatures -> pluralStringResource( id = R.plurals.signatures_unknown, @@ -202,6 +204,18 @@ fun ContainerNotificationsScreen( count = type.count, type.count, ) + is ContainerNotificationType.UnknownTimestamps -> + pluralStringResource( + id = R.plurals.timestamps_unknown, + count = type.count, + type.count, + ) + is ContainerNotificationType.InvalidTimestamps -> + pluralStringResource( + id = R.plurals.timestamps_invalid, + count = type.count, + type.count, + ) }, ) } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt index 7b289cbb9..2c17463b1 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt @@ -37,6 +37,7 @@ import ee.ria.DigiDoc.common.Constant.UNSIGNABLE_CONTAINER_MIMETYPES import ee.ria.DigiDoc.domain.repository.fileopening.FileOpeningRepository import ee.ria.DigiDoc.domain.repository.siva.SivaRepository import ee.ria.DigiDoc.libdigidoclib.SignedContainer +import ee.ria.DigiDoc.libdigidoclib.domain.model.DataFileInterface import ee.ria.DigiDoc.libdigidoclib.domain.model.SignatureInterface import ee.ria.DigiDoc.utilsLib.container.ContainerUtil import ee.ria.DigiDoc.utilsLib.container.ContainerUtil.createContainerAction @@ -45,6 +46,8 @@ import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog import ee.ria.DigiDoc.utilsLib.mimetype.MimeTypeResolver import ee.ria.DigiDoc.viewmodel.shared.SharedContainerViewModel import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withContext import org.apache.commons.io.FilenameUtils import java.io.File @@ -65,6 +68,9 @@ class SigningViewModel private val _shouldResetSignedContainer = MutableLiveData(false) val shouldResetSignedContainer: LiveData = _shouldResetSignedContainer + private val _hasEmptyFiles = MutableStateFlow(false) + val hasEmptyFiles: StateFlow = _hasEmptyFiles + fun handleBackButton() { _shouldResetSignedContainer.postValue(true) } @@ -137,6 +143,9 @@ class SigningViewModel signature.countryName.isEmpty() && signature.postalCode.isEmpty() + fun isEmptyFileInList(dataFiles: List): Boolean = + dataFiles.any { dataFile -> dataFile.fileSize == 0L } + @Throws(Exception::class) suspend fun openCryptoContainer( context: Context, diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0164a1153..bb14e4803 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -52,25 +52,28 @@ Failide laadimine ebaõnnestus Ümbriku laadimine ebaõnnestus Ümbrikule on lisatud tühi fail. Allkirjastamiseks eemalda ümbrikust tühi fail. - Tegemist on XADES allkirja sisalduva ASICS ümbrikuga. Sellele ümbrikule ei saa allkirja lisada ega eemaldada. + Tegemist on XADES allkirja sisaldava ASICS ümbrikuga. Sellele ümbrikule ei saa allkirja lisada ega eemaldada. Tegemist on CAdES allkirja sisaldava ümbrikuga. Sellele ümbrikule ei saa allkirja lisada ega eemaldada. + Tegemist on vanas formaadis DigiDoc ümbrikuga. Sellele ümbrikule ei saa allkirja lisada ega eemaldada. - %1$d ALLKIRI ON TEADMATA - %1$d ALLKIRJA ON TEADMATA - - + %1$d allkiri on teadmata + %1$d allkirja on teadmata - %1$d ALLKIRI EI KEHTI - %1$d ALLKIRJA EI KEHTI - - + %1$d allkiri ei kehti + %1$d allkirja ei kehti %1$d kehtiv allkiri %1$d kehtivat allkirja - - + + + %1$d ajatempel on teadmata + %1$d ajatemplit on teadmata + + + %1$d ajatempel ei kehti + %1$d ajatemplit ei kehti Kontrollkood: diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37d811938..bf64e3f8b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -54,23 +54,26 @@ An empty file is attached to the envelope. Remove the empty file from the container to sign. This ASICS container contains XADES signature. You are not allowed to add or remove signatures to this container. This container contains CAdES signature. You are not allowed to add or remove signatures to this container. + The current file is a DigiDoc container that is not supported officially any longer. You are not allowed to add or remove signatures to this container. - %1$d UNKNOWN SIGNATURE - %1$d UNKNOWN SIGNATURES - - + %1$d unknown signature + %1$d unknown signatures - %1$d INVALID SIGNATURE - %1$d INVALID SIGNATURES - - + %1$d invalid signature + %1$d invalid signature %1$d valid signature %1$d valid signatures - - + + + %1$d unknown timestamp + %1$d unknown timestamps + + + %1$d invalid timestamp + %1$d invalid timestamps Control code: diff --git a/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt b/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt index 40fe0a23b..cf6b72be9 100644 --- a/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt +++ b/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt @@ -685,6 +685,26 @@ class SignedContainerTest { assertEquals(0, signaturesStatuses[ValidatorInterface.Status.Invalid]) } + @Test + fun signedContainer_getTimestampStatusCount_successWithNoTimestamps() = + runTest { + val noSignaturesContainer = + getResourceFileAsFile( + context, + "example_no_signatures.asice", + ee.ria.DigiDoc.common.R.raw.example_no_signatures, + ) + + val signedContainer = openOrCreate(context, noSignaturesContainer, listOf(noSignaturesContainer), true) + + val signaturesStatuses = signedContainer.getTimestampStatusCount() + + assertNotNull(signaturesStatuses) + assertEquals(0, signaturesStatuses[ValidatorInterface.Status.Valid]) + assertEquals(0, signaturesStatuses[ValidatorInterface.Status.Unknown]) + assertEquals(0, signaturesStatuses[ValidatorInterface.Status.Invalid]) + } + // Requires internet access, emulator should be running with internet access and RIA VPN on. @Test fun signedContainer_openOrCreate_successCreatingNewContainerWithPDFSignatures() = diff --git a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt index 9d75545c9..9c4f7ed0a 100644 --- a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt +++ b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt @@ -24,6 +24,8 @@ package ee.ria.DigiDoc.libdigidoclib import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import ee.ria.DigiDoc.common.Constant.ASICS_MIMETYPE +import ee.ria.DigiDoc.common.Constant.DDOC_EXTENSION +import ee.ria.DigiDoc.common.Constant.DDOC_MIMETYPE import ee.ria.DigiDoc.common.Constant.DEFAULT_CONTAINER_EXTENSION import ee.ria.DigiDoc.common.Constant.DEFAULT_FILENAME import ee.ria.DigiDoc.common.Constant.NON_LEGACY_CONTAINER_EXTENSIONS @@ -68,7 +70,7 @@ class SignedContainer private val container: Container? = null, private var containerFile: File? = null, private val isExistingContainer: Boolean = false, - private val timestamps: List? = emptyList(), + private val timestamps: List = emptyList(), ) : ee.ria.DigiDoc.common.container.Container { suspend fun getDataFiles(): List { return CoroutineScope(IO) @@ -108,7 +110,7 @@ class SignedContainer return null } - fun getTimestamps(): List? = timestamps + fun getTimestamps(): List = timestamps suspend fun getSignatures(thread: CoroutineContext = IO): List = withContext(thread) { @@ -237,7 +239,26 @@ class SignedContainer signature?.profile()?.lowercase()?.contains("cades") ?: false } ?: false + fun isDdoc(): Boolean = + containerMimetype().equals(DDOC_MIMETYPE, true) && + containerFile?.extension == DDOC_EXTENSION + suspend fun getSignaturesStatusCount(): Map { + val signatures = getSignatures(Main) + return countStatuses(signatures) { it.validator.status } + } + + fun getTimestampStatusCount(): Map { + val timestamps = getTimestamps() + return countStatuses(timestamps) { it.validator.status } + } + + fun isSignedPDF(): Boolean = containerFile?.isSignedPDF(context) ?: false + + private fun countStatuses( + items: List, + getStatus: (T) -> ValidatorInterface.Status, + ): Map { val counts = mutableMapOf( ValidatorInterface.Status.Valid to 0, @@ -245,19 +266,14 @@ class SignedContainer ValidatorInterface.Status.Invalid to 0, ) - val signatures = getSignatures(Main) - - for (signature in signatures) { - signature.validator.status.let { status -> - counts[status] = counts[status]?.plus(1) ?: 1 - } + for (item in items) { + val status = getStatus(item) + counts[status] = counts[status]?.plus(1) ?: 1 } - return counts.toMap() + return counts } - fun isSignedPDF(): Boolean = containerFile?.isSignedPDF(context) ?: false - companion object { @Throws(Exception::class) fun addDataFiles(