From 9b56d657df1f8e7148e2beb81dcdf2ef035f45e0 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Mon, 11 May 2026 18:38:16 +0300 Subject: [PATCH] Update dependencies and deprecated code --- README.md | 2 +- app/build.gradle.kts | 28 ++--- .../viewmodel/DiagnosticsViewModelTest.kt | 6 +- .../shared/SharedContainerViewModelTest.kt | 6 +- .../shared/SharedSettingsViewModelTest.kt | 5 + .../fileopening/FileOpeningRepositoryImpl.kt | 10 +- .../fileopening/FileOpeningServiceImpl.kt | 10 +- .../screen/ProxyServicesSettingsScreen.kt | 24 ++++ .../menu/LanguageChoiceButtonItem.kt | 4 - .../DigiDoc/ui/component/shared/TabView.kt | 12 +- .../signing/SignatureAddRadioItem.kt | 4 - .../DigiDoc/utils/locale/LocaleUtilImpl.kt | 4 +- .../DigiDoc/viewmodel/DiagnosticsViewModel.kt | 5 +- .../ria/DigiDoc/viewmodel/SigningViewModel.kt | 4 +- .../shared/SharedContainerViewModel.kt | 5 +- .../shared/SharedSettingsViewModel.kt | 104 +++++++----------- build.gradle.kts | 2 +- .../DigiDoc/libcdoc/update/LibcdocPlugin.kt | 5 +- .../libcdoc/update/UpdateLibcdocTask.kt | 17 ++- .../libdigidoc/update/LibdigidocppPlugin.kt | 5 +- .../update/UpdateLibdigidocppTask.kt | 17 ++- commons-lib/build.gradle.kts | 21 ++-- commons-lib/consumer-rules.pro | 0 commons-lib/test-files/build.gradle.kts | 13 +-- config-lib/build.gradle.kts | 28 +++-- config-lib/consumer-rules.pro | 0 crypto-lib/build.gradle.kts | 25 ++--- gradle.properties | 12 +- gradle/libs.versions.toml | 74 +++++++------ gradle/wrapper/gradle-wrapper.properties | 2 +- id-card-lib/build.gradle.kts | 21 ++-- libdigidoc-lib/build.gradle.kts | 25 ++--- .../libdigidoclib/SignedContainerTest.kt | 2 +- .../libdigidoclib/utils/FileUtilsTest.kt | 8 +- .../DigiDoc/libdigidoclib/SignedContainer.kt | 26 ++--- .../DigiDoc/libdigidoclib/utils/FileUtils.kt | 4 +- mobile-id-lib/build.gradle.kts | 21 ++-- networking-lib/build.gradle.kts | 17 ++- smart-id-lib/build.gradle.kts | 21 ++-- utils-lib/build.gradle.kts | 21 ++-- .../utilsLib/extensions/FileExtensions.kt | 6 +- .../ee/ria/DigiDoc/utilsLib/file/FileUtil.kt | 33 +++--- 42 files changed, 333 insertions(+), 326 deletions(-) create mode 100644 commons-lib/consumer-rules.pro create mode 100644 config-lib/consumer-rules.pro diff --git a/README.md b/README.md index 4ee7f7fd0..8efed662a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ * License: LGPL 2.1 * © Estonian Information System Authority -# RIA-DigiDoc-Android v3 +# RIA-DigiDoc-Android Android application that allows signing containers with ID-card via USB reader, ID-card via NFC, Mobile-ID and Smart-ID. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a9038cdea..8a3fb92e5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,14 +1,13 @@ import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension import org.jetbrains.kotlin.gradle.dsl.JvmTarget -val appAbiFilters = "arm64-v8a;armeabi-v7a;x86_64" +val appAbiFilters = "arm64-v8a;armeabi-v7a;x86_64".split(';').map { it.trim() } plugins { jacoco alias(libs.plugins.androidApplication) - alias(libs.plugins.jetbrainsKotlinAndroid) alias(libs.plugins.compose.compiler) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") alias(libs.plugins.google.services) alias(libs.plugins.google.firebase.crashlytics) @@ -20,6 +19,8 @@ tasks.register("jacocoFilteredReport") { group = "Reporting" description = "Generates filtered JaCoCo report excluding UI package and other noise" + val buildDir = layout.buildDirectory.get().asFile + val coverageFiles = fileTree("$buildDir/outputs/code_coverage/debugAndroidTest/connected") { include("**/coverage.ec") @@ -85,7 +86,7 @@ android { ndk { abiFilters.clear() - abiFilters.addAll(appAbiFilters.split(';').map { it.trim() }) + abiFilters.addAll(appAbiFilters) } } @@ -132,14 +133,8 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -157,6 +152,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) @@ -166,6 +167,7 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.material.icons.core) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) @@ -179,7 +181,7 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.google.dagger.hilt.android) implementation(libs.firebase.crashlytics.ktx) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) implementation(libs.kotlinx.coroutines.rx3) diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModelTest.kt index 875e014da..ec9201f74 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModelTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModelTest.kt @@ -65,6 +65,8 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner +import org.mockito.kotlin.whenever +import java.io.ByteArrayOutputStream import java.io.File import java.io.FileNotFoundException import java.nio.charset.Charset @@ -332,7 +334,9 @@ class DiagnosticsViewModelTest { fun diagnosticsViewModel_saveFile_success() { val file = createTempFileWithStringContent("test", "Test content") val intent = Intent() - intent.data = Uri.fromFile(file) + val uri = Uri.fromFile(file) + intent.data = uri + whenever(contentResolver.openOutputStream(uri)).thenReturn(ByteArrayOutputStream()) val activityResult = ActivityResult(-1, intent) viewModel.saveFile(file, activityResult) } diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModelTest.kt index 91d33aa64..c5363edfc 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModelTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModelTest.kt @@ -68,6 +68,8 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner +import org.mockito.kotlin.whenever +import java.io.ByteArrayOutputStream import java.io.File import java.io.FileNotFoundException import java.nio.charset.Charset @@ -127,7 +129,9 @@ class SharedContainerViewModelTest { fun sharedContainerViewModel_saveContainerFile_success() { val file = createTempFileWithStringContent("test", "Test content") val intent = Intent() - intent.data = Uri.fromFile(file) + val uri = Uri.fromFile(file) + intent.data = uri + whenever(contentResolver.openOutputStream(uri)).thenReturn(ByteArrayOutputStream()) val activityResult = ActivityResult(-1, intent) viewModel.saveContainerFile(file, activityResult) } diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModelTest.kt index f2183ebdf..f397c0176 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModelTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModelTest.kt @@ -63,6 +63,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever import java.io.File import java.io.InputStream import java.nio.charset.Charset @@ -266,6 +267,7 @@ class SharedSettingsViewModelTest { ee.ria.DigiDoc.common.R.raw.siva, ) val uri = Uri.fromFile(file) + whenever(contentResolver.openInputStream(uri)).thenReturn(file.inputStream()) viewModel.handleSivaFile(uri) val validUrl = "https://valid-siva-url.com" @@ -299,6 +301,7 @@ class SharedSettingsViewModelTest { ee.ria.DigiDoc.common.R.raw.siva, ) val uri = Uri.fromFile(file) + whenever(contentResolver.openInputStream(uri)).thenReturn(file.inputStream()) viewModel.handleTsaFile(uri) val validUrl = "https://valid-tsa-url.com" @@ -332,6 +335,7 @@ class SharedSettingsViewModelTest { ) val uri = Uri.fromFile(file) + whenever(contentResolver.openInputStream(uri)).thenReturn(file.inputStream()) viewModel.handleSivaFile(uri) assertEquals("sivaCert", dataStore.getSettingsSivaCertName()) } @@ -353,6 +357,7 @@ class SharedSettingsViewModelTest { ) val uri = Uri.fromFile(file) + whenever(contentResolver.openInputStream(uri)).thenReturn(file.inputStream()) viewModel.handleTsaFile(uri) assertEquals("tsaCert", dataStore.getTSACertName()) } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/domain/repository/fileopening/FileOpeningRepositoryImpl.kt b/app/src/main/kotlin/ee/ria/DigiDoc/domain/repository/fileopening/FileOpeningRepositoryImpl.kt index 63233d7b5..1a411e32d 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/domain/repository/fileopening/FileOpeningRepositoryImpl.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/domain/repository/fileopening/FileOpeningRepositoryImpl.kt @@ -200,9 +200,7 @@ class FileOpeningRepositoryImpl override fun isFileAlreadyInContainer( file: File, container: CryptoContainer, - ): Boolean = - container.dataFiles?.any { it?.name == file.name } - ?: false + ): Boolean = container.dataFiles.any { it.name == file.name } override fun isSivaConfirmationNeeded( context: Context, @@ -272,10 +270,8 @@ class FileOpeningRepositoryImpl val dataFiles = cryptoContainer.dataFiles - if (dataFiles != null) { - for (i in dataFiles.indices) { - dataFiles[i]?.name?.let { containerFileNames.add(it) } - } + for (i in dataFiles.indices) { + dataFiles[i].name.let { containerFileNames.add(it) } } return containerFileNames diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/domain/service/fileopening/FileOpeningServiceImpl.kt b/app/src/main/kotlin/ee/ria/DigiDoc/domain/service/fileopening/FileOpeningServiceImpl.kt index 07e709c1b..d0c12627a 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/domain/service/fileopening/FileOpeningServiceImpl.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/domain/service/fileopening/FileOpeningServiceImpl.kt @@ -74,13 +74,13 @@ class FileOpeningServiceImpl : FileOpeningService { displayName = it .getString(0) ?.let { name -> - sanitizeString(name, "")?.trim()?.let { - if (it.isEmpty() || it.startsWith(".")) { - "$DEFAULT_FILENAME$it" - } else if (it.endsWith(".")) { + sanitizeString(name, "").trim().let { sanitizedString -> + if (sanitizedString.isEmpty() || sanitizedString.startsWith(".")) { + "$DEFAULT_FILENAME$sanitizedString" + } else if (sanitizedString.endsWith(".")) { DEFAULT_FILENAME } else { - it + sanitizedString } } }?.let { sanitized -> diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/ProxyServicesSettingsScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/ProxyServicesSettingsScreen.kt index 4ed2e6571..81221de64 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/ProxyServicesSettingsScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/ProxyServicesSettingsScreen.kt @@ -282,6 +282,7 @@ fun ProxyServicesSettingsScreen( .clickable { settingsProxyChoice.value = ProxySetting.NO_PROXY.name setProxySetting(ProxySetting.NO_PROXY) + sharedSettingsViewModel.saveProxySettings(true, ManualProxy("", 80, "", "")) }, verticalAlignment = Alignment.CenterVertically, ) { @@ -302,6 +303,7 @@ fun ProxyServicesSettingsScreen( onClick = { settingsProxyChoice.value = ProxySetting.NO_PROXY.name setProxySetting(ProxySetting.NO_PROXY) + sharedSettingsViewModel.saveProxySettings(true, ManualProxy("", 80, "", "")) }, ) } @@ -328,6 +330,7 @@ fun ProxyServicesSettingsScreen( .clickable { settingsProxyChoice.value = ProxySetting.SYSTEM_PROXY.name setProxySetting(ProxySetting.SYSTEM_PROXY) + sharedSettingsViewModel.saveProxySettings(true, ManualProxy("", 80, "", "")) }, verticalAlignment = Alignment.CenterVertically, ) { @@ -348,6 +351,7 @@ fun ProxyServicesSettingsScreen( onClick = { settingsProxyChoice.value = ProxySetting.SYSTEM_PROXY.name setProxySetting(ProxySetting.SYSTEM_PROXY) + sharedSettingsViewModel.saveProxySettings(true, ManualProxy("", 80, "", "")) }, ) } @@ -372,8 +376,18 @@ fun ProxyServicesSettingsScreen( .fillMaxWidth() .padding(SPadding) .clickable { + val proxyPortValue = proxyPort.text.toIntOrNull() ?: 80 settingsProxyChoice.value = ProxySetting.MANUAL_PROXY.name setProxySetting(ProxySetting.MANUAL_PROXY) + sharedSettingsViewModel.saveProxySettings( + false, + ManualProxy( + host = proxyHost.text, + port = proxyPortValue, + username = proxyUsername.text, + password = proxyPassword.text, + ), + ) }, verticalAlignment = Alignment.CenterVertically, ) { @@ -393,8 +407,18 @@ fun ProxyServicesSettingsScreen( }, selected = settingsProxyChoice.value == ProxySetting.MANUAL_PROXY.name, onClick = { + val proxyPortValue = proxyPort.text.toIntOrNull() ?: 80 settingsProxyChoice.value = ProxySetting.MANUAL_PROXY.name setProxySetting(ProxySetting.MANUAL_PROXY) + sharedSettingsViewModel.saveProxySettings( + false, + ManualProxy( + host = proxyHost.text, + port = proxyPortValue, + username = proxyUsername.text, + password = proxyPassword.text, + ), + ) }, ) } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/LanguageChoiceButtonItem.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/LanguageChoiceButtonItem.kt index 103565e28..014616651 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/LanguageChoiceButtonItem.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/menu/LanguageChoiceButtonItem.kt @@ -22,17 +22,13 @@ package ee.ria.DigiDoc.ui.component.menu import androidx.annotation.StringRes -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Home import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import ee.ria.DigiDoc.R import ee.ria.DigiDoc.utils.Language data class LanguageChoiceButtonItem( @param:StringRes val label: Int = 0, - val icon: ImageVector = Icons.Filled.Home, val locale: String = "", val contentDescription: String = "", val testTag: String = "", diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/TabView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/TabView.kt index 90ff9de17..4ba3c7e63 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/TabView.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/TabView.kt @@ -25,10 +25,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SecondaryTabRow import androidx.compose.material3.Tab -import androidx.compose.material3.TabRow -import androidx.compose.material3.TabRowDefaults -import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -59,14 +57,8 @@ fun TabView( }.testTag(testTag) .fillMaxSize(), ) { - TabRow( + SecondaryTabRow( selectedTabIndex = selectedTabIndex, - indicator = { tabPositions -> - TabRowDefaults.SecondaryIndicator( - modifier = modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]), - color = MaterialTheme.colorScheme.primary, - ) - }, ) { tabItems.forEachIndexed { index, (title, _) -> val isSelected = selectedTabIndex == index diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SignatureAddRadioItem.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SignatureAddRadioItem.kt index 67bc36c38..b948d375e 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SignatureAddRadioItem.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SignatureAddRadioItem.kt @@ -22,17 +22,13 @@ package ee.ria.DigiDoc.ui.component.signing import androidx.annotation.StringRes -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Home import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import ee.ria.DigiDoc.R import ee.ria.DigiDoc.domain.model.methods.SigningMethod data class SignatureAddRadioItem( @param:StringRes val label: Int = 0, - val icon: ImageVector = Icons.Filled.Home, val method: SigningMethod = SigningMethod.NFC, val contentDescription: String = "", val testTag: String = "", diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/utils/locale/LocaleUtilImpl.kt b/app/src/main/kotlin/ee/ria/DigiDoc/utils/locale/LocaleUtilImpl.kt index 8b31b8497..f544191d8 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/utils/locale/LocaleUtilImpl.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/utils/locale/LocaleUtilImpl.kt @@ -45,8 +45,6 @@ class LocaleUtilImpl val config = context.resources.configuration config.setLocale(locale) config.setLayoutDirection(locale) - val configurationContext = context.createConfigurationContext(config) - context.resources.updateConfiguration(config, context.resources.displayMetrics) - return configurationContext + return context.createConfigurationContext(config) } } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModel.kt index c0e35525c..c04ae8f4d 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/DiagnosticsViewModel.kt @@ -216,9 +216,8 @@ class DiagnosticsViewModel contentResolver .openOutputStream(it) .use { outputStream -> - if (outputStream != null) { - ByteStreams.copy(inputStream, outputStream) - } + outputStream ?: throw FileNotFoundException("Unable to open output stream for URI: $it") + ByteStreams.copy(inputStream, outputStream) } } } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt index d4da5eafb..e5c39dfd0 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/SigningViewModel.kt @@ -168,8 +168,8 @@ class SigningViewModel ContainerUtil.getContainerDataFilesDir(context, it) } - dataFiles.mapNotNullTo(uris) { dataFile -> - container.getDataFile(dataFile, dataFilesDir)?.toUri() + dataFiles.mapTo(uris) { dataFile -> + container.getDataFile(dataFile, dataFilesDir).toUri() } } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModel.kt index 22d8de192..1eb12e965 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedContainerViewModel.kt @@ -339,9 +339,8 @@ class SharedContainerViewModel contentResolver .openOutputStream(it) .use { outputStream -> - if (outputStream != null) { - ByteStreams.copy(inputStream, outputStream) - } + outputStream ?: throw FileNotFoundException("Unable to open output stream for URI: $it") + ByteStreams.copy(inputStream, outputStream) } } } diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModel.kt index 6c21e2f2c..ed2cdac1a 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/shared/SharedSettingsViewModel.kt @@ -128,6 +128,8 @@ class SharedSettingsViewModel private val _errorState = MutableStateFlow(null) val errorState: StateFlow = _errorState + private val defaultManualProxySettings = ManualProxy("", 80, "", "") + init { CoroutineScope(Main).launch { configurationRepository.observeConfigurationUpdates { newConfig -> @@ -150,11 +152,13 @@ class SharedSettingsViewModel private fun resetProxySettings() { dataStore.setProxySetting(ProxySetting.NO_PROXY) clearProxySettings() + overrideLibdigidocppProxy(defaultManualProxySettings) } private fun clearProxySettings() { - val manualProxySettings = ManualProxy("", 80, "", "") + val manualProxySettings = defaultManualProxySettings setManualProxySettings(manualProxySettings) + overrideLibdigidocppProxy(defaultManualProxySettings) } private fun setManualProxySettings(manualProxy: ManualProxy) { @@ -253,7 +257,7 @@ class SharedSettingsViewModel if (currentProxySetting == ProxySetting.MANUAL_PROXY) { setManualProxySettings(manualProxySettings) } else if (currentProxySetting == ProxySetting.SYSTEM_PROXY) { - val systemSettings: ProxyConfig = ProxyUtil.getProxy(currentProxySetting, ManualProxy("", 80, "", "")) + val systemSettings: ProxyConfig = ProxyUtil.getProxy(currentProxySetting, defaultManualProxySettings) val proxySettings: ManualProxy? = systemSettings.manualProxy() if (proxySettings != null) { overrideLibdigidocppProxy(proxySettings) @@ -411,27 +415,19 @@ class SharedSettingsViewModel fun handleSivaFile(uri: Uri) { try { val initialStream: InputStream? = contentResolver.openInputStream(uri) - val documentFile = DocumentFile.fromSingleUri(context, uri) - if (documentFile != null) { - val sivaCertFolder = File(context.filesDir, DIR_SIVA_CERT) - if (!sivaCertFolder.exists()) { - val isFolderCreated = sivaCertFolder.mkdirs() - debugLog( - logTag, - String.format("SiVa cert folder created: %s", isFolderCreated), - ) - } - - var fileName = documentFile.name - if (fileName.isNullOrEmpty()) { - fileName = "sivaCert" - } - val sivaFile = File(sivaCertFolder, fileName) - - FileUtils.copyInputStreamToFile(initialStream, sivaFile) - - dataStore.setSettingsSivaCertName(sivaFile.name) + val sivaCertFolder = File(context.filesDir, DIR_SIVA_CERT) + if (!sivaCertFolder.exists()) { + val isFolderCreated = sivaCertFolder.mkdirs() + debugLog(logTag, String.format("SiVa cert folder created: %s", isFolderCreated)) } + val fileName = + DocumentFile + .fromSingleUri(context, uri) + ?.name + .takeUnless { it.isNullOrEmpty() } ?: "sivaCert" + val sivaFile = File(sivaCertFolder, fileName) + FileUtils.copyInputStreamToFile(initialStream, sivaFile) + dataStore.setSettingsSivaCertName(sivaFile.name) } catch (e: Exception) { errorLog(logTag, "Unable to read SiVa certificate file data", e) } @@ -440,27 +436,19 @@ class SharedSettingsViewModel fun handleTsaFile(uri: Uri) { try { val initialStream: InputStream? = contentResolver.openInputStream(uri) - val documentFile = DocumentFile.fromSingleUri(context, uri) - if (documentFile != null) { - val tsaCertFolder = File(context.filesDir, DIR_TSA_CERT) - if (!tsaCertFolder.exists()) { - val isFolderCreated = tsaCertFolder.mkdirs() - debugLog( - logTag, - String.format("TSA cert folder created: %s", isFolderCreated), - ) - } - - var fileName = documentFile.name - if (fileName.isNullOrEmpty()) { - fileName = "tsaCert" - } - val tsaFile = File(tsaCertFolder, fileName) - - FileUtils.copyInputStreamToFile(initialStream, tsaFile) - - dataStore.setTSACertName(tsaFile.name) + val tsaCertFolder = File(context.filesDir, DIR_TSA_CERT) + if (!tsaCertFolder.exists()) { + val isFolderCreated = tsaCertFolder.mkdirs() + debugLog(logTag, String.format("TSA cert folder created: %s", isFolderCreated)) } + val fileName = + DocumentFile + .fromSingleUri(context, uri) + ?.name + .takeUnless { it.isNullOrEmpty() } ?: "tsaCert" + val tsaFile = File(tsaCertFolder, fileName) + FileUtils.copyInputStreamToFile(initialStream, tsaFile) + dataStore.setTSACertName(tsaFile.name) } catch (e: Exception) { errorLog(logTag, "Unable to read TSA certificate file data", e) } @@ -469,27 +457,19 @@ class SharedSettingsViewModel fun handleCryptoCertFile(uri: Uri) { try { val initialStream: InputStream? = contentResolver.openInputStream(uri) - val documentFile = DocumentFile.fromSingleUri(context, uri) - if (documentFile != null) { - val cryptoCertFolder = File(context.filesDir, DIR_CRYPTO_CERT) - if (!cryptoCertFolder.exists()) { - val isFolderCreated = cryptoCertFolder.mkdirs() - debugLog( - logTag, - String.format("Crypto cert folder created: %s", isFolderCreated), - ) - } - - var fileName = documentFile.name - if (fileName.isNullOrEmpty()) { - fileName = "cryptoCert" - } - val cryptoCertFile = File(cryptoCertFolder, fileName) - - FileUtils.copyInputStreamToFile(initialStream, cryptoCertFile) - - dataStore.setCryptoCertName(cryptoCertFile.name) + val cryptoCertFolder = File(context.filesDir, DIR_CRYPTO_CERT) + if (!cryptoCertFolder.exists()) { + val isFolderCreated = cryptoCertFolder.mkdirs() + debugLog(logTag, String.format("Crypto cert folder created: %s", isFolderCreated)) } + val fileName = + DocumentFile + .fromSingleUri(context, uri) + ?.name + .takeUnless { it.isNullOrEmpty() } ?: "cryptoCert" + val cryptoCertFile = File(cryptoCertFolder, fileName) + FileUtils.copyInputStreamToFile(initialStream, cryptoCertFile) + dataStore.setCryptoCertName(cryptoCertFile.name) } catch (e: Exception) { errorLog(logTag, "Unable to read Crypto certificate file data", e) } diff --git a/build.gradle.kts b/build.gradle.kts index 343685708..97ee745a7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.androidLibrary) apply false - alias(libs.plugins.jetbrainsKotlinAndroid) apply false + alias(libs.plugins.ksp) apply false alias(libs.plugins.googleDagger) apply false alias(libs.plugins.jlleitschuhKtlint) alias(libs.plugins.jetbrainsKotlinJvm) apply false diff --git a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/LibcdocPlugin.kt b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/LibcdocPlugin.kt index 5543e541f..5fc949cfb 100644 --- a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/LibcdocPlugin.kt +++ b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/LibcdocPlugin.kt @@ -26,6 +26,9 @@ import org.gradle.api.Project open class LibcdocPlugin : Plugin { override fun apply(project: Project) { - project.tasks.register("updateLibcdoc", UpdateLibcdocTask::class.java) + project.tasks.register("updateLibcdoc", UpdateLibcdocTask::class.java) { + rootDir = project.rootDir + projectDir = project.projectDir + } } } diff --git a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/UpdateLibcdocTask.kt b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/UpdateLibcdocTask.kt index b949130cd..3560f423b 100644 --- a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/UpdateLibcdocTask.kt +++ b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libcdoc/update/UpdateLibcdocTask.kt @@ -23,6 +23,7 @@ package ee.ria.DigiDoc.libcdoc.update import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import java.io.File @@ -44,6 +45,12 @@ import javax.tools.StandardJavaFileManager import javax.tools.ToolProvider open class UpdateLibcdocTask : DefaultTask() { + @get:Internal + lateinit var rootDir: File + + @get:Internal + lateinit var projectDir: File + companion object { private const val PREFIX = "libcdoc." private const val SUFFIX = ".zip" @@ -72,7 +79,7 @@ open class UpdateLibcdocTask : DefaultTask() { @TaskAction fun run() { - val inputDir = File(project.rootDir, dir) + val inputDir = File(rootDir, dir) val outputDir = temporaryDir outputDir.deleteRecursively() @@ -110,7 +117,7 @@ open class UpdateLibcdocTask : DefaultTask() { compile(sourceDir) jar(sourceDir, jarFile) - val destinationDir = File(project.projectDir, "libs") + val destinationDir = File(projectDir, "libs") jarFile.copyTo(destinationDir.resolve(JAR), overwrite = true) } @@ -119,8 +126,8 @@ open class UpdateLibcdocTask : DefaultTask() { abi: String ) { val nativeLib = File(cacheDir, "lib/libcdoc_java.so") - val destDirDebug = File(project.projectDir, "src/debug/jniLibs/$abi") - val destDirMain = File(project.projectDir, "src/main/jniLibs/$abi") + val destDirDebug = File(projectDir, "src/debug/jniLibs/$abi") + val destDirMain = File(projectDir, "src/main/jniLibs/$abi") nativeLib.copyTo(File(destDirDebug, "libcdoc_java.so"), true) nativeLib.copyTo(File(destDirMain, "libcdoc_java.so"), true) @@ -173,7 +180,7 @@ open class UpdateLibcdocTask : DefaultTask() { while (zipInputStream.nextEntry.also { entry = it } != null) { val entryName = entry?.name ?: throw ZipException("Invalid zip entry") val entryFile = File(destination, entryName) - if (entry?.isDirectory == true) continue + if (entry.isDirectory) continue if (!entryFile.toPath().normalize().startsWith(destination.toPath())) { throw ZipException("Bad zip entry: $entryName") diff --git a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/LibdigidocppPlugin.kt b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/LibdigidocppPlugin.kt index e2331203d..4cb9f91e8 100644 --- a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/LibdigidocppPlugin.kt +++ b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/LibdigidocppPlugin.kt @@ -26,6 +26,9 @@ import org.gradle.api.Project open class LibdigidocppPlugin : Plugin { override fun apply(project: Project) { - project.tasks.register("updateLibdigidocpp", UpdateLibdigidocppTask::class.java) + project.tasks.register("updateLibdigidocpp", UpdateLibdigidocppTask::class.java) { + rootDir = project.rootDir + projectDir = project.projectDir + } } } diff --git a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/UpdateLibdigidocppTask.kt b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/UpdateLibdigidocppTask.kt index 51580cb8b..fc5da328b 100644 --- a/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/UpdateLibdigidocppTask.kt +++ b/buildSrc/src/main/kotlin/ee/ria/DigiDoc/libdigidoc/update/UpdateLibdigidocppTask.kt @@ -23,6 +23,7 @@ package ee.ria.DigiDoc.libdigidoc.update import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.options.Option import java.io.File @@ -45,6 +46,12 @@ import javax.tools.StandardJavaFileManager import javax.tools.ToolProvider open class UpdateLibdigidocppTask : DefaultTask() { + @get:Internal + lateinit var rootDir: File + + @get:Internal + lateinit var projectDir: File + companion object { private const val PREFIX = "libdigidocpp." private const val SUFFIX = ".zip" @@ -74,7 +81,7 @@ open class UpdateLibdigidocppTask : DefaultTask() { @TaskAction fun run() { - val inputDir = File(project.rootDir, dir) + val inputDir = File(rootDir, dir) val outputDir = temporaryDir outputDir.deleteRecursively() @@ -113,7 +120,7 @@ open class UpdateLibdigidocppTask : DefaultTask() { compile(sourceDir) jar(sourceDir, jarFile) - val destinationDir = File(project.projectDir, "libs") + val destinationDir = File(projectDir, "libs") jarFile.copyTo(destinationDir.resolve(JAR), overwrite = true) } @@ -135,7 +142,7 @@ open class UpdateLibdigidocppTask : DefaultTask() { } } } - val schemaDir = File(project.projectDir, "src/main/res/raw") + val schemaDir = File(projectDir, "src/main/res/raw") schemaZipFile.copyTo(File(schemaDir, SCHEMA), true) } @@ -144,8 +151,8 @@ open class UpdateLibdigidocppTask : DefaultTask() { abi: String ) { val nativeLib = File(cacheDir, "lib/libdigidoc_java.so") - val destDirDebug = File(project.projectDir, "src/debug/jniLibs/$abi") - val destDirMain = File(project.projectDir, "src/main/jniLibs/$abi") + val destDirDebug = File(projectDir, "src/debug/jniLibs/$abi") + val destDirMain = File(projectDir, "src/main/jniLibs/$abi") nativeLib.copyTo(File(destDirDebug, "libdigidoc_java.so"), true) nativeLib.copyTo(File(destDirMain, "libdigidoc_java.so"), true) diff --git a/commons-lib/build.gradle.kts b/commons-lib/build.gradle.kts index 1698ed0cc..3f03a27db 100644 --- a/commons-lib/build.gradle.kts +++ b/commons-lib/build.gradle.kts @@ -2,8 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -19,14 +18,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -48,6 +41,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) @@ -57,7 +56,7 @@ dependencies { implementation(libs.guava) implementation(libs.bouncy.castle) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) androidTestImplementation(libs.byte.buddy) diff --git a/commons-lib/consumer-rules.pro b/commons-lib/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/commons-lib/test-files/build.gradle.kts b/commons-lib/test-files/build.gradle.kts index 1567b7525..9ea086077 100644 --- a/commons-lib/test-files/build.gradle.kts +++ b/commons-lib/test-files/build.gradle.kts @@ -2,7 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) } android { @@ -16,14 +15,14 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } +} - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) } } diff --git a/config-lib/build.gradle.kts b/config-lib/build.gradle.kts index 4a970fdc0..d74153434 100644 --- a/config-lib/build.gradle.kts +++ b/config-lib/build.gradle.kts @@ -2,8 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -19,14 +18,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -48,6 +41,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { api(libs.gson) @@ -57,7 +56,7 @@ dependencies { implementation(libs.androidx.appcompat) implementation(libs.material) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) testImplementation(libs.junit) @@ -93,9 +92,14 @@ configurations { } tasks.register("fetchAndPackageDefaultConfiguration") { + notCompatibleWithConfigurationCache("Uses project.copy() in doLast") dependsOn("build") dependsOn(":networking-lib:build") - classpath(files("${layout.buildDirectory.get().asFile}/tmp/kotlin-classes/release")) + classpath( + files( + "${layout.buildDirectory.get().asFile}/intermediates/classes/release/transformReleaseClassesWithAsm/dirs", + ), + ) classpath(configurations["generateMatchers"]) mainClass = "ee.ria.DigiDoc.configuration.task.FetchAndPackageDefaultConfigurationTask" diff --git a/config-lib/consumer-rules.pro b/config-lib/consumer-rules.pro new file mode 100644 index 000000000..e69de29bb diff --git a/crypto-lib/build.gradle.kts b/crypto-lib/build.gradle.kts index f17a8de84..db4d5bec0 100644 --- a/crypto-lib/build.gradle.kts +++ b/crypto-lib/build.gradle.kts @@ -1,12 +1,11 @@ import ee.ria.DigiDoc.libcdoc.update.LibcdocPlugin import org.jetbrains.kotlin.gradle.dsl.JvmTarget -val appAbiFilters = "arm64-v8a;armeabi-v7a;x86_64" +val appAbiFilters = "arm64-v8a;armeabi-v7a;x86_64".split(';').map { it.trim() } plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -24,19 +23,13 @@ android { ndk { abiFilters.clear() - abiFilters.addAll(appAbiFilters.split(';').map { it.trim() }) + abiFilters.addAll(appAbiFilters) } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -59,13 +52,19 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) implementation(libs.bouncy.castle) api(libs.guava) diff --git a/gradle.properties b/gradle.properties index 070e37376..2bc3fd379 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,9 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 +# Caches the configuration phase result so subsequent builds skip it entirely, +# reducing incremental build times +org.gradle.configuration-cache=true # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects @@ -23,6 +26,9 @@ kotlin.code.style=official android.nonTransitiveRClass=true # Avoid compiling BuildConfig as Java file. Generate as compiled file android.enableBuildConfigAsBytecode=true -# From Kotlin 1.9.20, you can try using the kapt compiler plugin with the K2 compiler, which brings performance improvements and many other benefits. -# https://kotlinlang.org/docs/kapt.html#try-kotlin-k2-compiler -kapt.use.k2=true \ No newline at end of file +# Allows library modules to share package names. Set to false to avoid strict +# unique package name enforcement across modules +android.uniquePackageNames=false +# Disables R8's strict validation of keep rules to avoid build failures caused +# by rules that reference missing classes +android.r8.strictFullModeForKeepRules=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f95eebac9..535f28f07 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,61 +2,63 @@ compileSdkVersion = "36" minSdkVersion = "33" targetSdkVersion = "36" -versionCode = "74" # Updated via CodeMagic pipeline. +versionCode = "1" # Updated via CodeMagic pipeline. versionName = "3.0.5" -agp = "8.13.0" -algp = "8.13.0" -kotlin = "2.2.21" -coreKtx = "1.17.0" +agp = "9.2.1" +algp = "9.2.1" +kotlin = "2.3.21" +ksp = "2.3.7" +coreKtx = "1.18.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" -activityCompose = "1.11.0" -lifecycleRuntimeKtx = "2.9.4" -navigationCompose = "2.9.5" +activityCompose = "1.13.0" +lifecycleRuntimeKtx = "2.10.0" +navigationCompose = "2.9.8" constraintlayoutCompose = "1.1.1" -composeBom = "2025.09.00" -coreSplashscreen = "1.0.1" +composeBom = "2026.05.00" +coreSplashscreen = "1.2.0" documentfile = "1.1.0" -jlleitschuhKtlint = "13.1.0" -dagger = "2.57.2" +androidxui = "1.11.1" +androidxMaterial3 = "1.4.0" +jlleitschuhKtlint = "14.2.0" +dagger = "2.59.2" hilt = "1.3.0" appcompat = "1.7.1" material = "1.13.0" -mockito = "5.20.0" -mockito-kotlin = "6.1.0" +mockito = "5.23.0" +mockito-kotlin = "6.3.0" kotlinx-coroutines-test = "1.10.2" -gson = "2.13.2" -jackson-databind = "2.20.0" +gson = "2.14.0" +jackson-databind = "2.21.3" threegpp-telecom-charsets = "1.0.1" -commons-io = "2.20.0" -commons-text = "1.14.0" -commonsCodec = "1.19.0" +commons-io = "2.22.0" +commons-text = "1.15.0" +commonsCodec = "1.22.0" commons-compress = "1.28.0" -bouncy-castle = "1.82" -okhttp = "5.2.1" +bouncy-castle = "1.84" +okhttp = "5.3.2" retrofit = "3.0.0" preferencex = "1.1.0" androidx-security-crypto = "1.1.0" pdfbox-android = "2.0.27.0" -guava = "33.5.0-android" +guava = "33.6.0-android" coreKtxVersion = "1.7.0" androidx-arch-core-testing = "2.2.0" -firebaseCrashlytics = "3.0.6" +firebaseCrashlytics = "3.0.7" googleServices = "4.4.4" firebaseCrashlyticsKtx = "19.4.4" kotlinxCoroutinesRx3 = "1.10.2" cdoc4j = "1.5" stax-api = "1.0-2" -unboundid-ldapsdk = "7.0.3" - -# Override Byte Buddy version to avoid issues with Android builds -#noinspection NewerVersionAvailable -byte-buddy = "1.14.6" +unboundid-ldapsdk = "7.0.4" +material-icons-core = "1.7.8" +byte-buddy = "1.18.8" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core", version.ref = "material-icons-core" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -65,13 +67,13 @@ androidx-activity-compose = { group = "androidx.activity", name = "activity-comp androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "coreSplashscreen" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } -androidx-ui = { group = "androidx.compose.ui", name = "ui" } -androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } -androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } -androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidxui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "androidxui" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidxui" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidxui" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "androidxui" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "androidxui" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxMaterial3" } androidx-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "constraintlayoutCompose" } google-dagger-hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "dagger" } @@ -115,7 +117,7 @@ kotlinx-coroutines-rx3 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "algp" } -jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } jlleitschuhKtlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "jlleitschuhKtlint" } googleDagger = {id = "com.google.dagger.hilt.android", version.ref = "dagger"} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cfc881094..9409dbc40 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Mar 27 15:38:54 EET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/id-card-lib/build.gradle.kts b/id-card-lib/build.gradle.kts index 77090fc0b..5b536f784 100644 --- a/id-card-lib/build.gradle.kts +++ b/id-card-lib/build.gradle.kts @@ -2,8 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -18,14 +17,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildTypes { @@ -43,6 +36,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { api(project(":id-card-lib:id-lib")) api(project(":id-card-lib:smart-lib")) @@ -53,7 +52,7 @@ dependencies { implementation(libs.bouncy.castle) implementation(libs.guava) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) implementation(libs.kotlinx.coroutines.rx3) diff --git a/libdigidoc-lib/build.gradle.kts b/libdigidoc-lib/build.gradle.kts index 949193d38..4628c3222 100644 --- a/libdigidoc-lib/build.gradle.kts +++ b/libdigidoc-lib/build.gradle.kts @@ -1,12 +1,11 @@ import ee.ria.DigiDoc.libdigidoc.update.LibdigidocppPlugin import org.jetbrains.kotlin.gradle.dsl.JvmTarget -val appAbiFilters = "arm64-v8a;armeabi-v7a;x86_64" +val appAbiFilters = "arm64-v8a;armeabi-v7a;x86_64".split(';').map { it.trim() } plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -24,19 +23,13 @@ android { ndk { abiFilters.clear() - abiFilters.addAll(appAbiFilters.split(';').map { it.trim() }) + abiFilters.addAll(appAbiFilters) } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -58,6 +51,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) @@ -68,7 +67,7 @@ dependencies { implementation(libs.bouncy.castle) implementation(libs.google.dagger.hilt.android) implementation(libs.preferencex) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) testImplementation(libs.junit) diff --git a/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt b/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt index d11bd12a2..09cbc48b0 100644 --- a/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt +++ b/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainerTest.kt @@ -348,7 +348,7 @@ class SignedContainerTest { }, ) - assertEquals(testFile.name, result?.name) + assertEquals(testFile.name, result.name) } @Test diff --git a/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtilsTest.kt b/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtilsTest.kt index 43cf97346..864d1cd4a 100644 --- a/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtilsTest.kt +++ b/libdigidoc-lib/src/androidTest/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtilsTest.kt @@ -116,13 +116,7 @@ class FileUtilsTest { `when`(context.cacheDir).thenReturn(cacheDir) `when`(resources.openRawResource(anyInt())).thenReturn(inputStream) - try { - initSchema(context) - } catch (e: ZipException) { - assertEquals(ZipException::class.java, e.javaClass) - assertEquals("Bad zip entry: ../file.txt", e.message) - throw e - } + initSchema(context) } @Test diff --git a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt index 95316cbf8..fb5c4954e 100644 --- a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt +++ b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/SignedContainer.kt @@ -96,7 +96,7 @@ class SignedContainer val containerDataFilesDir = ContainerUtil.getContainerDataFilesDir(context, containerRawFile) val nestedTimestampedFile = getDataFile(DataFileWrapper(dataFile), containerDataFilesDir) - return nestedTimestampedFile?.let { + return nestedTimestampedFile.let { SignedContainer( context = context, container = open(context, it, true).rawContainer(), @@ -138,17 +138,15 @@ class SignedContainer suspend fun setName(filename: String) { val name = sanitizeString(filename, "") - val containerName = name?.let { ContainerUtil.addExtensionToContainerFilename(it) } - val newFile = containerName?.let { File(containerFile?.parent, it) } + val containerName = name.let { ContainerUtil.addExtensionToContainerFilename(it) } + val newFile = File(containerFile?.parent, containerName) - if (newFile != null) { - withContext(IO) { - containerFile?.renameTo(newFile) - containerFile = newFile + withContext(IO) { + containerFile?.renameTo(newFile) + containerFile = newFile - containerFile?.let { - container?.save(newFile.path) - } + containerFile?.let { + container?.save(newFile.path) } } } @@ -175,16 +173,14 @@ class SignedContainer fun getDataFile( dataFile: DataFileInterface, directory: File?, - ): File? { - val file = sanitizeString(dataFile.fileName, "")?.let { File(directory, it) } + ): File { + val file = File(directory, sanitizeString(dataFile.fileName, "")) val dataFiles = container?.dataFiles() if (dataFiles != null) { for (i in dataFiles.indices) { val containerDataFile = dataFiles[i] if (dataFile.id == containerDataFile.id()) { - if (file != null) { - containerDataFile.saveAs(file.absolutePath) - } + containerDataFile.saveAs(file.absolutePath) return file } } diff --git a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt index 96c829bb8..3560fb155 100644 --- a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt +++ b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt @@ -23,9 +23,9 @@ package ee.ria.DigiDoc.libdigidoclib.utils import android.content.Context import android.content.res.Resources.NotFoundException -import android.util.Log import ee.ria.DigiDoc.libdigidoclib.R import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.debugLog +import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog import java.io.File import java.io.IOException import java.io.InputStream @@ -65,7 +65,7 @@ object FileUtils { try { schemaResourceInputStream = context.resources.openRawResource(R.raw.schema) } catch (nfe: NotFoundException) { - Log.e(LIBDIGIDOC_FILEUTILS_LOG_TAG, "Unable to get 'schema' resource", nfe) + errorLog(LIBDIGIDOC_FILEUTILS_LOG_TAG, "Unable to get 'schema' resource", nfe) throw nfe } schemaResourceInputStream.use { inputStream -> diff --git a/mobile-id-lib/build.gradle.kts b/mobile-id-lib/build.gradle.kts index 102373ed4..aa21222ef 100644 --- a/mobile-id-lib/build.gradle.kts +++ b/mobile-id-lib/build.gradle.kts @@ -2,8 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -18,14 +17,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } packaging { @@ -45,6 +38,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) @@ -53,7 +52,7 @@ dependencies { implementation(libs.gson) implementation(libs.preferencex) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) testImplementation(libs.junit) diff --git a/networking-lib/build.gradle.kts b/networking-lib/build.gradle.kts index 4c8e4d13f..b0ab3fbd9 100644 --- a/networking-lib/build.gradle.kts +++ b/networking-lib/build.gradle.kts @@ -2,7 +2,6 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) } android { @@ -16,14 +15,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildTypes { @@ -40,6 +33,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { api(libs.okhttp3) diff --git a/smart-id-lib/build.gradle.kts b/smart-id-lib/build.gradle.kts index 4e511d517..6648119b8 100644 --- a/smart-id-lib/build.gradle.kts +++ b/smart-id-lib/build.gradle.kts @@ -2,8 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -18,14 +17,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } packaging { @@ -43,13 +36,19 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) testImplementation(libs.junit) androidTestImplementation(libs.retrofit.mock) diff --git a/utils-lib/build.gradle.kts b/utils-lib/build.gradle.kts index 1c55473a1..c2f9f0217 100644 --- a/utils-lib/build.gradle.kts +++ b/utils-lib/build.gradle.kts @@ -2,8 +2,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - kotlin("kapt") + alias(libs.plugins.ksp) id("com.google.dagger.hilt.android") } @@ -18,14 +17,8 @@ android { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - kotlin { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } buildFeatures { @@ -47,6 +40,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + dependencies { implementation(libs.androidx.core.ktx) @@ -62,7 +61,7 @@ dependencies { implementation(libs.preferencex) implementation(libs.google.dagger.hilt.android) - kapt(libs.google.dagger.hilt.android.compile) + ksp(libs.google.dagger.hilt.android.compile) implementation(libs.androidx.hilt) implementation(libs.pdfbox.android) { diff --git a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/extensions/FileExtensions.kt b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/extensions/FileExtensions.kt index 9745f1ad0..801ff659c 100644 --- a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/extensions/FileExtensions.kt +++ b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/extensions/FileExtensions.kt @@ -22,7 +22,6 @@ package ee.ria.DigiDoc.utilsLib.extensions import android.content.Context -import android.util.Log import android.webkit.MimeTypeMap import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import com.tom_roush.pdfbox.pdmodel.PDDocument @@ -40,6 +39,7 @@ import ee.ria.DigiDoc.utilsLib.file.FileUtil.getFileInContainerZip import ee.ria.DigiDoc.utilsLib.file.FileUtil.parseXMLFile import ee.ria.DigiDoc.utilsLib.file.FileUtil.readFileAsString import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.debugLog +import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.compress.archivers.zip.ZipFile import java.io.File @@ -134,7 +134,7 @@ fun File.isSignedPDF(context: Context): Boolean { } } } catch (e: IOException) { - Log.e(FILE_EXTENSIONS_LOG_TAG, "Unable to check if PDF is signed", e) + errorLog(FILE_EXTENSIONS_LOG_TAG, "Unable to check if PDF is signed", e) false } } @@ -145,7 +145,7 @@ fun File.md5Hash(): String { return DigestUtils.md5Hex(fis) } } catch (e: IOException) { - Log.e(FILE_EXTENSIONS_LOG_TAG, "Unable to get MD5 of file: ${this.name}", e) + errorLog(FILE_EXTENSIONS_LOG_TAG, "Unable to get MD5 of file: ${this.name}", e) return "" } } diff --git a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt index 62fd4f4e5..0c3146c67 100644 --- a/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt +++ b/utils-lib/src/main/kotlin/ee/ria/DigiDoc/utilsLib/file/FileUtil.kt @@ -55,7 +55,6 @@ import java.io.OutputStreamWriter import java.nio.charset.Charset import java.nio.charset.StandardCharsets import java.nio.file.Files -import java.util.Objects import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.SAXParserFactory @@ -292,15 +291,15 @@ object FileUtil { content: String?, ) { val file = File(filePath) - val parentFile = Objects.requireNonNull(file.getParentFile()) - if (!parentFile.exists()) { - val isDirsCreated = parentFile.mkdirs() - if (isDirsCreated) { - infoLog(LOG_TAG, "Directories created or already exist for $filePath") - } - } try { - FileOutputStream(file.getAbsoluteFile()).use { outputStream -> + val parentFile = file.parentFile ?: throw IOException("No parent directory for path: $filePath") + if (!parentFile.exists()) { + val isDirsCreated = parentFile.mkdirs() + if (isDirsCreated) { + infoLog(LOG_TAG, "Directories created or already exist for $filePath") + } + } + FileOutputStream(file.absoluteFile).use { outputStream -> OutputStreamWriter(outputStream, StandardCharsets.UTF_8) .use { writer -> writer.write(content) @@ -316,14 +315,14 @@ object FileUtil { content: ByteArray?, ) { val file = File(filePath) - val parentFile = Objects.requireNonNull(file.getParentFile()) - if (!parentFile.exists()) { - val isDirsCreated = parentFile.mkdirs() - if (isDirsCreated) { - infoLog(LOG_TAG, "Directories created or already exist for $filePath") - } - } try { + val parentFile = file.parentFile ?: throw IOException("No parent directory for path: $filePath") + if (!parentFile.exists()) { + val isDirsCreated = parentFile.mkdirs() + if (isDirsCreated) { + infoLog(LOG_TAG, "Directories created or already exist for $filePath") + } + } FileOutputStream(file).use { os -> os.write(content) } } catch (e: IOException) { throw IllegalStateException("Failed to store file '$filePath'!", e) @@ -481,7 +480,7 @@ object FileUtil { start: Int, length: Int, ) { - val text = java.lang.String(ch, start, length).trim() + val text = ch?.let { String(it, start, length).trim() }.orEmpty() if (text.isNotEmpty()) { currentElement?.appendChild(document.createTextNode(text)) }