From dd7ab4c94c32ed70590c89303a2e7fa8eaff1eba Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Fri, 16 Jan 2026 17:06:59 +0200 Subject: [PATCH 1/9] MOPPAND-1738 Crypto and Signed Container closing logic update and remove Recent Documents UI and logic. --- .../ee/ria/DigiDoc/utils/ConstantTest.kt | 2 - .../kotlin/ee/ria/DigiDoc/utils/RouteTest.kt | 6 - .../viewmodel/RecentDocumentsViewModelTest.kt | 265 -------- .../ee/ria/DigiDoc/RIADigiDocAppNavigation.kt | 28 - .../fragment/RecentDocumentsFragment.kt | 86 --- .../ria/DigiDoc/fragment/screen/HomeScreen.kt | 43 +- .../fragment/screen/RecentDocumentsScreen.kt | 602 ------------------ .../ui/component/crypto/EncryptNavigation.kt | 57 +- .../ui/component/menu/OpenMenuBottomSheet.kt | 95 --- .../ui/component/menu/TwoButtonMenu.kt | 62 -- .../DigiDoc/ui/component/shared/Document.kt | 157 ----- .../ui/component/signing/SigningNavigation.kt | 58 +- .../bottomsheet/ContainerBottomSheet.kt | 2 + .../kotlin/ee/ria/DigiDoc/utils/Constant.kt | 3 - .../main/kotlin/ee/ria/DigiDoc/utils/Route.kt | 9 - .../ria/DigiDoc/viewmodel/EncryptViewModel.kt | 23 +- .../viewmodel/RecentDocumentsViewModel.kt | 184 ------ app/src/main/res/values-et/strings.xml | 14 +- app/src/main/res/values/strings.xml | 14 +- 19 files changed, 93 insertions(+), 1617 deletions(-) delete mode 100644 app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModelTest.kt delete mode 100644 app/src/main/kotlin/ee/ria/DigiDoc/fragment/RecentDocumentsFragment.kt delete mode 100644 app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/RecentDocumentsScreen.kt delete mode 100644 app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/OpenMenuBottomSheet.kt delete mode 100644 app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/TwoButtonMenu.kt delete mode 100644 app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/Document.kt delete mode 100644 app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModel.kt diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/ConstantTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/ConstantTest.kt index 51f3211d..aaed9ecb 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/ConstantTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/ConstantTest.kt @@ -51,8 +51,6 @@ class ConstantTest { assertEquals("signer_detail_route", Constant.Routes.SIGNER_DETAIL_SCREEN) assertEquals("certificate_detail_route", Constant.Routes.CERTIFICATE_DETAIL_SCREEN) assertEquals("recipient_detail_route", Constant.Routes.RECIPIENT_DETAIL_SCREEN) - assertEquals("recent_documents_route", Constant.Routes.RECENT_DOCUMENTS_SCREEN) - assertEquals("recent_documents_from_encrypt_route", Constant.Routes.RECENT_DOCUMENTS_FROM_ENCRYPT_SCREEN) assertEquals("settings_route", Constant.Routes.SETTINGS_SCREEN) assertEquals("settings_language_chooser_route", Constant.Routes.SETTINGS_LANGUAGE_CHOOSER_SCREEN) assertEquals("settings_theme_chooser_route", Constant.Routes.SETTINGS_THEME_CHOOSER_SCREEN) diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/RouteTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/RouteTest.kt index 6126f66c..fc68943e 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/RouteTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/utils/RouteTest.kt @@ -42,9 +42,6 @@ import ee.ria.DigiDoc.utils.Constant.Routes.MYEID_IDENTIFICATION_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.MYEID_PIN_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.MYEID_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.PROXY_SERVICES_SCREEN -import ee.ria.DigiDoc.utils.Constant.Routes.RECENT_DOCUMENTS_FROM_ENCRYPT_SCREEN -import ee.ria.DigiDoc.utils.Constant.Routes.RECENT_DOCUMENTS_SCREEN -import ee.ria.DigiDoc.utils.Constant.Routes.RECENT_DOCUMENTS_SCREEN_FROM_SIGNING_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.RECIPIENT_DETAIL_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.ROOT_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.SETTINGS_LANGUAGE_CHOOSER_SCREEN @@ -82,9 +79,6 @@ class RouteTest { assertEquals(SIGNER_DETAIL_SCREEN, Route.SignerDetail.route) assertEquals(CERTIFICATE_DETAIL_SCREEN, Route.CertificateDetail.route) assertEquals(RECIPIENT_DETAIL_SCREEN, Route.RecipientDetail.route) - assertEquals(RECENT_DOCUMENTS_SCREEN, Route.RecentDocuments.route) - assertEquals(RECENT_DOCUMENTS_SCREEN_FROM_SIGNING_SCREEN, Route.RecentDocumentsFromSigning.route) - assertEquals(RECENT_DOCUMENTS_FROM_ENCRYPT_SCREEN, Route.RecentDocumentsFromEncrypt.route) assertEquals(SETTINGS_SCREEN, Route.Settings.route) assertEquals(SETTINGS_LANGUAGE_CHOOSER_SCREEN, Route.SettingsLanguageChooser.route) assertEquals(SETTINGS_THEME_CHOOSER_SCREEN, Route.SettingsThemeChooser.route) diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModelTest.kt deleted file mode 100644 index 3a720459..00000000 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModelTest.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName") - -package ee.ria.DigiDoc.viewmodel - -import android.content.ContentResolver -import android.content.Context -import androidx.arch.core.executor.testing.InstantTaskExecutorRule -import androidx.test.platform.app.InstrumentationRegistry -import com.google.gson.Gson -import ee.ria.DigiDoc.common.Constant.ASICE_MIMETYPE -import ee.ria.DigiDoc.common.Constant.DEFAULT_MIME_TYPE -import ee.ria.DigiDoc.common.testfiles.asset.AssetFile -import ee.ria.DigiDoc.common.testfiles.file.TestFileUtil.Companion.createZipWithTextFile -import ee.ria.DigiDoc.configuration.ConfigurationProperty -import ee.ria.DigiDoc.configuration.ConfigurationSignatureVerifierImpl -import ee.ria.DigiDoc.configuration.loader.ConfigurationLoader -import ee.ria.DigiDoc.configuration.loader.ConfigurationLoaderImpl -import ee.ria.DigiDoc.configuration.properties.ConfigurationPropertiesImpl -import ee.ria.DigiDoc.configuration.repository.CentralConfigurationRepositoryImpl -import ee.ria.DigiDoc.configuration.repository.ConfigurationRepository -import ee.ria.DigiDoc.configuration.repository.ConfigurationRepositoryImpl -import ee.ria.DigiDoc.configuration.service.CentralConfigurationServiceImpl -import ee.ria.DigiDoc.cryptolib.CDOC2Settings -import ee.ria.DigiDoc.domain.repository.siva.SivaRepository -import ee.ria.DigiDoc.libdigidoclib.SignedContainer -import ee.ria.DigiDoc.libdigidoclib.init.Initialization -import ee.ria.DigiDoc.libdigidoclib.init.LibdigidocLibraryLoader -import ee.ria.DigiDoc.utilsLib.container.ContainerUtil -import ee.ria.DigiDoc.utilsLib.mimetype.MimeTypeCache -import ee.ria.DigiDoc.utilsLib.mimetype.MimeTypeResolver -import ee.ria.DigiDoc.utilsLib.mimetype.MimeTypeResolverImpl -import ee.ria.DigiDoc.viewmodel.shared.SharedContainerViewModel -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Assert.fail -import org.junit.Before -import org.junit.BeforeClass -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` -import org.mockito.MockitoAnnotations -import org.mockito.junit.MockitoJUnitRunner -import org.mockito.kotlin.anyOrNull -import org.mockito.kotlin.verify -import java.io.File - -@RunWith(MockitoJUnitRunner::class) -class RecentDocumentsViewModelTest { - @get:Rule - val instantExecutorRule = InstantTaskExecutorRule() - - @Mock - private lateinit var sivaRepository: SivaRepository - - @Mock - private lateinit var mimeTypeCache: MimeTypeCache - - private lateinit var mimeTypeResolver: MimeTypeResolver - - private lateinit var cdoc2Settings: CDOC2Settings - - private lateinit var container: File - private lateinit var signedContainer: SignedContainer - - private lateinit var sharedContainerViewModel: SharedContainerViewModel - - companion object { - private var context: Context = InstrumentationRegistry.getInstrumentation().targetContext - private lateinit var configurationLoader: ConfigurationLoader - private lateinit var configurationRepository: ConfigurationRepository - - @JvmStatic - @BeforeClass - fun setupOnce() { - runBlocking { - try { - configurationLoader = - ConfigurationLoaderImpl( - Gson(), - CentralConfigurationRepositoryImpl( - CentralConfigurationServiceImpl("Tests", ConfigurationProperty()), - ), - ConfigurationProperty(), - ConfigurationPropertiesImpl(), - ConfigurationSignatureVerifierImpl(), - ) - configurationRepository = ConfigurationRepositoryImpl(context, configurationLoader) - LibdigidocLibraryLoader().init(context) - Initialization(configurationRepository).init(context) - } catch (_: Exception) { - } - } - } - } - - private lateinit var viewModel: RecentDocumentsViewModel - - @Before - fun setup() { - MockitoAnnotations.openMocks(this) - mimeTypeResolver = MimeTypeResolverImpl(mimeTypeCache) - cdoc2Settings = CDOC2Settings(context, configurationRepository) - viewModel = RecentDocumentsViewModel(context, sivaRepository, mimeTypeResolver, cdoc2Settings) - - container = - AssetFile.getResourceFileAsFile( - context, - "example.asice", - ee.ria.DigiDoc.common.R.raw.example, - ) - - signedContainer = - runBlocking { - SignedContainer.openOrCreate(context, container, listOf(container), true) - } - - sharedContainerViewModel = - SharedContainerViewModel( - context, - mock(ContentResolver::class.java), - ) - } - - @Test - fun recentDocumentsViewModel_openSignatureDocument_success() = - runBlocking { - val container = - AssetFile.getResourceFileAsFile( - context, - "example.asice", - ee.ria.DigiDoc.common.R.raw.example, - ) - - `when`(sivaRepository.isTimestampedContainer(anyOrNull())).thenReturn(false) - - val result = viewModel.openSignatureDocument(container, true) - - assertEquals(result.getDataFiles().size, 1) - } - - @Test - fun recentDocumentsViewModel_openSignatureDocument_successWithTimestampedContainer(): Unit = - runBlocking { - val tempFile = File.createTempFile("testFile", "asics", ContainerUtil.signatureContainersDir(context)) - tempFile.writeText("test content") - tempFile.deleteOnExit() - - `when`(sivaRepository.isTimestampedContainer(anyOrNull())).thenReturn(true) - - viewModel.openSignatureDocument(tempFile, true) - - verify(sivaRepository).getTimestampedContainer(anyOrNull(), anyOrNull()) - } - - @Test - fun recentDocumentsViewModel_getRecentDocumentList_success() { - ContainerUtil.signatureContainersDir(context).delete() - val tempFile = File.createTempFile("testFile", ".asice", ContainerUtil.signatureContainersDir(context)) - tempFile.deleteOnExit() - val list = viewModel.getRecentDocumentList() - - assertTrue(list.isNotEmpty()) - } - - @Test - fun recentDocumentsViewModel_handleSendToSigningViewWithSiva_sendToSigningViewWithSivaFalseWithOtherMimetype() = - runTest { - val mimeType = "some/other-mime" - val mockFile = createZipWithTextFile("application/zip") - - viewModel.handleDocument(mockFile, mimeType, true, sharedContainerViewModel) - - val sendToSiva = viewModel.sendToSigningViewWithSiva.value - if (sendToSiva != null) { - assertFalse(sendToSiva) - } else { - fail("sendToSigningViewWithSiva cannot be null") - } - } - - @Test - fun recentDocumentsViewModel_handleSendToSigningViewWithSiva_sendToSigningViewWithSivaTrue() = - runTest { - viewModel.handleSendToSigningViewWithSiva(true) - - val sendToSiva = viewModel.sendToSigningViewWithSiva.value - - if (sendToSiva != null) { - assertTrue(sendToSiva) - } else { - fail("sendToSigningViewWithSiva cannot be null") - } - } - - @Test - fun recentDocumentsViewModel_handleSendToSigningViewWithSiva_sendToSigningViewWithSivaFalse() = - runTest { - viewModel.handleSendToSigningViewWithSiva(false) - - val sendToSiva = viewModel.sendToSigningViewWithSiva.value - - if (sendToSiva != null) { - assertFalse(sendToSiva) - } else { - fail("sendToSigningViewWithSiva cannot be null") - } - } - - @Test - fun recentDocumentsViewModel_getMimetype_success() = - runTest { - `when`(mimeTypeCache.getMimeType(anyOrNull())).thenReturn(ASICE_MIMETYPE) - - val mimetype = viewModel.getMimetype(container) - - assertEquals(ASICE_MIMETYPE, mimetype) - } - - @Test - fun recentDocumentsViewModel_getMimetype_defaultMimeType() = - runTest { - `when`(mimeTypeCache.getMimeType(anyOrNull())).thenReturn("") - - val mimetype = viewModel.getMimetype(container) - - assertEquals(DEFAULT_MIME_TYPE, mimetype) - } - - @Test - fun recentDocumentsViewModel_getMimetype_successGettingFileMimetype() = - runTest { - `when`(mimeTypeCache.getMimeType(anyOrNull())).thenReturn("text/plain") - - val file = File.createTempFile("temp", ".txt") - - val mimetype = viewModel.getMimetype(file) - - assertEquals("text/plain", mimetype) - } -} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt index df0e2583..89a7180d 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt @@ -52,7 +52,6 @@ import ee.ria.DigiDoc.fragment.MyEidIdentificationFragment import ee.ria.DigiDoc.fragment.MyEidIdentificationMethodChooserFragment import ee.ria.DigiDoc.fragment.MyEidPinFragment import ee.ria.DigiDoc.fragment.ProxyServicesSettingsFragment -import ee.ria.DigiDoc.fragment.RecentDocumentsFragment import ee.ria.DigiDoc.fragment.RootFragment import ee.ria.DigiDoc.fragment.SignatureInputFragment import ee.ria.DigiDoc.fragment.SignatureMethodFragment @@ -220,33 +219,6 @@ fun RIADigiDocAppScreen(externalFileUris: List) { sharedSettingsViewModel = sharedSettingsViewModel, ) } - composable(route = Route.RecentDocuments.route) { - RecentDocumentsFragment( - modifier = Modifier.safeDrawingPadding(), - navController = navController, - sharedMenuViewModel = sharedMenuViewModel, - sharedContainerViewModel = sharedContainerViewModel, - fileOpeningMethod = FileOpeningMethod.ALL, - ) - } - composable(route = Route.RecentDocumentsFromSigning.route) { - RecentDocumentsFragment( - modifier = Modifier.safeDrawingPadding(), - navController = navController, - sharedMenuViewModel = sharedMenuViewModel, - sharedContainerViewModel = sharedContainerViewModel, - fileOpeningMethod = FileOpeningMethod.SIGNING, - ) - } - composable(route = Route.RecentDocumentsFromEncrypt.route) { - RecentDocumentsFragment( - modifier = Modifier.safeDrawingPadding(), - navController = navController, - sharedMenuViewModel = sharedMenuViewModel, - sharedContainerViewModel = sharedContainerViewModel, - fileOpeningMethod = FileOpeningMethod.CRYPTO, - ) - } composable(route = Route.Settings.route) { AdvancedSettingsFragment( modifier = Modifier.safeDrawingPadding(), diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/RecentDocumentsFragment.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/RecentDocumentsFragment.kt deleted file mode 100644 index b3347726..00000000 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/RecentDocumentsFragment.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName", "FunctionName") - -package ee.ria.DigiDoc.fragment - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.compose.ui.tooling.preview.Preview -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import ee.ria.DigiDoc.common.model.FileOpeningMethod -import ee.ria.DigiDoc.fragment.screen.RecentDocumentsScreen -import ee.ria.DigiDoc.ui.theme.RIADigiDocTheme -import ee.ria.DigiDoc.viewmodel.shared.SharedContainerViewModel -import ee.ria.DigiDoc.viewmodel.shared.SharedMenuViewModel - -@OptIn(ExperimentalComposeUiApi::class) -@Composable -fun RecentDocumentsFragment( - modifier: Modifier = Modifier, - navController: NavHostController, - sharedMenuViewModel: SharedMenuViewModel, - sharedContainerViewModel: SharedContainerViewModel, - fileOpeningMethod: FileOpeningMethod, -) { - Surface( - modifier = - modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .semantics { - testTagsAsResourceId = true - }.testTag("recentDocumentsFragment"), - color = MaterialTheme.colorScheme.background, - ) { - RecentDocumentsScreen( - modifier = modifier, - navController = navController, - sharedMenuViewModel = sharedMenuViewModel, - sharedContainerViewModel = sharedContainerViewModel, - fileOpeningMethod = fileOpeningMethod, - ) - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun RecentDocumentsFragmentPreview() { - RIADigiDocTheme { - RecentDocumentsFragment( - navController = rememberNavController(), - sharedMenuViewModel = hiltViewModel(), - sharedContainerViewModel = hiltViewModel(), - fileOpeningMethod = FileOpeningMethod.ALL, - ) - } -} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt index 83715138..7fc6bb7d 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt @@ -74,7 +74,6 @@ import ee.ria.DigiDoc.R import ee.ria.DigiDoc.ui.component.home.ActionButton import ee.ria.DigiDoc.ui.component.main.CrashDialog import ee.ria.DigiDoc.ui.component.menu.MainMenuBottomSheet -import ee.ria.DigiDoc.ui.component.menu.OpenMenuBottomSheet import ee.ria.DigiDoc.ui.component.menu.SettingsMenuBottomSheet import ee.ria.DigiDoc.ui.component.shared.TopBar import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding @@ -103,9 +102,6 @@ fun HomeScreen( val isMainMenuBottomSheetVisible = rememberSaveable { mutableStateOf(false) } val isSettingsMenuBottomSheetVisible = rememberSaveable { mutableStateOf(false) } - val isOpenMenuBottomSheetVisible = rememberSaveable { mutableStateOf(false) } - val isOpenMenuBottomFromSigning = rememberSaveable { mutableStateOf(false) } - val isOpenMenuBottomFromEncrypt = rememberSaveable { mutableStateOf(false) } val openMenuAddFileNavigateTo = remember { mutableStateOf(Route.SigningFileChoosing.route) } @@ -210,27 +206,6 @@ fun HomeScreen( isBottomSheetVisible = isSettingsMenuBottomSheetVisible, ) - OpenMenuBottomSheet( - isBottomSheetVisible = isOpenMenuBottomSheetVisible, - firstButtonClick = { - isOpenMenuBottomSheetVisible.value = false - navController.navigate( - openMenuAddFileNavigateTo.value, - ) - }, - secondButtonClick = { - isOpenMenuBottomSheetVisible.value = false - val recentDocumentsRoute = - when { - isOpenMenuBottomFromSigning.value -> Route.RecentDocumentsFromSigning.route - isOpenMenuBottomFromEncrypt.value -> Route.RecentDocumentsFromEncrypt.route - else -> Route.RecentDocuments.route - } - - navController.navigate(recentDocumentsRoute) - }, - ) - Surface( color = MaterialTheme.colorScheme.surface, modifier = @@ -318,9 +293,9 @@ fun HomeScreen( stringResource(id = R.string.main_home_open_document_description), onClickItem = { openMenuAddFileNavigateTo.value = Route.AllFilesChoosing.route - isOpenMenuBottomSheetVisible.value = true - isOpenMenuBottomFromSigning.value = false - isOpenMenuBottomFromEncrypt.value = false + navController.navigate( + openMenuAddFileNavigateTo.value, + ) }, testTag = "homeOpenDocumentButton", ) @@ -334,9 +309,9 @@ fun HomeScreen( stringResource(id = R.string.main_home_signature_description), onClickItem = { openMenuAddFileNavigateTo.value = Route.SigningFileChoosing.route - isOpenMenuBottomSheetVisible.value = true - isOpenMenuBottomFromSigning.value = true - isOpenMenuBottomFromEncrypt.value = false + navController.navigate( + openMenuAddFileNavigateTo.value, + ) }, testTag = "homeSignatureButton", ) @@ -350,9 +325,9 @@ fun HomeScreen( stringResource(id = R.string.main_home_crypto_description), onClickItem = { openMenuAddFileNavigateTo.value = Route.CryptoFileChoosing.route - isOpenMenuBottomSheetVisible.value = true - isOpenMenuBottomFromSigning.value = false - isOpenMenuBottomFromEncrypt.value = true + navController.navigate( + openMenuAddFileNavigateTo.value, + ) }, testTag = "homeCryptoButton", ) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/RecentDocumentsScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/RecentDocumentsScreen.kt deleted file mode 100644 index 6bf3c56a..00000000 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/RecentDocumentsScreen.kt +++ /dev/null @@ -1,602 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName", "FunctionName") - -package ee.ria.DigiDoc.fragment.screen - -import android.content.res.Configuration -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.SearchBar -import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.SearchBarDefaults.inputFieldColors -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.focusProperties -import androidx.compose.ui.focus.focusTarget -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.semantics.heading -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel -import androidx.lifecycle.asFlow -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController -import ee.ria.DigiDoc.R -import ee.ria.DigiDoc.common.Constant.DEFAULT_CONTAINER_EXTENSION -import ee.ria.DigiDoc.common.Constant.SEND_SIVA_CONTAINER_NOTIFICATION_MIMETYPES -import ee.ria.DigiDoc.common.model.FileOpeningMethod -import ee.ria.DigiDoc.cryptolib.CryptoContainer -import ee.ria.DigiDoc.libdigidoclib.SignedContainer -import ee.ria.DigiDoc.ui.component.menu.SettingsMenuBottomSheet -import ee.ria.DigiDoc.ui.component.shared.Document -import ee.ria.DigiDoc.ui.component.shared.InvisibleElement -import ee.ria.DigiDoc.ui.component.shared.LoadingScreen -import ee.ria.DigiDoc.ui.component.shared.MessageDialog -import ee.ria.DigiDoc.ui.component.shared.PreventResize -import ee.ria.DigiDoc.ui.component.shared.TopBar -import ee.ria.DigiDoc.ui.component.shared.dialog.SivaConfirmationDialog -import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding -import ee.ria.DigiDoc.ui.theme.Dimensions.XSPadding -import ee.ria.DigiDoc.ui.theme.Dimensions.dividerHeight -import ee.ria.DigiDoc.ui.theme.Dimensions.iconSizeXXS -import ee.ria.DigiDoc.ui.theme.Dimensions.invisibleElementHeight -import ee.ria.DigiDoc.ui.theme.Dimensions.zeroPadding -import ee.ria.DigiDoc.ui.theme.RIADigiDocTheme -import ee.ria.DigiDoc.utils.Route -import ee.ria.DigiDoc.utils.accessibility.AccessibilityUtil.Companion.getAccessibilityEventType -import ee.ria.DigiDoc.utils.accessibility.AccessibilityUtil.Companion.sendAccessibilityEvent -import ee.ria.DigiDoc.utils.extensions.reachedBottom -import ee.ria.DigiDoc.utils.snackbar.SnackBarManager -import ee.ria.DigiDoc.utils.snackbar.SnackBarManager.showMessage -import ee.ria.DigiDoc.utilsLib.extensions.isCades -import ee.ria.DigiDoc.utilsLib.extensions.isXades -import ee.ria.DigiDoc.viewmodel.RecentDocumentsViewModel -import ee.ria.DigiDoc.viewmodel.shared.SharedContainerViewModel -import ee.ria.DigiDoc.viewmodel.shared.SharedMenuViewModel -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.Dispatchers.Main -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.File - -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) -@Composable -fun RecentDocumentsScreen( - modifier: Modifier = Modifier, - navController: NavHostController, - sharedMenuViewModel: SharedMenuViewModel, - sharedContainerViewModel: SharedContainerViewModel, - recentDocumentsViewModel: RecentDocumentsViewModel = hiltViewModel(), - fileOpeningMethod: FileOpeningMethod, -) { - val logTag = "RecentDocumentsScreen" - - val context = LocalContext.current - - val snackBarHostState = remember { SnackbarHostState() } - val snackBarScope = rememberCoroutineScope() - val scope = rememberCoroutineScope() - - val messages by SnackBarManager.messages.collectAsState(emptyList()) - - val showLoading = remember { mutableStateOf(false) } - val showSivaDialog = remember { mutableStateOf(false) } - val selectedDocument = remember { mutableStateOf(null) } - val isSettingsMenuBottomSheetVisible = rememberSaveable { mutableStateOf(false) } - - val handleResult: (Boolean) -> Unit = { confirmed -> - showLoading.value = true - val document = selectedDocument.value - - if (document == null) { - showLoading.value = false - } else { - scope.launch(IO) { - try { - val documentMimeType = recentDocumentsViewModel.getMimetype(document) - - recentDocumentsViewModel.handleDocument( - document, - documentMimeType ?: DEFAULT_CONTAINER_EXTENSION, - confirmed, - sharedContainerViewModel, - ) - } catch (ex: Exception) { - recentDocumentsViewModel.handleError(logTag, ex) - } finally { - showLoading.value = false - } - } - } - } - - val recentDocumentList = - remember { - mutableStateOf( - recentDocumentsViewModel.getRecentDocumentList(fileOpeningMethod), - ) - } - var actionDocument by remember { mutableStateOf(null) } - val openRemoveDocumentDialog = remember { mutableStateOf(false) } - - val documentRemoved = stringResource(id = R.string.document_removed) - val documentRemovalCancelled = stringResource(id = R.string.document_removal_cancelled) - - val removeDocumentDialogMessage = - stringResource(id = R.string.recent_documents_remove_confirmation_message) - val removeDocumentCancelButtonContentDescription = - stringResource(id = R.string.recent_documents_cancel_container_removal_button) - val removeDocumentOkButtonContentDescription = - stringResource(id = R.string.recent_documents_confirm_container_removal_button) - val closeDocumentDialog = { - openRemoveDocumentDialog.value = false - } - val dismissRemoveDocumentDialog = { - closeDocumentDialog() - sendAccessibilityEvent(context, getAccessibilityEventType(), documentRemovalCancelled) - } - - val listState = rememberLazyListState() - var expanded by rememberSaveable { mutableStateOf(false) } - val searchText by recentDocumentsViewModel.searchText.collectAsState() - val documentList by recentDocumentsViewModel.documentList.collectAsState() - - val dismissSearch = { - expanded = false - recentDocumentsViewModel.onSearchTextChange("") - } - - LaunchedEffect(recentDocumentsViewModel.sendToSigningViewWithSiva) { - recentDocumentsViewModel.sendToSigningViewWithSiva.asFlow().collect { openSigningView -> - if (openSigningView) { - recentDocumentsViewModel.handleSendToSigningViewWithSiva(false) - navController.navigate(Route.Signing.route) - } - } - } - - LaunchedEffect(recentDocumentsViewModel.errorState) { - recentDocumentsViewModel.errorState.asFlow().collect { error -> - error?.let { - showMessage(context, error) - } - } - } - - LaunchedEffect(messages) { - messages.forEach { message -> - snackBarScope.launch { - snackBarHostState.showSnackbar(message) - } - SnackBarManager.removeMessage(message) - } - } - - Scaffold( - snackbarHost = { - SnackbarHost( - modifier = modifier.padding(vertical = SPadding), - hostState = snackBarHostState, - ) - }, - modifier = - modifier - .semantics { - testTagsAsResourceId = true - }.testTag("recentDocumentsScreen"), - topBar = { - if (!expanded) { - TopBar( - modifier = modifier, - sharedMenuViewModel = sharedMenuViewModel, - title = null, - onLeftButtonClick = { - navController.navigateUp() - }, - onRightSecondaryButtonClick = { - isSettingsMenuBottomSheetVisible.value = true - }, - ) - } - }, - ) { paddingValues -> - SettingsMenuBottomSheet( - navController = navController, - isBottomSheetVisible = isSettingsMenuBottomSheetVisible, - ) - - Column( - modifier = - modifier - .padding(paddingValues) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - if (!expanded) { - Text( - text = stringResource(id = R.string.recent_documents_title), - maxLines = 2, - modifier = - modifier - .fillMaxWidth() - .padding(SPadding) - .semantics { heading() } - .focusable(enabled = true) - .focusTarget() - .focusProperties { canFocus = true }, - textAlign = TextAlign.Start, - style = MaterialTheme.typography.headlineSmall, - ) - } - val searchBarPadding = - if (!expanded) { - SPadding - } else { - zeroPadding - } - SearchBar( - modifier = - modifier - .padding(horizontal = searchBarPadding), - inputField = { - SearchBarDefaults.InputField( - modifier = - modifier - .fillMaxWidth() - .wrapContentHeight(), - query = searchText, - onQueryChange = recentDocumentsViewModel::onSearchTextChange, - onSearch = recentDocumentsViewModel::onSearchTextChange, - expanded = expanded, - enabled = true, - placeholder = { - PreventResize { - Text(stringResource(id = R.string.recent_documents_search)) - } - }, - leadingIcon = { - Icon( - modifier = - modifier - .size(iconSizeXXS), - imageVector = ImageVector.vectorResource(R.drawable.ic_m3_search_48dp_wght400), - contentDescription = null, - ) - }, - trailingIcon = { - IconButton( - modifier = - modifier - .padding(end = XSPadding) - .size(iconSizeXXS) - .testTag("searchCancelButton"), - onClick = dismissSearch, - content = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_m3_close_48dp_wght400), - contentDescription = - stringResource( - id = R.string.recent_documents_search_cancel, - ), - ) - }, - ) - }, - onExpandedChange = { expanded = it }, - colors = inputFieldColors(), - interactionSource = null, - ) - }, - expanded = expanded, - onExpandedChange = { expanded = it }, - ) { - LazyColumn( - state = listState, - modifier = modifier.testTag("lazyColumnScrollView"), - ) { - if (documentList.isNotEmpty()) { - item { - HorizontalDivider( - modifier = - modifier - .fillMaxWidth() - .padding(SPadding) - .height(dividerHeight), - ) - } - items(documentList) { document -> - Document( - name = document.name, - onItemClick = { - scope.launch(IO) { - selectedDocument.value = document - if (( - SEND_SIVA_CONTAINER_NOTIFICATION_MIMETYPES.contains( - recentDocumentsViewModel.getMimetype(document), - ) || - document.isCades(context) - ) && - !document.isXades(context) - ) { - showSivaDialog.value = true - } else { - showSivaDialog.value = false - - val container = - recentDocumentsViewModel.openDocument( - document, - true, - ) - - if (container is SignedContainer) { - sharedContainerViewModel.setSignedContainer( - container, - ) - - withContext(Main) { - navController.navigate( - Route.Signing.route, - ) - } - } else if (container is CryptoContainer) { - sharedContainerViewModel.setCryptoContainer( - container, - ) - - withContext(Main) { - navController.navigate( - Route.Encrypt.route, - ) - } - } - } - } - }, - onRemoveButtonClick = { - actionDocument = document - openRemoveDocumentDialog.value = true - }, - ) - HorizontalDivider( - modifier = - modifier - .fillMaxWidth() - .padding(SPadding) - .height(dividerHeight), - ) - } - } else { - item { - Box( - modifier = - modifier - .padding(SPadding), - contentAlignment = Alignment.Center, - ) { - Text( - modifier = - modifier - .testTag("recentDocumentsListEmpty"), - text = stringResource(id = R.string.recent_documents_search_empty), - style = MaterialTheme.typography.bodyLarge, - ) - } - } - } - - item { - Spacer( - modifier = modifier.height(invisibleElementHeight), - ) - if (listState.reachedBottom()) { - InvisibleElement(modifier = modifier) - } - } - } - } - if (!expanded) { - LazyColumn( - state = listState, - modifier = modifier.testTag("lazyColumnScrollView"), - ) { - if (recentDocumentList.value.isNotEmpty()) { - item { - HorizontalDivider( - modifier = - modifier - .fillMaxWidth() - .padding(SPadding) - .height(dividerHeight), - ) - } - items(recentDocumentList.value) { document -> - Document( - name = document.name, - onItemClick = { - scope.launch(IO) { - selectedDocument.value = document - if (( - SEND_SIVA_CONTAINER_NOTIFICATION_MIMETYPES.contains( - recentDocumentsViewModel.getMimetype(document), - ) || - document.isCades(context) - ) && - !document.isXades(context) - ) { - showSivaDialog.value = true - } else { - showSivaDialog.value = false - - val container = - recentDocumentsViewModel.openDocument( - document, - true, - ) - - if (container is SignedContainer) { - sharedContainerViewModel.setSignedContainer( - container, - ) - - withContext(Main) { - navController.navigate( - Route.Signing.route, - ) - } - } else if (container is CryptoContainer) { - sharedContainerViewModel.setCryptoContainer( - container, - ) - - withContext(Main) { - navController.navigate( - Route.Encrypt.route, - ) - } - } - } - } - }, - onRemoveButtonClick = { - actionDocument = document - openRemoveDocumentDialog.value = true - }, - ) - HorizontalDivider( - modifier = - modifier - .fillMaxWidth() - .padding(SPadding) - .height(dividerHeight), - ) - } - } else { - item { - Box( - modifier = - modifier - .padding(SPadding), - contentAlignment = Alignment.Center, - ) { - Text( - modifier = - modifier - .testTag("recentDocumentsListEmpty"), - text = stringResource(id = R.string.recent_documents_empty_message), - style = MaterialTheme.typography.bodyLarge, - ) - } - } - } - - item { - Spacer( - modifier = modifier.height(invisibleElementHeight), - ) - if (listState.reachedBottom()) { - InvisibleElement(modifier = modifier) - } - } - } - } - } - if (openRemoveDocumentDialog.value) { - MessageDialog( - modifier = modifier, - title = stringResource(R.string.document_remove_button), - message = removeDocumentDialogMessage, - showIcons = false, - dismissButtonText = stringResource(R.string.cancel_button), - confirmButtonText = stringResource(R.string.remove_title), - dismissButtonContentDescription = removeDocumentCancelButtonContentDescription, - confirmButtonContentDescription = removeDocumentOkButtonContentDescription, - onDismissRequest = dismissRemoveDocumentDialog, - onDismissButton = dismissRemoveDocumentDialog, - onConfirmButton = { - actionDocument?.delete() - recentDocumentList.value = recentDocumentsViewModel.getRecentDocumentList(fileOpeningMethod) - dismissSearch() - closeDocumentDialog() - sendAccessibilityEvent(context, getAccessibilityEventType(), documentRemoved) - }, - ) - } - - SivaConfirmationDialog( - showDialog = showSivaDialog, - modifier = modifier, - onResult = handleResult, - ) - - if (showLoading.value) { - LoadingScreen(modifier = modifier) - } - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun RecentDocumentsScreenPreview() { - RIADigiDocTheme { - RecentDocumentsScreen( - sharedMenuViewModel = hiltViewModel(), - sharedContainerViewModel = hiltViewModel(), - navController = rememberNavController(), - fileOpeningMethod = FileOpeningMethod.ALL, - ) - } -} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt index 5f29184c..e76a226a 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt @@ -189,7 +189,7 @@ fun EncryptNavigation( stringResource(id = R.string.document_remove_last_confirmation_message) } val closeContainerMessage = stringResource(id = R.string.crypto_close_container_message) - val removeContainerMessage = stringResource(id = R.string.remove_container) + val confirmCloseContainerMessage = stringResource(id = R.string.close_container) val saveContainerMessage = stringResource(id = R.string.container_save) val dismissRemoveFileDialog = { closeRemoveFileDialog() @@ -448,7 +448,7 @@ fun EncryptNavigation( } BackHandler { - if (!isNestedContainer && encryptViewModel.isEncryptedContainer(cryptoContainer)) { + if (!isNestedContainer && encryptViewModel.isSaveButtonShown(cryptoContainer)) { showContainerCloseConfirmationDialog.value = true } else { handleBackButtonClick( @@ -622,7 +622,7 @@ fun EncryptNavigation( }, leftIconContentDescription = R.string.crypto_close_container_title, onLeftButtonClick = { - if (!isNestedContainer && encryptViewModel.isEncryptedContainer(cryptoContainer)) { + if (!isNestedContainer && encryptViewModel.isSaveButtonShown(cryptoContainer)) { showContainerCloseConfirmationDialog.value = true } else { handleBackButtonClick( @@ -1106,13 +1106,7 @@ fun EncryptNavigation( !encryptViewModel.isEncryptedContainer(cryptoContainer) && !encryptViewModel.isDecryptedContainer(cryptoContainer), openEditContainerNameDialog = openEditContainerNameDialog, - isSaveButtonShown = ( - encryptViewModel.isEncryptedContainer(cryptoContainer) || - ( - encryptViewModel.isDecryptedContainer(cryptoContainer) && - cryptoContainer?.hasRecipients() == true - ) - ), + isSaveButtonShown = encryptViewModel.isSaveButtonShown(cryptoContainer), isSignButtonShown = !isNestedContainer && encryptViewModel.isEncryptedContainer(cryptoContainer), cryptoContainer = cryptoContainer, onSignClick = onSignActionClick, @@ -1138,36 +1132,49 @@ fun EncryptNavigation( } if (showContainerCloseConfirmationDialog.value) { + val isSaveContainerShown = encryptViewModel.isSaveButtonShown(cryptoContainer) + val dismissIcon = + if (isSaveContainerShown) { + R.drawable.ic_m3_download_48dp_wght400 + } else { + R.drawable.ic_m3_cancel_48dp_wght400 + } + val dismissButtonText = + if (isSaveContainerShown) { + stringResource(R.string.save) + } else { + stringResource(R.string.cancel_button) + } MessageDialog( modifier = modifier, title = stringResource(R.string.crypto_close_container_title), message = closeContainerMessage, showIcons = true, - dismissIcon = R.drawable.ic_m3_download_48dp_wght400, + dismissIcon = dismissIcon, confirmIcon = R.drawable.ic_m3_delete_48dp_wght400, - dismissButtonText = stringResource(R.string.save), - confirmButtonText = stringResource(R.string.remove_title), + dismissButtonText = dismissButtonText, + confirmButtonText = stringResource(R.string.close_title), dismissButtonContentDescription = saveContainerMessage, - confirmButtonContentDescription = removeContainerMessage, + confirmButtonContentDescription = confirmCloseContainerMessage, onDismissRequest = { showContainerCloseConfirmationDialog.value = false }, onDismissButton = { - val file = cryptoContainer?.file - if (file != null) { - saveFile( - file, - cryptoContainer?.containerMimetype(), - saveFileLauncher, - ) + if (isSaveContainerShown) { + val file = cryptoContainer?.file + if (file != null) { + saveFile( + file, + cryptoContainer?.containerMimetype(), + saveFileLauncher, + ) + } + } else { + showContainerCloseConfirmationDialog.value = false } }, onConfirmButton = { showContainerCloseConfirmationDialog.value = false - val containerFile = cryptoContainer?.file - if (containerFile?.exists() == true) { - containerFile.delete() - } sharedContainerViewModel.resetCryptoContainer() handleBackButtonClick(navController, encryptViewModel, sharedContainerViewModel) }, diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/OpenMenuBottomSheet.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/OpenMenuBottomSheet.kt deleted file mode 100644 index 89588b27..00000000 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/OpenMenuBottomSheet.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName", "FunctionName") - -package ee.ria.DigiDoc.ui.component.menu - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.testTagsAsResourceId -import ee.ria.DigiDoc.R -import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding - -// Menu component for the bottom sheet (Open document, add more files, sign document and encrypt document) -@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) -@Composable -fun OpenMenuBottomSheet( - modifier: Modifier = Modifier, - isBottomSheetVisible: MutableState = mutableStateOf(false), - @StringRes firstButtonStringRes: Int = R.string.main_menu_add_file, - @StringRes secondButtonStringRes: Int = R.string.main_menu_recent_documents, - @StringRes firstButtonStringResContentDescription: Int = R.string.main_menu_add_file_accessibility, - @StringRes secondButtonStringResContentDescription: Int = R.string.main_menu_recent_documents_accessibility, - @DrawableRes firstButtonIcon: Int = R.drawable.ic_m3_attach_file_48dp_wght400, - @DrawableRes secondButtonIcon: Int = R.drawable.ic_m3_folder_48dp_wght400, - firstButtonClick: () -> Unit = {}, - secondButtonClick: () -> Unit = {}, - testTag: String = "menuOpenBottomSheet", - firstButtonTestTag: String = "menuOpenAddFileButton", - secondButtonTestTag: String = "menuOpenRecentDocumentsButton", -) { - if (isBottomSheetVisible.value) { - ModalBottomSheet( - modifier = modifier, - containerColor = MaterialTheme.colorScheme.surfaceContainer, - contentColor = MaterialTheme.colorScheme.onSurface, - onDismissRequest = { isBottomSheetVisible.value = false }, - sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true), - ) { - Column( - modifier = - Modifier - .semantics { - testTagsAsResourceId = true - }.testTag(testTag) - .fillMaxWidth() - .padding(SPadding), - ) { - TwoButtonMenu( - modifier = modifier, - firstButtonStringRes = firstButtonStringRes, - secondButtonStringRes = secondButtonStringRes, - firstButtonStringResContentDescription = firstButtonStringResContentDescription, - secondButtonStringResContentDescription = secondButtonStringResContentDescription, - firstButtonIcon = firstButtonIcon, - secondButtonIcon = secondButtonIcon, - firstButtonClick = firstButtonClick, - secondButtonClick = secondButtonClick, - firstButtonTestTag = firstButtonTestTag, - secondButtonTestTag = secondButtonTestTag, - ) - } - } - } -} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/TwoButtonMenu.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/TwoButtonMenu.kt deleted file mode 100644 index 25a9da22..00000000 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/TwoButtonMenu.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName", "FunctionName") - -package ee.ria.DigiDoc.ui.component.menu - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier - -@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class) -@Composable -fun TwoButtonMenu( - modifier: Modifier = Modifier, - @StringRes firstButtonStringRes: Int, - @StringRes secondButtonStringRes: Int, - @StringRes firstButtonStringResContentDescription: Int, - @StringRes secondButtonStringResContentDescription: Int, - @DrawableRes firstButtonIcon: Int, - @DrawableRes secondButtonIcon: Int, - firstButtonClick: () -> Unit = {}, - secondButtonClick: () -> Unit = {}, - firstButtonTestTag: String, - secondButtonTestTag: String, -) { - MenuButton( - modifier = modifier, - buttonStringRes = firstButtonStringRes, - buttonIcon = firstButtonIcon, - buttonStringResContentDescription = firstButtonStringResContentDescription, - buttonClick = firstButtonClick, - testTag = firstButtonTestTag, - ) - MenuButton( - modifier = modifier, - buttonStringRes = secondButtonStringRes, - buttonIcon = secondButtonIcon, - buttonStringResContentDescription = secondButtonStringResContentDescription, - buttonClick = secondButtonClick, - testTag = secondButtonTestTag, - ) -} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/Document.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/Document.kt deleted file mode 100644 index 28c8475c..00000000 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/Document.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName", "FunctionName") - -package ee.ria.DigiDoc.ui.component.shared - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.Preview -import androidx.constraintlayout.compose.ConstraintLayout -import ee.ria.DigiDoc.R -import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding -import ee.ria.DigiDoc.ui.theme.Dimensions.XSPadding -import ee.ria.DigiDoc.ui.theme.Dimensions.iconSizeXXS -import ee.ria.DigiDoc.ui.theme.RIADigiDocTheme -import ee.ria.DigiDoc.utils.accessibility.AccessibilityUtil.Companion.formatNumbers - -@Composable -fun Document( - modifier: Modifier = Modifier, - name: String, - onItemClick: () -> Unit = {}, - onRemoveButtonClick: () -> Unit = {}, -) { - val documentTitle = stringResource(id = R.string.document) - val textColor = MaterialTheme.colorScheme.inverseSurface.toArgb() - ConstraintLayout( - modifier = - modifier - .padding(vertical = XSPadding) - .wrapContentHeight() - .fillMaxWidth() - .semantics { - this.contentDescription = "$documentTitle ${formatNumbers(name).lowercase()}" - }.focusable(true) - .clickable(onClick = onItemClick) - .testTag("recentDocumentsItem"), - ) { - val ( - folderIcon, - documentText, - removeIcon, - ) = createRefs() - Icon( - modifier = - modifier - .padding(start = SPadding, end = XSPadding) - .size(iconSizeXXS) - .constrainAs(folderIcon) { - start.linkTo(parent.start) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - }, - imageVector = ImageVector.vectorResource(R.drawable.ic_m3_folder_48dp_wght400), - contentDescription = null, - ) - MiddleEllipsizeMultilineText( - modifier = - modifier - .wrapContentSize() - .padding(end = iconSizeXXS * 2 + XSPadding * 2 + SPadding * 2) - .constrainAs(documentText) { - start.linkTo(folderIcon.end) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - }.focusable(false) - .testTag("recentDocumentsItemName"), - text = name, - maxLines = 4, - textColor = textColor, - ) - IconButton( - modifier = - modifier - .padding(end = SPadding) - .size(iconSizeXXS) - .constrainAs(removeIcon) { - end.linkTo(parent.end) - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - }.testTag("recentDocumentsItemRemoveButton"), - onClick = onRemoveButtonClick, - content = { - Icon( - imageVector = ImageVector.vectorResource(R.drawable.ic_m3_delete_48dp_wght400), - contentDescription = "${ - stringResource( - id = R.string.recent_documents_remove_button, - ) - } ${formatNumbers(name).lowercase()}", - ) - }, - ) - } -} - -@Preview(showBackground = true) -@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun DocumentPreview() { - RIADigiDocTheme { - Surface( - modifier = Modifier.background(MaterialTheme.colorScheme.background), - color = MaterialTheme.colorScheme.background, - ) { - Column { - Document( - name = "test-container.asice", - ) - Document( - name = "some-" + "very-".repeat(30) + "long-document-name-document.bdoc", - ) - Document( - name = "some-" + "very-".repeat(40) + "long-document-name-document.ddoc", - ) - } - } - } -} 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 8f259988..b816c790 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,6 +181,8 @@ fun SigningNavigation( val isNestedContainer = sharedContainerViewModel.isNestedContainer(signedContainer) val isXadesContainer = signedContainer?.isXades() == true val isCadesContainer = signedContainer?.isCades() == true + val isSignedContainer = signedContainer?.isSigned() == true + val isSaveContainerShown = remember { mutableStateOf(isSignedContainer) } var validSignaturesCount by remember { mutableIntStateOf(0) } var unknownSignaturesCount by remember { mutableIntStateOf(0) } @@ -219,7 +221,7 @@ fun SigningNavigation( stringResource(id = R.string.document_remove_last_confirmation_message) } val closeContainerMessage = stringResource(id = R.string.signing_close_container_message) - val removeContainerMessage = stringResource(id = R.string.remove_container) + val confirmCloseContainerMessage = stringResource(id = R.string.close_container) val saveContainerMessage = stringResource(id = R.string.container_save) val dismissRemoveFileDialog = { closeRemoveFileDialog() @@ -464,7 +466,7 @@ fun SigningNavigation( } BackHandler { - if (!isNestedContainer) { + if (!isNestedContainer && isSaveContainerShown.value == true) { showContainerCloseConfirmationDialog.value = true } else { handleBackButtonClick( @@ -590,6 +592,9 @@ fun SigningNavigation( if (newTime >= (pastTime + 2 * 1000)) { sendAccessibilityEvent(context, getAccessibilityEventType(), signaturesLoaded) } + if (isSaveContainerShown.value != true) { + isSaveContainerShown.value = signatures.count() > 0 + } } } @@ -703,7 +708,7 @@ fun SigningNavigation( }, leftIconContentDescription = R.string.signing_close_container_title, onLeftButtonClick = { - if (!isNestedContainer) { + if (!isNestedContainer && isSaveContainerShown.value == true) { showContainerCloseConfirmationDialog.value = true } else { handleBackButtonClick( @@ -798,6 +803,7 @@ fun SigningNavigation( ) } } + isSaveContainerShown.value = true showMessage(signatureAddedSuccessText) signatureAddedSuccess.value = false } @@ -1046,6 +1052,7 @@ fun SigningNavigation( ) } openEditContainerNameDialog.value = false + isSaveContainerShown.value = true sendAccessibilityEvent( context, getAccessibilityEventType(), @@ -1107,6 +1114,7 @@ fun SigningNavigation( } } } + isSaveContainerShown.value = true closeRemoveFileDialog() sendAccessibilityEvent(context, getAccessibilityEventType(), fileRemoved) }, @@ -1151,6 +1159,7 @@ fun SigningNavigation( actionSignature, ) } + isSaveContainerShown.value = true closeSignatureDialog() sendAccessibilityEvent(context, getAccessibilityEventType(), signatureRemoved) }, @@ -1196,6 +1205,7 @@ fun SigningNavigation( ContainerBottomSheet( modifier = modifier, showSheet = showContainerBottomSheet, + isSaveButtonShown = isSaveContainerShown.value, isEditContainerButtonShown = signingViewModel.isBottomContainerButtonShown( signedContainer, @@ -1249,36 +1259,48 @@ fun SigningNavigation( } if (showContainerCloseConfirmationDialog.value) { + val dismissIcon = + if (isSaveContainerShown.value == true) { + R.drawable.ic_m3_download_48dp_wght400 + } else { + R.drawable.ic_m3_cancel_48dp_wght400 + } + val dismissButtonText = + if (isSaveContainerShown.value == true) { + stringResource(R.string.save) + } else { + stringResource(R.string.cancel_button) + } MessageDialog( modifier = modifier, title = stringResource(R.string.signing_close_container_title), message = closeContainerMessage, showIcons = true, - dismissIcon = R.drawable.ic_m3_download_48dp_wght400, + dismissIcon = dismissIcon, confirmIcon = R.drawable.ic_m3_delete_48dp_wght400, - dismissButtonText = stringResource(R.string.save), - confirmButtonText = stringResource(R.string.remove_title), + dismissButtonText = dismissButtonText, + confirmButtonText = stringResource(R.string.close_title), dismissButtonContentDescription = saveContainerMessage, - confirmButtonContentDescription = removeContainerMessage, + confirmButtonContentDescription = confirmCloseContainerMessage, onDismissRequest = { showContainerCloseConfirmationDialog.value = false }, onDismissButton = { - val file = signedContainer?.getContainerFile() - if (file != null) { - saveFile( - file, - signedContainer?.containerMimetype(), - saveFileLauncher, - ) + if (isSaveContainerShown.value == true) { + val file = signedContainer?.getContainerFile() + if (file != null) { + saveFile( + file, + signedContainer?.containerMimetype(), + saveFileLauncher, + ) + } + } else { + showContainerCloseConfirmationDialog.value = false } }, onConfirmButton = { showContainerCloseConfirmationDialog.value = false - val containerFile = signedContainer?.getContainerFile() - if (containerFile?.exists() == true) { - containerFile.delete() - } sharedContainerViewModel.resetSignedContainer() sharedContainerViewModel.resetContainerNotifications() handleBackButtonClick(navController, signingViewModel, sharedContainerViewModel) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt index c7a76774..3ef95b79 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt @@ -37,6 +37,7 @@ import java.io.File fun ContainerBottomSheet( modifier: Modifier, showSheet: MutableState, + isSaveButtonShown:Boolean = false, isEditContainerButtonShown: Boolean = true, openEditContainerNameDialog: MutableState, isEncryptButtonShown: Boolean = true, @@ -66,6 +67,7 @@ fun ContainerBottomSheet( openEditContainerNameDialog.value = true }, BottomSheetButton( + showButton = isSaveButtonShown, icon = R.drawable.ic_m3_download_48dp_wght400, text = stringResource(R.string.container_save), contentDescription = "${stringResource( diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt index 4aeab42b..5b5fc834 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt @@ -48,9 +48,6 @@ object Constant { const val SIGNER_DETAIL_SCREEN = "signer_detail_route" const val CERTIFICATE_DETAIL_SCREEN = "certificate_detail_route" const val RECIPIENT_DETAIL_SCREEN = "recipient_detail_route" - const val RECENT_DOCUMENTS_SCREEN = "recent_documents_route" - const val RECENT_DOCUMENTS_SCREEN_FROM_SIGNING_SCREEN = "recent_documents_route_from_signing_screen" - const val RECENT_DOCUMENTS_FROM_ENCRYPT_SCREEN = "recent_documents_from_encrypt_route" const val SETTINGS_SCREEN = "settings_route" const val SETTINGS_LANGUAGE_CHOOSER_SCREEN = "settings_language_chooser_route" const val SETTINGS_THEME_CHOOSER_SCREEN = "settings_theme_chooser_route" diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt index a81e6362..2c7aa954 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt @@ -42,9 +42,6 @@ import ee.ria.DigiDoc.utils.Constant.Routes.MYEID_IDENTIFICATION_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.MYEID_PIN_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.MYEID_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.PROXY_SERVICES_SCREEN -import ee.ria.DigiDoc.utils.Constant.Routes.RECENT_DOCUMENTS_FROM_ENCRYPT_SCREEN -import ee.ria.DigiDoc.utils.Constant.Routes.RECENT_DOCUMENTS_SCREEN -import ee.ria.DigiDoc.utils.Constant.Routes.RECENT_DOCUMENTS_SCREEN_FROM_SIGNING_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.RECIPIENT_DETAIL_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.ROOT_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.SETTINGS_LANGUAGE_CHOOSER_SCREEN @@ -98,12 +95,6 @@ sealed class Route( data object RecipientDetail : Route(RECIPIENT_DETAIL_SCREEN) - data object RecentDocuments : Route(RECENT_DOCUMENTS_SCREEN) - - data object RecentDocumentsFromSigning : Route(RECENT_DOCUMENTS_SCREEN_FROM_SIGNING_SCREEN) - - data object RecentDocumentsFromEncrypt : Route(RECENT_DOCUMENTS_FROM_ENCRYPT_SCREEN) - data object Settings : Route(SETTINGS_SCREEN) data object SettingsLanguageChooser : Route(SETTINGS_LANGUAGE_CHOOSER_SCREEN) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt index f20387f0..df1aab9b 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt @@ -35,8 +35,6 @@ import ee.ria.DigiDoc.common.Constant.CDOC1_EXTENSION import ee.ria.DigiDoc.cryptolib.CDOC2Settings import ee.ria.DigiDoc.cryptolib.CryptoContainer 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.utilsLib.container.ContainerUtil import ee.ria.DigiDoc.utilsLib.container.ContainerUtil.createContainerAction import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog @@ -52,7 +50,6 @@ import javax.inject.Inject class EncryptViewModel @Inject constructor( - private val sivaRepository: SivaRepository, private val mimeTypeResolver: MimeTypeResolver, private val contentResolver: ContentResolver, private val fileOpeningRepository: FileOpeningRepository, @@ -92,6 +89,11 @@ class EncryptViewModel ) && isDataFilesInContainer(cryptoContainer) + fun isSaveButtonShown(cryptoContainer: CryptoContainer?): Boolean { + return (isEncryptedContainer(cryptoContainer) || (isDecryptedContainer(cryptoContainer) + && cryptoContainer?.hasRecipients() == true)) + } + fun isSignButtonShown( cryptoContainer: CryptoContainer?, isNestedContainer: Boolean, @@ -150,20 +152,7 @@ class EncryptViewModel fun getMimetype(file: File): String? = mimeTypeResolver.mimeType(file) - suspend fun getTimestampedContainer( - context: Context, - signedContainer: SignedContainer, - ): SignedContainer { - if (sivaRepository.isTimestampedContainer(signedContainer) && - !signedContainer.isXades() - ) { - return sivaRepository.getTimestampedContainer(context, signedContainer) - } - - return signedContainer - } - - @Throws(Exception::class) + @Throws(Exception::class) suspend fun openSignedContainer( context: Context, container: CryptoContainer?, diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModel.kt deleted file mode 100644 index 1847ae0f..00000000 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/RecentDocumentsViewModel.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -@file:Suppress("PackageName") - -package ee.ria.DigiDoc.viewmodel - -import android.content.Context -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext -import ee.ria.DigiDoc.R -import ee.ria.DigiDoc.common.Constant.ASICS_MIMETYPE -import ee.ria.DigiDoc.common.Constant.DDOC_MIMETYPE -import ee.ria.DigiDoc.common.container.Container -import ee.ria.DigiDoc.common.exception.NoInternetConnectionException -import ee.ria.DigiDoc.common.model.FileOpeningMethod -import ee.ria.DigiDoc.cryptolib.CDOC2Settings -import ee.ria.DigiDoc.cryptolib.CryptoContainer -import ee.ria.DigiDoc.domain.repository.siva.SivaRepository -import ee.ria.DigiDoc.libdigidoclib.SignedContainer -import ee.ria.DigiDoc.utilsLib.container.ContainerUtil -import ee.ria.DigiDoc.utilsLib.extensions.isCades -import ee.ria.DigiDoc.utilsLib.extensions.isCryptoContainer -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.SharingStarted -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.withContext -import java.io.File -import java.io.IOException -import javax.inject.Inject - -@HiltViewModel -class RecentDocumentsViewModel - @Inject - constructor( - @param:ApplicationContext private val context: Context, - private val sivaRepository: SivaRepository, - private val mimeTypeResolver: MimeTypeResolver, - private val cdoc2Settings: CDOC2Settings, - ) : ViewModel() { - private val logTag = "RecentDocumentsViewModel" - private val _sendToSigningViewWithSiva = MutableLiveData(false) - val sendToSigningViewWithSiva: LiveData = _sendToSigningViewWithSiva - - private val _errorState = MutableLiveData(null) - val errorState: LiveData = _errorState - private val _searchText = MutableStateFlow("") - val searchText = _searchText.asStateFlow() - - private val _documentList = MutableStateFlow(getRecentDocumentList()) - val documentList = filterDocuments() - - private fun filterDocuments() = - searchText - .combine(_documentList) { text, documents -> - documents.filter { document -> - document.name.uppercase().contains(text.trim().uppercase()) - } - }.stateIn( - scope = viewModelScope, - // It will allow the StateFlow survive 5 seconds before it been canceled - started = SharingStarted.WhileSubscribed(5000), - initialValue = _documentList.value, - ) - - suspend fun openDocument( - document: File, - isSivaConfirmed: Boolean, - ): Container? { - return try { - if (!document.isCryptoContainer()) { - openSignatureDocument(document, isSivaConfirmed) - } else { - openCryptoDocument(document) - } - } catch (e: Exception) { - errorLog(logTag, "Unable to open container", e) - _errorState.postValue(R.string.container_load_error) - return null - } - } - - @Throws(Exception::class) - suspend fun openSignatureDocument( - document: File, - isSivaConfirmed: Boolean, - ): SignedContainer { - val signedContainer = SignedContainer.openOrCreate(context, document, listOf(document), isSivaConfirmed) - if (sivaRepository.isTimestampedContainer(signedContainer) && - !signedContainer.isXades() - ) { - return sivaRepository.getTimestampedContainer(context, signedContainer) - } - - return SignedContainer.openOrCreate(context, document, listOf(document), isSivaConfirmed) - } - - @Throws(Exception::class) - suspend fun openCryptoDocument(document: File): CryptoContainer = - CryptoContainer.openOrCreate(context, document, listOf(document), cdoc2Settings) - - fun getRecentDocumentList(fileOpeningMethod: FileOpeningMethod = FileOpeningMethod.ALL): List = - ContainerUtil.findRecentContainerFiles(context, fileOpeningMethod) - - suspend fun handleDocument( - document: File, - mimeType: String, - confirmed: Boolean, - sharedContainerViewModel: SharedContainerViewModel, - ) { - val isCades = document.isCades(context) - val isAsicsOrConfirmedDdoc = mimeType == ASICS_MIMETYPE || (mimeType == DDOC_MIMETYPE && confirmed) - - if (isAsicsOrConfirmedDdoc || isCades) { - val signedContainer = openSignatureDocument(document, confirmed) - sharedContainerViewModel.setSignedContainer(signedContainer) - - handleSendToSigningViewWithSiva(true) - } - } - - fun handleSendToSigningViewWithSiva(sendToSigningView: Boolean) { - _sendToSigningViewWithSiva.postValue(sendToSigningView) - } - - suspend fun handleError( - logTag: String, - ex: Exception, - ) { - errorLog(logTag, "Unable to open container from recent documents", ex) - - var errorMessage: Int? = R.string.error_general_client - - withContext(Main) { - val exceptionMessage = ex.message ?: "" - if (ex is IOException && - exceptionMessage.isNotEmpty() && - exceptionMessage.contains("Online validation disabled") - ) { - errorMessage = null - return@withContext - } - - if (ex is NoInternetConnectionException) { - errorMessage = R.string.no_internet_connection - } - - _errorState.postValue(errorMessage) - } - } - - fun getMimetype(file: File): String? = mimeTypeResolver.mimeType(file) - - fun onSearchTextChange(text: String) { - _searchText.value = text - _documentList.value = getRecentDocumentList() - } - } diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0164a115..bc5a4bc1 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -40,7 +40,9 @@ Fail salvestatud Faili salvestamine ebaõnnestus Eemalda + Sulge Eemalda ümbrik + Sulge ümbrik Eemalda fail Salvesta fail Soovid eemaldada faili ümbrikust? @@ -127,18 +129,6 @@ Oled sulgemas ümbrikut. Palun vali, mida ümbrikuga edasi teha: Ümbriku teavitused - - Dokument - Otsi ümbrikku… - Katkesta ümbriku otsing - Hiljutisi ümbrikke ei leitud - Hiljutised ümbrikud - Ümbrikud puuduvad - Eemalda ümbrik - Soovid eemaldada ümbriku? - Katkesta ümbriku eemaldamine - Kinnita ümbriku eemaldamine - Seda tüüpi allkirjastatud dokument edastatakse digitaalallkirjade kehtivuse kontrollimiseks valideerimisteenusele SiVa. Digitaalallkirjade kehtivuse kontrollimisel edastatud andmete kohta loe lähemalt veebilehelt siit. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 37d81193..f7128dfb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,7 +40,9 @@ File saved Failed to save file Remove + Close Remove container + Close container Remove file Save file Remove file from container? @@ -127,18 +129,6 @@ You are about to close the container. Please choose what to do with the container: Container notifications - - Document - Search container… - Cancel search container - No recent containers found - Recent containers - No recent containers - Remove container - Remove container? - Cancel container removal - Confirm container removal - This type of signed document will be transmitted to the Digital Signature Validation Service SiVa to verify the validity of the digital signature. Read more information about transmitted data to Digital Signature Validation Service on the website here. From 8bd48332d7ea8462e3b53f017c63481f8e3d8cc9 Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Mon, 19 Jan 2026 16:20:00 +0200 Subject: [PATCH 2/9] MOPPAND-1738 KtLint fix. --- .../signing/bottomsheet/ContainerBottomSheet.kt | 2 +- .../ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt index 3ef95b79..0a4f040e 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt @@ -37,7 +37,7 @@ import java.io.File fun ContainerBottomSheet( modifier: Modifier, showSheet: MutableState, - isSaveButtonShown:Boolean = false, + isSaveButtonShown: Boolean = false, isEditContainerButtonShown: Boolean = true, openEditContainerNameDialog: MutableState, isEncryptButtonShown: Boolean = true, diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt index df1aab9b..9d83d93c 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt @@ -89,10 +89,14 @@ class EncryptViewModel ) && isDataFilesInContainer(cryptoContainer) - fun isSaveButtonShown(cryptoContainer: CryptoContainer?): Boolean { - return (isEncryptedContainer(cryptoContainer) || (isDecryptedContainer(cryptoContainer) - && cryptoContainer?.hasRecipients() == true)) - } + fun isSaveButtonShown(cryptoContainer: CryptoContainer?): Boolean = + ( + isEncryptedContainer(cryptoContainer) || + ( + isDecryptedContainer(cryptoContainer) && + cryptoContainer?.hasRecipients() == true + ) + ) fun isSignButtonShown( cryptoContainer: CryptoContainer?, @@ -152,7 +156,7 @@ class EncryptViewModel fun getMimetype(file: File): String? = mimeTypeResolver.mimeType(file) - @Throws(Exception::class) + @Throws(Exception::class) suspend fun openSignedContainer( context: Context, container: CryptoContainer?, From 0268404778c88fc030417215ed087ace17b7e67c Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Tue, 20 Jan 2026 11:46:37 +0200 Subject: [PATCH 3/9] MOPPAND-1738 Remove duplicate strings and clean up container folders on app start and on closing of container. --- .../kotlin/ee/ria/DigiDoc/MainActivity.kt | 4 +++ .../ui/component/crypto/EncryptNavigation.kt | 8 +++-- .../ui/component/signing/SigningNavigation.kt | 8 +++-- app/src/main/res/values-et/strings.xml | 2 -- app/src/main/res/values/strings.xml | 2 -- .../utilsLib/container/ContainerUtil.kt | 30 +++++++++++++++++++ 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt index b9df6878..9882fe51 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt @@ -47,6 +47,7 @@ import ee.ria.DigiDoc.utils.locale.LocaleUtilImpl import ee.ria.DigiDoc.utils.secure.SecureUtil import ee.ria.DigiDoc.utilsLib.R.string.main_diagnostics_logging_key import ee.ria.DigiDoc.utilsLib.R.string.main_diagnostics_logging_running_key +import ee.ria.DigiDoc.utilsLib.container.ContainerUtil import ee.ria.DigiDoc.utilsLib.file.FileUtil.getExternalFileUris import ee.ria.DigiDoc.utilsLib.file.FileUtil.getLogsDirectory import ee.ria.DigiDoc.utilsLib.locale.LocaleUtil.getLocale @@ -167,6 +168,9 @@ class MainActivity : } } } + + ContainerUtil.removeSignatureContainersDir(applicationContext) + ContainerUtil.removeCryptoContainersDir(applicationContext) } override fun attachBaseContext(newBase: Context?) { diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt index e76a226a..488fc9c6 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt @@ -189,7 +189,7 @@ fun EncryptNavigation( stringResource(id = R.string.document_remove_last_confirmation_message) } val closeContainerMessage = stringResource(id = R.string.crypto_close_container_message) - val confirmCloseContainerMessage = stringResource(id = R.string.close_container) + val confirmCloseContainerMessage = stringResource(id = R.string.crypto_close_container_title) val saveContainerMessage = stringResource(id = R.string.container_save) val dismissRemoveFileDialog = { closeRemoveFileDialog() @@ -1153,7 +1153,7 @@ fun EncryptNavigation( dismissIcon = dismissIcon, confirmIcon = R.drawable.ic_m3_delete_48dp_wght400, dismissButtonText = dismissButtonText, - confirmButtonText = stringResource(R.string.close_title), + confirmButtonText = stringResource(R.string.close_button), dismissButtonContentDescription = saveContainerMessage, confirmButtonContentDescription = confirmCloseContainerMessage, onDismissRequest = { @@ -1175,6 +1175,10 @@ fun EncryptNavigation( }, onConfirmButton = { showContainerCloseConfirmationDialog.value = false + val containerFile = cryptoContainer?.file + if (containerFile?.exists() == true) { + containerFile.delete() + } sharedContainerViewModel.resetCryptoContainer() handleBackButtonClick(navController, encryptViewModel, sharedContainerViewModel) }, 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 b816c790..743e8057 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 @@ -221,7 +221,7 @@ fun SigningNavigation( stringResource(id = R.string.document_remove_last_confirmation_message) } val closeContainerMessage = stringResource(id = R.string.signing_close_container_message) - val confirmCloseContainerMessage = stringResource(id = R.string.close_container) + val confirmCloseContainerMessage = stringResource(id = R.string.signing_close_container_title) val saveContainerMessage = stringResource(id = R.string.container_save) val dismissRemoveFileDialog = { closeRemoveFileDialog() @@ -1279,7 +1279,7 @@ fun SigningNavigation( dismissIcon = dismissIcon, confirmIcon = R.drawable.ic_m3_delete_48dp_wght400, dismissButtonText = dismissButtonText, - confirmButtonText = stringResource(R.string.close_title), + confirmButtonText = stringResource(R.string.close_button), dismissButtonContentDescription = saveContainerMessage, confirmButtonContentDescription = confirmCloseContainerMessage, onDismissRequest = { @@ -1301,6 +1301,10 @@ fun SigningNavigation( }, onConfirmButton = { showContainerCloseConfirmationDialog.value = false + val containerFile = signedContainer?.getContainerFile() + if (containerFile?.exists() == true) { + containerFile.delete() + } sharedContainerViewModel.resetSignedContainer() sharedContainerViewModel.resetContainerNotifications() handleBackButtonClick(navController, signingViewModel, sharedContainerViewModel) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index bc5a4bc1..15d580f4 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -40,9 +40,7 @@ Fail salvestatud Faili salvestamine ebaõnnestus Eemalda - Sulge Eemalda ümbrik - Sulge ümbrik Eemalda fail Salvesta fail Soovid eemaldada faili ümbrikust? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f7128dfb..0368c43f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -40,9 +40,7 @@ File saved Failed to save file Remove - Close Remove container - Close container Remove file Save file Remove file from container? diff --git a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt index 2ff4e8db..378bba96 100644 --- a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt +++ b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt @@ -282,6 +282,36 @@ object ContainerUtil { return dir } + fun removeSignatureContainersDir(context: Context): Boolean { + val dir = File(context.filesDir, DIR_SIGNATURE_CONTAINERS) + return deleteDirSafely(dir) + } + + fun removeCryptoContainersDir(context: Context): Boolean { + val dir = File(context.filesDir, DIR_CRYPTO_CONTAINERS) + return deleteDirSafely(dir) + } + + private fun deleteDirSafely(dir: File): Boolean { + if (!dir.exists()) { + debugLog(LOG_TAG, "Directory does not exist: ${dir.path}") + return true + } + + return try { + dir.deleteRecursively().also { success -> + if (success) { + debugLog(LOG_TAG, "Directory removed: ${dir.path}") + } else { + debugLog(LOG_TAG, "Failed to remove directory: ${dir.path}") + } + } + } catch (e: Exception) { + debugLog(LOG_TAG, "Error removing directory ${dir.path}: ${e.message}", e) + false + } + } + fun removeExtensionFromContainerFilename(filename: String): String = FilenameUtils.removeExtension(filename) fun addExtensionToContainerFilename(filename: String): String { From df41f6ced5553871128cc7873fb351f769db3e6e Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Tue, 20 Jan 2026 13:45:48 +0200 Subject: [PATCH 4/9] MOPPAND-1738 Code review suggestions. --- app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt | 6 +++--- .../ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt index 9882fe51..d31162dc 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt @@ -160,6 +160,9 @@ class MainActivity : fileTypeSetup.initializeApplicationFileTypesAssociation(componentClassName) librarySetup.setupLibraries(applicationContext, isLoggingEnabled) + ContainerUtil.removeSignatureContainersDir(applicationContext) + ContainerUtil.removeCryptoContainersDir(applicationContext) + isAppReady = true setContent { @@ -168,9 +171,6 @@ class MainActivity : } } } - - ContainerUtil.removeSignatureContainersDir(applicationContext) - ContainerUtil.removeCryptoContainersDir(applicationContext) } override fun attachBaseContext(newBase: Context?) { diff --git a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt index 378bba96..88cb73b9 100644 --- a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt +++ b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/container/ContainerUtil.kt @@ -284,15 +284,15 @@ object ContainerUtil { fun removeSignatureContainersDir(context: Context): Boolean { val dir = File(context.filesDir, DIR_SIGNATURE_CONTAINERS) - return deleteDirSafely(dir) + return deleteDir(dir) } fun removeCryptoContainersDir(context: Context): Boolean { val dir = File(context.filesDir, DIR_CRYPTO_CONTAINERS) - return deleteDirSafely(dir) + return deleteDir(dir) } - private fun deleteDirSafely(dir: File): Boolean { + private fun deleteDir(dir: File): Boolean { if (!dir.exists()) { debugLog(LOG_TAG, "Directory does not exist: ${dir.path}") return true From b51525a1e30c308695cddd3881b4190459252256 Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Thu, 22 Jan 2026 16:24:22 +0200 Subject: [PATCH 5/9] MOPPAND-1738 Fixes after testing phase 1 and fix LDAP search in case of error with one of search providers. --- .../ee/ria/DigiDoc/RIADigiDocAppNavigation.kt | 10 +++ .../ria/DigiDoc/fragment/EncryptFragment.kt | 2 + .../fragment/screen/EncryptRecipientScreen.kt | 4 +- .../DigiDoc/fragment/screen/EncryptScreen.kt | 2 + .../ui/component/crypto/EncryptNavigation.kt | 78 ++++++++++++++----- .../ui/component/signing/SigningNavigation.kt | 71 ++++++++++------- .../kotlin/ee/ria/DigiDoc/utils/Constant.kt | 1 + .../main/kotlin/ee/ria/DigiDoc/utils/Route.kt | 3 + .../repository/RecipientRepositoryImpl.kt | 16 +++- 9 files changed, 137 insertions(+), 50 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt index 89a7180d..ed1b1f63 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt @@ -180,6 +180,16 @@ fun RIADigiDocAppScreen(externalFileUris: List) { sharedRecipientViewModel = sharedRecipientViewModel, ) } + composable(route = Route.Encrypted.route) { + EncryptFragment( + modifier = Modifier.safeDrawingPadding(), + navController = navController, + sharedMenuViewModel = sharedMenuViewModel, + sharedContainerViewModel = sharedContainerViewModel, + sharedRecipientViewModel = sharedRecipientViewModel, + withEncryption = true, + ) + } composable(route = Route.DecryptScreen.route) { DecryptFragment( modifier = Modifier.safeDrawingPadding(), diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/EncryptFragment.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/EncryptFragment.kt index d2979525..3683a7d9 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/EncryptFragment.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/EncryptFragment.kt @@ -50,6 +50,7 @@ fun EncryptFragment( sharedMenuViewModel: SharedMenuViewModel, sharedContainerViewModel: SharedContainerViewModel, sharedRecipientViewModel: SharedRecipientViewModel, + withEncryption: Boolean = false, ) { Surface( modifier = @@ -67,6 +68,7 @@ fun EncryptFragment( sharedMenuViewModel = sharedMenuViewModel, sharedContainerViewModel = sharedContainerViewModel, sharedRecipientViewModel = sharedRecipientViewModel, + withEncryption = withEncryption, ) } } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptRecipientScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptRecipientScreen.kt index 00b6161d..e7900c69 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptRecipientScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptRecipientScreen.kt @@ -219,7 +219,7 @@ fun EncryptRecipientScreen( encryptRecipientViewModel.handleIsContainerEncrypted(false) containerEncryptedSuccess.value = false - navController.navigate(Route.Encrypt.route) { + navController.navigate(Route.Encrypted.route) { popUpTo(Route.Home.route) { inclusive = false } @@ -607,7 +607,7 @@ fun EncryptRecipientScreen( ) } - if (showLoading.value == true) { + if (showLoading.value) { LoadingScreen(modifier = modifier) } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptScreen.kt index 7ad4a6bc..4a0ad6a8 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/EncryptScreen.kt @@ -41,6 +41,7 @@ fun EncryptScreen( sharedMenuViewModel: SharedMenuViewModel, sharedContainerViewModel: SharedContainerViewModel, sharedRecipientViewModel: SharedRecipientViewModel, + withEncryption: Boolean = false, ) { EncryptNavigation( modifier = modifier, @@ -48,6 +49,7 @@ fun EncryptScreen( sharedMenuViewModel = sharedMenuViewModel, sharedContainerViewModel = sharedContainerViewModel, sharedRecipientViewModel = sharedRecipientViewModel, + withEncryption = withEncryption, ) } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt index 488fc9c6..c6da01a6 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt @@ -118,6 +118,7 @@ import ee.ria.DigiDoc.utils.accessibility.AccessibilityUtil.Companion.sendAccess import ee.ria.DigiDoc.utils.extensions.reachedBottom import ee.ria.DigiDoc.utils.snackbar.SnackBarManager import ee.ria.DigiDoc.utils.snackbar.SnackBarManager.showMessage +import ee.ria.DigiDoc.utilsLib.container.ContainerUtil import ee.ria.DigiDoc.utilsLib.container.ContainerUtil.createContainerAction import ee.ria.DigiDoc.utilsLib.container.ContainerUtil.removeExtensionFromContainerFilename import ee.ria.DigiDoc.utilsLib.extensions.isContainer @@ -149,6 +150,7 @@ fun EncryptNavigation( signingViewModel: SigningViewModel = hiltViewModel(), encryptViewModel: EncryptViewModel = hiltViewModel(), encryptRecipientViewModel: EncryptRecipientViewModel = hiltViewModel(), + withEncryption: Boolean = false, ) { val cryptoContainer by sharedContainerViewModel.cryptoContainer.asFlow().collectAsState(null) val shouldResetContainer by encryptViewModel.shouldResetCryptoContainer.asFlow().collectAsState(false) @@ -162,6 +164,8 @@ fun EncryptNavigation( val clickedRecipient = remember { mutableStateOf(null) } val isNestedContainer = sharedContainerViewModel.isNestedContainer(cryptoContainer) + val isSaveContainerShown = rememberSaveable { mutableStateOf(false) } + val isWithEncryptionHandled = rememberSaveable { mutableStateOf(false) } val containerEncryptedSuccess = remember { mutableStateOf(false) } val containerEncryptedSuccessText = stringResource(id = R.string.crypto_create_success) @@ -448,9 +452,11 @@ fun EncryptNavigation( } BackHandler { - if (!isNestedContainer && encryptViewModel.isSaveButtonShown(cryptoContainer)) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeCryptoContainersDir(context) + handleBackButtonClick( navController, encryptViewModel, @@ -472,6 +478,13 @@ fun EncryptNavigation( sharedContainerViewModel.setCryptoContainer(sharedContainerViewModel.currentContainer() as? CryptoContainer) } + LaunchedEffect(withEncryption) { + if (withEncryption && !isWithEncryptionHandled.value) { + isSaveContainerShown.value = true + isWithEncryptionHandled.value = true + } + } + LaunchedEffect(encryptRecipientViewModel.isContainerEncrypted) { encryptRecipientViewModel.isContainerEncrypted.asFlow().collect { isContainerEncrypted -> if (isContainerEncrypted) { @@ -489,6 +502,7 @@ fun EncryptNavigation( delay(500) encryptionButtonEnabled.value = true + isSaveContainerShown.value = true } } } @@ -526,14 +540,22 @@ fun EncryptNavigation( LaunchedEffect(isSaved) { if (isSaved) { + isSaveContainerShown.value = false if (showContainerCloseConfirmationDialog.value) { showContainerCloseConfirmationDialog.value = false + + val containerFile = cryptoContainer?.file + if (containerFile?.exists() == true) { + containerFile.delete() + } + handleBackButtonClick( navController, encryptViewModel, sharedContainerViewModel, ) } + @Suppress("AssignedValueIsNeverRead") isSaved = false } } @@ -550,8 +572,9 @@ fun EncryptNavigation( LaunchedEffect(sharedContainerViewModel.decryptNFCStatus) { sharedContainerViewModel.decryptNFCStatus.asFlow().collect { status -> status?.let { - if (status == true) { + if (status) { withContext(Main) { + isSaveContainerShown.value = true containerDecryptedSuccess.value = true sendAccessibilityEvent( context, @@ -570,8 +593,9 @@ fun EncryptNavigation( LaunchedEffect(sharedContainerViewModel.decryptIDCardStatus) { sharedContainerViewModel.decryptIDCardStatus.asFlow().collect { status -> status?.let { - if (status == true) { + if (status) { withContext(Main) { + isSaveContainerShown.value = true containerDecryptedSuccess.value = true sendAccessibilityEvent( context, @@ -622,9 +646,11 @@ fun EncryptNavigation( }, leftIconContentDescription = R.string.crypto_close_container_title, onLeftButtonClick = { - if (!isNestedContainer && encryptViewModel.isSaveButtonShown(cryptoContainer)) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeCryptoContainersDir(context) + handleBackButtonClick( navController, encryptViewModel, @@ -701,14 +727,16 @@ fun EncryptNavigation( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start, ) { - if (containerEncryptedSuccess.value == true) { + if (containerEncryptedSuccess.value) { showMessage(containerEncryptedSuccessText) containerEncryptedSuccess.value = false + isSaveContainerShown.value = true } - if (containerDecryptedSuccess.value == true) { + if (containerDecryptedSuccess.value) { showMessage(containerDecryptedSuccessText) containerDecryptedSuccess.value = false + isSaveContainerShown.value = true } if (encryptViewModel.isEmptyFileInContainer(cryptoContainer) && @@ -805,7 +833,21 @@ fun EncryptNavigation( } }, onMoreOptionsActionButtonClick = { - showContainerBottomSheet.value = true + val isEditContainerButtonShown = ( + !isNestedContainer && + !encryptViewModel.isEncryptedContainer(cryptoContainer) && + !encryptViewModel.isDecryptedContainer(cryptoContainer) + ) + val isSignButtonShown = ( + !isNestedContainer && + encryptViewModel.isEncryptedContainer(cryptoContainer) + ) + val result = ( + isSaveContainerShown.value || + isEditContainerButtonShown || + isSignButtonShown + ) + showContainerBottomSheet.value = (result) }, ) } @@ -935,9 +977,11 @@ fun EncryptNavigation( subtitle = stringResource(id = R.string.crypto_containter_update_name), editValue = containerName, onEditValueChange = { + @Suppress("AssignedValueIsNeverRead") containerName = it }, onClearValueClick = { + @Suppress("AssignedValueIsNeverRead") containerName = TextFieldValue("") }, cancelButtonClick = dismissEditContainerNameDialog, @@ -993,6 +1037,8 @@ fun EncryptNavigation( if ((cryptoContainer?.dataFiles?.size ?: 0) == 1) { cryptoContainer?.file?.delete() sharedContainerViewModel.resetCryptoContainer() + + ContainerUtil.removeCryptoContainersDir(context) handleBackButtonClick( navController, encryptViewModel, @@ -1097,7 +1143,6 @@ fun EncryptNavigation( ) }, ) - EncryptContainerBottomSheet( modifier = modifier, showSheet = showContainerBottomSheet, @@ -1106,7 +1151,7 @@ fun EncryptNavigation( !encryptViewModel.isEncryptedContainer(cryptoContainer) && !encryptViewModel.isDecryptedContainer(cryptoContainer), openEditContainerNameDialog = openEditContainerNameDialog, - isSaveButtonShown = encryptViewModel.isSaveButtonShown(cryptoContainer), + isSaveButtonShown = isSaveContainerShown.value, isSignButtonShown = !isNestedContainer && encryptViewModel.isEncryptedContainer(cryptoContainer), cryptoContainer = cryptoContainer, onSignClick = onSignActionClick, @@ -1132,15 +1177,14 @@ fun EncryptNavigation( } if (showContainerCloseConfirmationDialog.value) { - val isSaveContainerShown = encryptViewModel.isSaveButtonShown(cryptoContainer) val dismissIcon = - if (isSaveContainerShown) { + if (isSaveContainerShown.value) { R.drawable.ic_m3_download_48dp_wght400 } else { R.drawable.ic_m3_cancel_48dp_wght400 } val dismissButtonText = - if (isSaveContainerShown) { + if (isSaveContainerShown.value) { stringResource(R.string.save) } else { stringResource(R.string.cancel_button) @@ -1160,7 +1204,7 @@ fun EncryptNavigation( showContainerCloseConfirmationDialog.value = false }, onDismissButton = { - if (isSaveContainerShown) { + if (isSaveContainerShown.value) { val file = cryptoContainer?.file if (file != null) { saveFile( @@ -1175,10 +1219,7 @@ fun EncryptNavigation( }, onConfirmButton = { showContainerCloseConfirmationDialog.value = false - val containerFile = cryptoContainer?.file - if (containerFile?.exists() == true) { - containerFile.delete() - } + ContainerUtil.removeCryptoContainersDir(context) sharedContainerViewModel.resetCryptoContainer() handleBackButtonClick(navController, encryptViewModel, sharedContainerViewModel) }, @@ -1197,8 +1238,7 @@ private fun handleBackButtonClick( sharedContainerViewModel.resetIsSivaConfirmed() if (sharedContainerViewModel.nestedContainers.size > 1) { sharedContainerViewModel.removeLastContainer() - val currentContainer = sharedContainerViewModel.currentContainer() - when (currentContainer) { + when (val currentContainer = sharedContainerViewModel.currentContainer()) { is SignedContainer -> { sharedContainerViewModel.resetCryptoContainer() sharedContainerViewModel.setSignedContainer(currentContainer) 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 743e8057..7ad59eed 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 @@ -131,6 +131,7 @@ import ee.ria.DigiDoc.utils.extensions.reachedBottom import ee.ria.DigiDoc.utils.libdigidoc.SignatureStatusUtil import ee.ria.DigiDoc.utils.snackbar.SnackBarManager import ee.ria.DigiDoc.utils.snackbar.SnackBarManager.showMessage +import ee.ria.DigiDoc.utilsLib.container.ContainerUtil import ee.ria.DigiDoc.utilsLib.container.ContainerUtil.createContainerAction import ee.ria.DigiDoc.utilsLib.container.ContainerUtil.removeExtensionFromContainerFilename import ee.ria.DigiDoc.utilsLib.extensions.isContainer @@ -181,8 +182,7 @@ fun SigningNavigation( val isNestedContainer = sharedContainerViewModel.isNestedContainer(signedContainer) val isXadesContainer = signedContainer?.isXades() == true val isCadesContainer = signedContainer?.isCades() == true - val isSignedContainer = signedContainer?.isSigned() == true - val isSaveContainerShown = remember { mutableStateOf(isSignedContainer) } + val isSaveContainerShown = rememberSaveable { mutableStateOf(false) } var validSignaturesCount by remember { mutableIntStateOf(0) } var unknownSignaturesCount by remember { mutableIntStateOf(0) } @@ -466,9 +466,11 @@ fun SigningNavigation( } BackHandler { - if (!isNestedContainer && isSaveContainerShown.value == true) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeSignatureContainersDir(context) + handleBackButtonClick( navController, signingViewModel, @@ -528,7 +530,7 @@ fun SigningNavigation( LaunchedEffect(sharedContainerViewModel.signedNFCStatus) { sharedContainerViewModel.signedNFCStatus.asFlow().collect { status -> status?.let { - if (status == true) { + if (status) { signatures = signedContainer?.getSignatures() ?: emptyList() withContext(Main) { signatureAddedSuccess.value = true @@ -545,7 +547,7 @@ fun SigningNavigation( LaunchedEffect(sharedContainerViewModel.signedIDCardStatus) { sharedContainerViewModel.signedIDCardStatus.asFlow().collect { status -> status?.let { - if (status == true) { + if (status) { signatures = signedContainer?.getSignatures() ?: emptyList() withContext(Main) { signatureAddedSuccess.value = true @@ -592,9 +594,6 @@ fun SigningNavigation( if (newTime >= (pastTime + 2 * 1000)) { sendAccessibilityEvent(context, getAccessibilityEventType(), signaturesLoaded) } - if (isSaveContainerShown.value != true) { - isSaveContainerShown.value = signatures.count() > 0 - } } } @@ -604,6 +603,7 @@ fun SigningNavigation( val announcementText = when { unknownSignaturesCount == 0 && invalidSignaturesCount == 0 -> { + @Suppress("AssignedValueIsNeverRead") validSignaturesCount = signatures.size "$containerHasText, ${validSignaturesText.lowercase()}" } @@ -616,6 +616,7 @@ fun SigningNavigation( } delay(1000) + @Suppress("AssignedValueIsNeverRead") isSignaturesCountLoaded = true sendAccessibilityEvent( context, @@ -636,14 +637,18 @@ fun SigningNavigation( LaunchedEffect(isSaved) { if (isSaved) { + isSaveContainerShown.value = false if (showContainerCloseConfirmationDialog.value) { showContainerCloseConfirmationDialog.value = false + ContainerUtil.removeSignatureContainersDir(context) + handleBackButtonClick( navController, signingViewModel, sharedContainerViewModel, ) } + @Suppress("AssignedValueIsNeverRead") isSaved = false } } @@ -708,9 +713,11 @@ fun SigningNavigation( }, leftIconContentDescription = R.string.signing_close_container_title, onLeftButtonClick = { - if (!isNestedContainer && isSaveContainerShown.value == true) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeSignatureContainersDir(context) + handleBackButtonClick( navController, signingViewModel, @@ -786,7 +793,7 @@ fun SigningNavigation( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start, ) { - if (signatureAddedSuccess.value == true) { + if (signatureAddedSuccess.value) { // Make sure text is announced when TalkBack is enabled by having its own element if (isTalkBackEnabled(context)) { Box( @@ -877,7 +884,21 @@ fun SigningNavigation( }, onRightActionButtonClick = onEncryptActionClick, onMoreOptionsActionButtonClick = { - showContainerBottomSheet.value = true + val isEditContainerButtonShown = + signingViewModel.isBottomContainerButtonShown( + signedContainer, + isNestedContainer, + ) + val isEncryptButtonShown = + signingViewModel.isEncryptButtonShown( + signedContainer, + isNestedContainer, + ) + showContainerBottomSheet.value = ( + isSaveContainerShown.value || + isEditContainerButtonShown || + isEncryptButtonShown + ) }, ) } @@ -962,10 +983,10 @@ fun SigningNavigation( timestamps, showSignaturesLoadingIndicator.value, signaturesLoading, - true, - false, - onSignatureMoreClick, - onSignatureMoreClick, + showNameAsAllCaps = true, + isDdocValid = false, + onClick = onSignatureMoreClick, + onClickMore = onSignatureMoreClick, ) } } @@ -1039,9 +1060,11 @@ fun SigningNavigation( subtitle = stringResource(id = R.string.signature_update_name_update_name), editValue = containerName, onEditValueChange = { + @Suppress("AssignedValueIsNeverRead") containerName = it }, onClearValueClick = { + @Suppress("AssignedValueIsNeverRead") containerName = TextFieldValue("") }, cancelButtonClick = dismissEditContainerNameDialog, @@ -1099,6 +1122,7 @@ fun SigningNavigation( signedContainer?.getContainerFile()?.delete() sharedContainerViewModel.resetSignedContainer() sharedContainerViewModel.resetContainerNotifications() + ContainerUtil.removeSignatureContainersDir(context) handleBackButtonClick(navController, signingViewModel, sharedContainerViewModel) } else { scope.launch(IO) { @@ -1201,7 +1225,6 @@ fun SigningNavigation( ) }, ) - ContainerBottomSheet( modifier = modifier, showSheet = showContainerBottomSheet, @@ -1213,7 +1236,7 @@ fun SigningNavigation( ), openEditContainerNameDialog = openEditContainerNameDialog, isEncryptButtonShown = - signingViewModel.isBottomContainerButtonShown( + signingViewModel.isEncryptButtonShown( signedContainer, isNestedContainer, ), @@ -1260,13 +1283,13 @@ fun SigningNavigation( if (showContainerCloseConfirmationDialog.value) { val dismissIcon = - if (isSaveContainerShown.value == true) { + if (isSaveContainerShown.value) { R.drawable.ic_m3_download_48dp_wght400 } else { R.drawable.ic_m3_cancel_48dp_wght400 } val dismissButtonText = - if (isSaveContainerShown.value == true) { + if (isSaveContainerShown.value) { stringResource(R.string.save) } else { stringResource(R.string.cancel_button) @@ -1286,7 +1309,7 @@ fun SigningNavigation( showContainerCloseConfirmationDialog.value = false }, onDismissButton = { - if (isSaveContainerShown.value == true) { + if (isSaveContainerShown.value) { val file = signedContainer?.getContainerFile() if (file != null) { saveFile( @@ -1301,10 +1324,7 @@ fun SigningNavigation( }, onConfirmButton = { showContainerCloseConfirmationDialog.value = false - val containerFile = signedContainer?.getContainerFile() - if (containerFile?.exists() == true) { - containerFile.delete() - } + ContainerUtil.removeSignatureContainersDir(context) sharedContainerViewModel.resetSignedContainer() sharedContainerViewModel.resetContainerNotifications() handleBackButtonClick(navController, signingViewModel, sharedContainerViewModel) @@ -1345,8 +1365,7 @@ private fun handleBackButtonClick( sharedContainerViewModel.resetIsSivaConfirmed() if (sharedContainerViewModel.nestedContainers.size > 1) { sharedContainerViewModel.removeLastContainer() - val currentContainer = sharedContainerViewModel.currentContainer() - when (currentContainer) { + when (val currentContainer = sharedContainerViewModel.currentContainer()) { is SignedContainer -> { sharedContainerViewModel.resetCryptoContainer() sharedContainerViewModel.setSignedContainer(currentContainer) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt index 5b5fc834..25134961 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Constant.kt @@ -40,6 +40,7 @@ object Constant { const val CRYPTO_FILE_CHOOSING_SCREEN = "crypto_file_choosing_route" const val SIGNING_SCREEN = "signing_route" const val ENCRYPT_SCREEN = "encrypt_route" + const val ENCRYPTED_SCREEN = "encrypted_route" const val DECRYPT_SCREEN = "decrypt_route" const val DECRYPT_METHOD_SCREEN = "decrypt_method_route" const val ACCESSIBILITY_SCREEN = "accessibility_route" diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt index 2c7aa954..5964cfe0 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/utils/Route.kt @@ -31,6 +31,7 @@ import ee.ria.DigiDoc.utils.Constant.Routes.DECRYPT_METHOD_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.DECRYPT_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.DIAGNOSTICS_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.EID_SCREEN +import ee.ria.DigiDoc.utils.Constant.Routes.ENCRYPTED_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.ENCRYPTION_SERVICES_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.ENCRYPT_RECIPIENT_SCREEN import ee.ria.DigiDoc.utils.Constant.Routes.ENCRYPT_SCREEN @@ -79,6 +80,8 @@ sealed class Route( data object Encrypt : Route(ENCRYPT_SCREEN) + data object Encrypted : Route(ENCRYPTED_SCREEN) + data object DecryptScreen : Route(DECRYPT_SCREEN) data object DecryptMethodScreen : Route(DECRYPT_METHOD_SCREEN) diff --git a/crypto-lib/src/main/kotlin/ee/ria/DigiDoc/cryptolib/repository/RecipientRepositoryImpl.kt b/crypto-lib/src/main/kotlin/ee/ria/DigiDoc/cryptolib/repository/RecipientRepositoryImpl.kt index 97e6bc53..990d3975 100644 --- a/crypto-lib/src/main/kotlin/ee/ria/DigiDoc/cryptolib/repository/RecipientRepositoryImpl.kt +++ b/crypto-lib/src/main/kotlin/ee/ria/DigiDoc/cryptolib/repository/RecipientRepositoryImpl.kt @@ -98,7 +98,10 @@ class RecipientRepositoryImpl query: String, ): Pair, Int> { val configurationProvider = configurationRepository.getConfiguration() - + var exception: Exception = + CryptoException( + "No addressee found", + ) val ldapFilter = LdapFilter(query) if (ldapFilter.isPersonalCode(query)) { val ldapPersonUrls = configurationProvider?.ldapPersonUrls @@ -116,12 +119,19 @@ class RecipientRepositoryImpl count += countSearch } catch (e: NoInternetConnectionException) { errorLog(logTag, "Unable to connect to LDAP url: $ldapPersonUrl", e) - throw e + exception = e } catch (ce: CryptoException) { errorLog(logTag, "Unable to get certificates from LDAP url: $ldapPersonUrl", ce) - throw CryptoException("Unable to get certificates from LDAP url: $ldapPersonUrl", ce) + exception = + CryptoException( + "Unable to get certificates from LDAP url: $ldapPersonUrl", + ce, + ) } } + if (addressees.isEmpty()) { + throw exception + } return Pair(addressees, count) } else { val ldapCorpUrl = configurationProvider?.ldapCorpUrl?.split("://")[1] From 50246ed60f85cfd7bb5f12391256c0b89241a172 Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Mon, 26 Jan 2026 16:08:00 +0200 Subject: [PATCH 6/9] MOPPAND-1738 Do not offer to save container when decrypted. --- .../ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt index c6da01a6..84b972c4 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt @@ -574,7 +574,6 @@ fun EncryptNavigation( status?.let { if (status) { withContext(Main) { - isSaveContainerShown.value = true containerDecryptedSuccess.value = true sendAccessibilityEvent( context, @@ -595,7 +594,6 @@ fun EncryptNavigation( status?.let { if (status) { withContext(Main) { - isSaveContainerShown.value = true containerDecryptedSuccess.value = true sendAccessibilityEvent( context, @@ -736,7 +734,6 @@ fun EncryptNavigation( if (containerDecryptedSuccess.value) { showMessage(containerDecryptedSuccessText) containerDecryptedSuccess.value = false - isSaveContainerShown.value = true } if (encryptViewModel.isEmptyFileInContainer(cryptoContainer) && From df9994fecaa2c98ddfc82b72fbeb15eb3c209ab7 Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Tue, 27 Jan 2026 16:25:06 +0200 Subject: [PATCH 7/9] MOPPAND-1738 Empty file in container message flicker upon closing decrypted container fix. --- .../ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt index 84b972c4..90c74c9d 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt @@ -544,10 +544,7 @@ fun EncryptNavigation( if (showContainerCloseConfirmationDialog.value) { showContainerCloseConfirmationDialog.value = false - val containerFile = cryptoContainer?.file - if (containerFile?.exists() == true) { - containerFile.delete() - } + ContainerUtil.removeCryptoContainersDir(context) handleBackButtonClick( navController, @@ -737,7 +734,8 @@ fun EncryptNavigation( } if (encryptViewModel.isEmptyFileInContainer(cryptoContainer) && - !encryptViewModel.isEncryptedContainer(cryptoContainer) + !encryptViewModel.isEncryptedContainer(cryptoContainer) && + !encryptViewModel.isDecryptedContainer(cryptoContainer) ) { showMessage(emptyFileInContainerText) } From 4e9e1c157073c227b4a9c04793d75e9ab02ca52f Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Wed, 28 Jan 2026 16:48:36 +0200 Subject: [PATCH 8/9] MOPPAND-1738 Success message upon saving Crypto and Signed container. --- .../ria/DigiDoc/fragment/screen/HomeScreen.kt | 17 +++++++++++++++++ .../ui/component/crypto/EncryptNavigation.kt | 10 +++++++++- .../ui/component/signing/SigningNavigation.kt | 10 +++++++++- .../DigiDoc/utils/snackbar/SnackBarManager.kt | 4 ++-- app/src/main/res/values-et/strings.xml | 4 ++++ app/src/main/res/values/strings.xml | 4 ++++ 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt index 7fc6bb7d..a4627db4 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -82,6 +83,7 @@ import ee.ria.DigiDoc.ui.theme.Dimensions.iconSizeM import ee.ria.DigiDoc.ui.theme.RIADigiDocTheme import ee.ria.DigiDoc.utils.Route import ee.ria.DigiDoc.utils.snackbar.SnackBarManager +import ee.ria.DigiDoc.utils.snackbar.SnackBarManager.showMessage import ee.ria.DigiDoc.viewmodel.HomeViewModel import ee.ria.DigiDoc.viewmodel.shared.SharedMenuViewModel import kotlinx.coroutines.CoroutineScope @@ -96,6 +98,7 @@ fun HomeScreen( sharedMenuViewModel: SharedMenuViewModel, homeViewModel: HomeViewModel = hiltViewModel(), ) { + val context = LocalContext.current val activity = LocalActivity.current val openCrashDetectorDialog = remember { mutableStateOf(false) } val hasUnsentReports by homeViewModel.hasUnsentReports.asFlow().collectAsState(Tasks.forResult(false)) @@ -110,10 +113,24 @@ fun HomeScreen( val messages by SnackBarManager.messages.collectAsState(emptyList()) + val savedStateHandle = navController.currentBackStackEntry?.savedStateHandle + val snackbarMessageFlow = + savedStateHandle + ?.getStateFlow("snackbar_message", null) + + val snackbarMessage by snackbarMessageFlow?.collectAsState() ?: remember { mutableStateOf(null) } + BackHandler { activity?.finish() } + LaunchedEffect(snackbarMessage) { + snackbarMessage?.let { message -> + showMessage(context, message) + savedStateHandle?.set("snackbar_message", null) + } + } + LaunchedEffect(homeViewModel.didAppCrashOnPreviousExecution(), hasUnsentReports) { if (!homeViewModel.isCrashSendingAlwaysEnabled() && hasUnsentReports.result) { openCrashDetectorDialog.value = true diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt index 90c74c9d..35ed5e8c 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/crypto/EncryptNavigation.kt @@ -441,7 +441,7 @@ fun EncryptNavigation( } ?: run { cryptoContainer?.file?.let { sharedContainerViewModel.saveContainerFile(it, result) - showMessage(context, R.string.file_saved) + showMessage(context, R.string.crypto_saved_container_success) isSaved = true } ?: showMessage(context, R.string.file_saved_error) } @@ -550,6 +550,7 @@ fun EncryptNavigation( navController, encryptViewModel, sharedContainerViewModel, + true, ) } @Suppress("AssignedValueIsNeverRead") @@ -1228,6 +1229,7 @@ private fun handleBackButtonClick( navController: NavHostController, encryptViewModel: EncryptViewModel, sharedContainerViewModel: SharedContainerViewModel, + containerSavedSuccess: Boolean = false, ) { sharedContainerViewModel.resetExternalFileUris() sharedContainerViewModel.resetIsSivaConfirmed() @@ -1247,6 +1249,12 @@ private fun handleBackButtonClick( } else { sharedContainerViewModel.clearContainers() encryptViewModel.handleBackButton() + + if (containerSavedSuccess) { + navController.previousBackStackEntry + ?.savedStateHandle + ?.set("snackbar_message", R.string.crypto_saved_container_success) + } navController.navigateUp() } } 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 7ad59eed..7605500b 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 @@ -455,7 +455,7 @@ fun SigningNavigation( } ?: run { signedContainer?.getContainerFile()?.let { sharedContainerViewModel.saveContainerFile(it, result) - showMessage(context, R.string.file_saved) + showMessage(context, R.string.signature_saved_container_success) isSaved = true } ?: showMessage(context, R.string.file_saved_error) } @@ -646,6 +646,7 @@ fun SigningNavigation( navController, signingViewModel, sharedContainerViewModel, + true, ) } @Suppress("AssignedValueIsNeverRead") @@ -1360,6 +1361,7 @@ private fun handleBackButtonClick( navController: NavHostController, signingViewModel: SigningViewModel, sharedContainerViewModel: SharedContainerViewModel, + containerSavedSuccess: Boolean = false, ) { sharedContainerViewModel.resetExternalFileUris() sharedContainerViewModel.resetIsSivaConfirmed() @@ -1379,6 +1381,12 @@ private fun handleBackButtonClick( } else { sharedContainerViewModel.clearContainers() signingViewModel.handleBackButton() + + if (containerSavedSuccess) { + navController.previousBackStackEntry + ?.savedStateHandle + ?.set("snackbar_message", R.string.signature_saved_container_success) + } navController.navigateUp() } } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/utils/snackbar/SnackBarManager.kt b/app/src/main/kotlin/ee/ria/DigiDoc/utils/snackbar/SnackBarManager.kt index 283175ba..b0a28ac6 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/utils/snackbar/SnackBarManager.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/utils/snackbar/SnackBarManager.kt @@ -35,7 +35,7 @@ object SnackBarManager { fun showMessage(message: String) { CoroutineScope(Main).launch { - _messages.value = _messages.value + message + _messages.value += message } } @@ -44,7 +44,7 @@ object SnackBarManager { @StringRes message: Int, ) { CoroutineScope(Main).launch { - _messages.value = _messages.value + context.getString(message) + _messages.value += context.getString(message) } } diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 15d580f4..56558178 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -173,6 +173,8 @@ Ümbriku dekrüpteerimine Dekrüpteerimise meetod + Krüptokonteineri salvestamine õnnestus + Ümbriku allkirjastamine Ümbriku nimi @@ -190,6 +192,8 @@ Allkiri lisatud Lisa allkiri + Allkirjastatud konteineri salvestamine õnnestus + Allkirjastamise viisiks on mobiil-ID, vahekaart %1$d / %2$d Allkirjastamise viisiks on Smart-ID, vahekaart %1$d / %2$d Allkirjastamise viisiks on ID-kaart, vahekaart %1$d / %2$d diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0368c43f..3457ca3c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -173,6 +173,8 @@ Container decryption Decryption method + Successfully saved crypto container + Container signing Container name @@ -190,6 +192,8 @@ Signature added Add signature + Successfully saved signed container + Signing option Mobile-ID, tab %1$d of %2$d Signing option Smart-ID, tab %1$d of %2$d Signing option ID-card, tab %1$d of %2$d From 9f5da1e5c8952850000ac6eecd4bd22571b099e5 Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Mon, 2 Feb 2026 15:29:50 +0200 Subject: [PATCH 9/9] MOPPAND-1738 Container saved en text update. --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3457ca3c..072e1649 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -173,7 +173,7 @@ Container decryption Decryption method - Successfully saved crypto container + Crypto container saved successfully Container signing @@ -192,7 +192,7 @@ Signature added Add signature - Successfully saved signed container + Signed container saved successfully Signing option Mobile-ID, tab %1$d of %2$d Signing option Smart-ID, tab %1$d of %2$d