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/MainActivity.kt b/app/src/main/kotlin/ee/ria/DigiDoc/MainActivity.kt index b9df6878..d31162dc 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 @@ -159,6 +160,9 @@ class MainActivity : fileTypeSetup.initializeApplicationFileTypesAssociation(componentClassName) librarySetup.setupLibraries(applicationContext, isLoggingEnabled) + ContainerUtil.removeSignatureContainersDir(applicationContext) + ContainerUtil.removeCryptoContainersDir(applicationContext) + isAppReady = true setContent { diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/RIADigiDocAppNavigation.kt index df0e2583..ed1b1f63 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 @@ -181,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(), @@ -220,33 +229,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/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/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/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/fragment/screen/HomeScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/HomeScreen.kt index 83715138..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 @@ -74,7 +75,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 @@ -83,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 @@ -97,15 +98,13 @@ 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)) 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) } @@ -114,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 @@ -210,27 +223,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 +310,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 +326,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 +342,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..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 @@ -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) @@ -189,7 +193,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.crypto_close_container_title) val saveContainerMessage = stringResource(id = R.string.container_save) val dismissRemoveFileDialog = { closeRemoveFileDialog() @@ -437,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) } @@ -448,9 +452,11 @@ fun EncryptNavigation( } BackHandler { - if (!isNestedContainer && encryptViewModel.isEncryptedContainer(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,20 @@ fun EncryptNavigation( LaunchedEffect(isSaved) { if (isSaved) { + isSaveContainerShown.value = false if (showContainerCloseConfirmationDialog.value) { showContainerCloseConfirmationDialog.value = false + + ContainerUtil.removeCryptoContainersDir(context) + handleBackButtonClick( navController, encryptViewModel, sharedContainerViewModel, + true, ) } + @Suppress("AssignedValueIsNeverRead") isSaved = false } } @@ -550,7 +570,7 @@ fun EncryptNavigation( LaunchedEffect(sharedContainerViewModel.decryptNFCStatus) { sharedContainerViewModel.decryptNFCStatus.asFlow().collect { status -> status?.let { - if (status == true) { + if (status) { withContext(Main) { containerDecryptedSuccess.value = true sendAccessibilityEvent( @@ -570,7 +590,7 @@ fun EncryptNavigation( LaunchedEffect(sharedContainerViewModel.decryptIDCardStatus) { sharedContainerViewModel.decryptIDCardStatus.asFlow().collect { status -> status?.let { - if (status == true) { + if (status) { withContext(Main) { containerDecryptedSuccess.value = true sendAccessibilityEvent( @@ -622,9 +642,11 @@ fun EncryptNavigation( }, leftIconContentDescription = R.string.crypto_close_container_title, onLeftButtonClick = { - if (!isNestedContainer && encryptViewModel.isEncryptedContainer(cryptoContainer)) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeCryptoContainersDir(context) + handleBackButtonClick( navController, encryptViewModel, @@ -701,18 +723,20 @@ 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 } if (encryptViewModel.isEmptyFileInContainer(cryptoContainer) && - !encryptViewModel.isEncryptedContainer(cryptoContainer) + !encryptViewModel.isEncryptedContainer(cryptoContainer) && + !encryptViewModel.isDecryptedContainer(cryptoContainer) ) { showMessage(emptyFileInContainerText) } @@ -805,7 +829,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 +973,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 +1033,8 @@ fun EncryptNavigation( if ((cryptoContainer?.dataFiles?.size ?: 0) == 1) { cryptoContainer?.file?.delete() sharedContainerViewModel.resetCryptoContainer() + + ContainerUtil.removeCryptoContainersDir(context) handleBackButtonClick( navController, encryptViewModel, @@ -1097,7 +1139,6 @@ fun EncryptNavigation( ) }, ) - EncryptContainerBottomSheet( modifier = modifier, showSheet = showContainerBottomSheet, @@ -1106,13 +1147,7 @@ fun EncryptNavigation( !encryptViewModel.isEncryptedContainer(cryptoContainer) && !encryptViewModel.isDecryptedContainer(cryptoContainer), openEditContainerNameDialog = openEditContainerNameDialog, - isSaveButtonShown = ( - encryptViewModel.isEncryptedContainer(cryptoContainer) || - ( - encryptViewModel.isDecryptedContainer(cryptoContainer) && - cryptoContainer?.hasRecipients() == true - ) - ), + isSaveButtonShown = isSaveContainerShown.value, isSignButtonShown = !isNestedContainer && encryptViewModel.isEncryptedContainer(cryptoContainer), cryptoContainer = cryptoContainer, onSignClick = onSignActionClick, @@ -1138,36 +1173,49 @@ fun EncryptNavigation( } if (showContainerCloseConfirmationDialog.value) { + val dismissIcon = + if (isSaveContainerShown.value) { + R.drawable.ic_m3_download_48dp_wght400 + } else { + R.drawable.ic_m3_cancel_48dp_wght400 + } + val dismissButtonText = + if (isSaveContainerShown.value) { + 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_button), 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.value) { + 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() - } + ContainerUtil.removeCryptoContainersDir(context) sharedContainerViewModel.resetCryptoContainer() handleBackButtonClick(navController, encryptViewModel, sharedContainerViewModel) }, @@ -1181,13 +1229,13 @@ private fun handleBackButtonClick( navController: NavHostController, encryptViewModel: EncryptViewModel, sharedContainerViewModel: SharedContainerViewModel, + containerSavedSuccess: Boolean = false, ) { sharedContainerViewModel.resetExternalFileUris() 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) @@ -1201,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/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..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 @@ -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,6 +182,7 @@ fun SigningNavigation( val isNestedContainer = sharedContainerViewModel.isNestedContainer(signedContainer) val isXadesContainer = signedContainer?.isXades() == true val isCadesContainer = signedContainer?.isCades() == true + val isSaveContainerShown = rememberSaveable { mutableStateOf(false) } 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.signing_close_container_title) val saveContainerMessage = stringResource(id = R.string.container_save) val dismissRemoveFileDialog = { closeRemoveFileDialog() @@ -453,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) } @@ -464,9 +466,11 @@ fun SigningNavigation( } BackHandler { - if (!isNestedContainer) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeSignatureContainersDir(context) + handleBackButtonClick( navController, signingViewModel, @@ -526,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 @@ -543,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 @@ -599,6 +603,7 @@ fun SigningNavigation( val announcementText = when { unknownSignaturesCount == 0 && invalidSignaturesCount == 0 -> { + @Suppress("AssignedValueIsNeverRead") validSignaturesCount = signatures.size "$containerHasText, ${validSignaturesText.lowercase()}" } @@ -611,6 +616,7 @@ fun SigningNavigation( } delay(1000) + @Suppress("AssignedValueIsNeverRead") isSignaturesCountLoaded = true sendAccessibilityEvent( context, @@ -631,14 +637,19 @@ fun SigningNavigation( LaunchedEffect(isSaved) { if (isSaved) { + isSaveContainerShown.value = false if (showContainerCloseConfirmationDialog.value) { showContainerCloseConfirmationDialog.value = false + ContainerUtil.removeSignatureContainersDir(context) + handleBackButtonClick( navController, signingViewModel, sharedContainerViewModel, + true, ) } + @Suppress("AssignedValueIsNeverRead") isSaved = false } } @@ -703,9 +714,11 @@ fun SigningNavigation( }, leftIconContentDescription = R.string.signing_close_container_title, onLeftButtonClick = { - if (!isNestedContainer) { + if (!isNestedContainer && isSaveContainerShown.value) { showContainerCloseConfirmationDialog.value = true } else { + ContainerUtil.removeSignatureContainersDir(context) + handleBackButtonClick( navController, signingViewModel, @@ -781,7 +794,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( @@ -798,6 +811,7 @@ fun SigningNavigation( ) } } + isSaveContainerShown.value = true showMessage(signatureAddedSuccessText) signatureAddedSuccess.value = false } @@ -871,7 +885,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 + ) }, ) } @@ -956,10 +984,10 @@ fun SigningNavigation( timestamps, showSignaturesLoadingIndicator.value, signaturesLoading, - true, - false, - onSignatureMoreClick, - onSignatureMoreClick, + showNameAsAllCaps = true, + isDdocValid = false, + onClick = onSignatureMoreClick, + onClickMore = onSignatureMoreClick, ) } } @@ -1033,9 +1061,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, @@ -1046,6 +1076,7 @@ fun SigningNavigation( ) } openEditContainerNameDialog.value = false + isSaveContainerShown.value = true sendAccessibilityEvent( context, getAccessibilityEventType(), @@ -1092,6 +1123,7 @@ fun SigningNavigation( signedContainer?.getContainerFile()?.delete() sharedContainerViewModel.resetSignedContainer() sharedContainerViewModel.resetContainerNotifications() + ContainerUtil.removeSignatureContainersDir(context) handleBackButtonClick(navController, signingViewModel, sharedContainerViewModel) } else { scope.launch(IO) { @@ -1107,6 +1139,7 @@ fun SigningNavigation( } } } + isSaveContainerShown.value = true closeRemoveFileDialog() sendAccessibilityEvent(context, getAccessibilityEventType(), fileRemoved) }, @@ -1151,6 +1184,7 @@ fun SigningNavigation( actionSignature, ) } + isSaveContainerShown.value = true closeSignatureDialog() sendAccessibilityEvent(context, getAccessibilityEventType(), signatureRemoved) }, @@ -1192,10 +1226,10 @@ fun SigningNavigation( ) }, ) - ContainerBottomSheet( modifier = modifier, showSheet = showContainerBottomSheet, + isSaveButtonShown = isSaveContainerShown.value, isEditContainerButtonShown = signingViewModel.isBottomContainerButtonShown( signedContainer, @@ -1203,7 +1237,7 @@ fun SigningNavigation( ), openEditContainerNameDialog = openEditContainerNameDialog, isEncryptButtonShown = - signingViewModel.isBottomContainerButtonShown( + signingViewModel.isEncryptButtonShown( signedContainer, isNestedContainer, ), @@ -1249,36 +1283,49 @@ fun SigningNavigation( } if (showContainerCloseConfirmationDialog.value) { + val dismissIcon = + if (isSaveContainerShown.value) { + R.drawable.ic_m3_download_48dp_wght400 + } else { + R.drawable.ic_m3_cancel_48dp_wght400 + } + val dismissButtonText = + if (isSaveContainerShown.value) { + 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_button), 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) { + 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() - } + ContainerUtil.removeSignatureContainersDir(context) sharedContainerViewModel.resetSignedContainer() sharedContainerViewModel.resetContainerNotifications() handleBackButtonClick(navController, signingViewModel, sharedContainerViewModel) @@ -1314,13 +1361,13 @@ private fun handleBackButtonClick( navController: NavHostController, signingViewModel: SigningViewModel, sharedContainerViewModel: SharedContainerViewModel, + containerSavedSuccess: Boolean = false, ) { sharedContainerViewModel.resetExternalFileUris() 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) @@ -1334,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/ui/component/signing/bottomsheet/ContainerBottomSheet.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/bottomsheet/ContainerBottomSheet.kt index c7a76774..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,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..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" @@ -48,9 +49,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..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 @@ -42,9 +43,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,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) @@ -98,12 +98,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/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/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/EncryptViewModel.kt index f20387f0..9d83d93c 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,15 @@ class EncryptViewModel ) && isDataFilesInContainer(cryptoContainer) + fun isSaveButtonShown(cryptoContainer: CryptoContainer?): Boolean = + ( + isEncryptedContainer(cryptoContainer) || + ( + isDecryptedContainer(cryptoContainer) && + cryptoContainer?.hasRecipients() == true + ) + ) + fun isSignButtonShown( cryptoContainer: CryptoContainer?, isNestedContainer: Boolean, @@ -150,19 +156,6 @@ 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) suspend fun openSignedContainer( context: Context, 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..56558178 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -127,18 +127,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. @@ -185,6 +173,8 @@ Ümbriku dekrüpteerimine Dekrüpteerimise meetod + Krüptokonteineri salvestamine õnnestus + Ümbriku allkirjastamine Ümbriku nimi @@ -202,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 37d81193..072e1649 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -127,18 +127,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. @@ -185,6 +173,8 @@ Container decryption Decryption method + Crypto container saved successfully + Container signing Container name @@ -202,6 +192,8 @@ Signature added Add signature + Signed container saved successfully + 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 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] 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..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 @@ -282,6 +282,36 @@ object ContainerUtil { return dir } + fun removeSignatureContainersDir(context: Context): Boolean { + val dir = File(context.filesDir, DIR_SIGNATURE_CONTAINERS) + return deleteDir(dir) + } + + fun removeCryptoContainersDir(context: Context): Boolean { + val dir = File(context.filesDir, DIR_CRYPTO_CONTAINERS) + return deleteDir(dir) + } + + private fun deleteDir(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 {