diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f2f59e121..d9050460e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,4 @@ -import org.jetbrains.kotlin.konan.properties.loadProperties +import java.util.Properties plugins { alias(libs.plugins.android.application) @@ -15,9 +15,12 @@ val sharedCompileSdk: Int by rootProject.extra val sharedMinSdk: Int by rootProject.extra val sharedJavaVersion: JavaVersion by rootProject.extra -val envProperties = loadProperties("env.properties") -val sentryAuthToken = envProperties.getProperty("sentryAuthToken").takeIf { it.isNotEmpty() } - ?: error("The `sentryAuthToken` property in `env.properties` must be defined and not empty (see `env.example.properties`).") +val envProperties = rootProject.file("env.properties").takeIf { it.exists() }?.let { file -> + Properties().also { it.load(file.reader()) } +} + +val sentryAuthToken = envProperties?.getProperty("sentryAuthToken").takeUnless { it.isNullOrBlank() } + ?: error("The `sentryAuthToken` property in `env.properties` must be specified (see `env.example.properties`).") android { namespace = "com.infomaniak.swisstransfer" @@ -27,8 +30,8 @@ android { applicationId = "com.infomaniak.swisstransfer" minSdk = sharedMinSdk targetSdk = sharedCompileSdk - versionCode = 1 // 0_00_000_01 TODO: Update when released in prod - versionName = "0.0.1-Alpha1" + versionCode = 1_00_000_00 + versionName = "1.0.0-Alpha2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferCard.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferCard.kt index 5e8f63299..4f3b5d0bb 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferCard.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferCard.kt @@ -17,6 +17,7 @@ */ package com.infomaniak.swisstransfer.ui.components +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.padding @@ -27,6 +28,7 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import com.infomaniak.swisstransfer.ui.theme.CustomShapes import com.infomaniak.swisstransfer.ui.theme.Margin @@ -34,10 +36,17 @@ import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme import com.infomaniak.swisstransfer.ui.utils.PreviewLightAndDark @Composable -fun SwissTransferCard(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) { +fun SwissTransferCard(modifier: Modifier = Modifier, onClick: (() -> Unit)? = null, content: @Composable ColumnScope.() -> Unit) { Card( - modifier = modifier, - shape = CustomShapes.MEDIUM, + modifier = modifier + .clip(shape = CustomShapes.MEDIUM) + .then( + other = if (onClick == null) { + Modifier + } else { + Modifier.clickable(onClick = onClick) + } + ), colors = CardDefaults.cardColors(contentColor = SwissTransferTheme.colors.secondaryTextColor), content = content, ) @@ -51,7 +60,7 @@ private fun SwissTransferCardPreview() { SwissTransferCard( modifier = Modifier .size(300.dp, 200.dp) - .padding(Margin.Medium) + .padding(Margin.Medium), ) { Column(Modifier.padding(Margin.Large)) { Text("Hello World!") diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt index 69616eec1..4ac4f4a52 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/navigation/NavigationDestination.kt @@ -17,18 +17,17 @@ */ package com.infomaniak.swisstransfer.ui.navigation -import android.os.Bundle import androidx.compose.animation.AnimatedContentScope import androidx.compose.runtime.Composable import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable import androidx.navigation.navDeepLink +import androidx.navigation.toRoute +import com.infomaniak.sentry.SentryLog import com.infomaniak.swisstransfer.BuildConfig import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.components.TransferTypeUi import kotlinx.serialization.Serializable -import kotlin.reflect.KClass -import kotlin.reflect.full.primaryConstructor /** * Sealed class representing the navigation arguments for the main navigation flow. @@ -37,9 +36,11 @@ import kotlin.reflect.full.primaryConstructor sealed class MainNavigation : NavigationDestination() { var enableTransition = true + // If it has to be renamed, don't forget to rename `*DestinationName` in the companion object too. @Serializable data object SentDestination : MainNavigation() + // If it has to be renamed, don't forget to rename `*DestinationName` in the companion object too. @Serializable data class ReceivedDestination(val transferUuid: String? = null) : MainNavigation() { @@ -57,15 +58,35 @@ sealed class MainNavigation : NavigationDestination() { } } } - - @Serializable - data class TransferDetailsDestination(val transferUuid: String) : MainNavigation() + // If it has to be renamed, don't forget to rename `*DestinationName` in the companion object too. @Serializable data object SettingsDestination : MainNavigation() companion object { + private val TAG = MainNavigation::class.java.simpleName val startDestination = SentDestination + + // If these classes have to be renamed, they need to be renamed here too. + val sentDestinationName = "SentDestination" + val receivedDestinationName = "ReceivedDestination" + val settingsDestinationName = "SettingsDestination" + val destinationsNames = listOf(sentDestinationName, receivedDestinationName, settingsDestinationName) + + fun NavBackStackEntry.toMainDestination(): MainNavigation? { + return runCatching { + val destinationRoute = destination.route ?: error("Destination route cannot be empty") + when (destinationsNames.firstOrNull { destinationRoute.contains(it) }) { + sentDestinationName -> this.toRoute() + receivedDestinationName -> this.toRoute() + settingsDestinationName -> this.toRoute() + else -> error("Destination $destinationRoute is not handled") + } + }.getOrElse { exception -> + SentryLog.e(TAG, "toMainDestination: Failure", exception) + null + } + } } } @@ -98,34 +119,4 @@ sealed class NewTransferNavigation : NavigationDestination() { * Sealed class representing navigation arguments with a title resource. */ @Serializable -sealed class NavigationDestination { - - companion object { - - inline fun NavBackStackEntry.toDestination(): T? { - return toDestination(T::class, backStackEntry = this) - } - - fun toDestination(kClass: KClass, backStackEntry: NavBackStackEntry?): T? { - - fun kClassFromRoute(route: String) = kClass.sealedSubclasses.firstOrNull { - route.contains(it.qualifiedName.toString()) - } - - if (backStackEntry == null) return null - - val route = backStackEntry.destination.route ?: "" - val args = backStackEntry.arguments - val subclass = kClassFromRoute(route) ?: return null - - return createInstance(subclass, args) - } - - private fun createInstance(kClass: KClass, bundle: Bundle?): T? { - return kClass.primaryConstructor?.let { - val args = it.parameters.associateWith { parameter -> bundle?.get(parameter.name) } - it.callBy(args) - } ?: kClass.objectInstance - } - } -} +sealed class NavigationDestination diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt index a42714170..fe06e199f 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/MainScreen.kt @@ -25,7 +25,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import com.infomaniak.swisstransfer.ui.components.BrandTopAppBar import com.infomaniak.swisstransfer.ui.navigation.MainNavigation -import com.infomaniak.swisstransfer.ui.navigation.NavigationDestination.Companion.toDestination +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation.Companion.toMainDestination import com.infomaniak.swisstransfer.ui.screen.main.components.MainScaffold import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme import com.infomaniak.swisstransfer.ui.utils.PreviewAllWindows @@ -37,7 +37,7 @@ fun MainScreen() { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination by remember(navBackStackEntry) { - derivedStateOf { navBackStackEntry?.toDestination() ?: MainNavigation.startDestination } + derivedStateOf { navBackStackEntry?.toMainDestination() ?: MainNavigation.startDestination } } MainScaffold( diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/components/MainScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/components/MainScaffold.kt index 563d53799..c66aa7f66 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/components/MainScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/components/MainScaffold.kt @@ -17,7 +17,6 @@ */ package com.infomaniak.swisstransfer.ui.screen.main.components -import android.content.Intent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.material3.HorizontalDivider @@ -26,10 +25,8 @@ import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.navigation.NavController import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController -import com.infomaniak.core2.extensions.parcelableExtra import com.infomaniak.swisstransfer.ui.components.BrandTopAppBar import com.infomaniak.swisstransfer.ui.navigation.MainNavigation import com.infomaniak.swisstransfer.ui.navigation.NavigationItem diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/ImportFilesViewModel.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/ImportFilesViewModel.kt index fad25d73a..9f7fcfd49 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/ImportFilesViewModel.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/ImportFilesViewModel.kt @@ -18,7 +18,6 @@ package com.infomaniak.swisstransfer.ui.screen.newtransfer import android.net.Uri -import android.util.Log import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -26,18 +25,13 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.infomaniak.core2.appintegrity.AppIntegrityManager -import com.infomaniak.core2.appintegrity.AppIntegrityManager.Companion.APP_INTEGRITY_MANAGER_TAG -import com.infomaniak.multiplatform_swisstransfer.SharedApiUrlCreator import com.infomaniak.multiplatform_swisstransfer.common.interfaces.upload.RemoteUploadFile import com.infomaniak.multiplatform_swisstransfer.common.interfaces.upload.UploadFileSession import com.infomaniak.multiplatform_swisstransfer.common.utils.mapToList import com.infomaniak.multiplatform_swisstransfer.data.NewUploadSession import com.infomaniak.multiplatform_swisstransfer.managers.AppSettingsManager import com.infomaniak.multiplatform_swisstransfer.managers.UploadManager -import com.infomaniak.multiplatform_swisstransfer.network.exceptions.ContainerErrorsException import com.infomaniak.sentry.SentryLog -import com.infomaniak.swisstransfer.BuildConfig import com.infomaniak.swisstransfer.di.IoDispatcher import com.infomaniak.swisstransfer.ui.screen.main.settings.DownloadLimitOption import com.infomaniak.swisstransfer.ui.screen.main.settings.DownloadLimitOption.Companion.toTransferOption @@ -52,31 +46,27 @@ import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.TransferOp import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.components.TransferTypeUi import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.components.TransferTypeUi.Companion.toTransferTypeUi import com.infomaniak.swisstransfer.ui.utils.GetSetCallbacks -import com.infomaniak.swisstransfer.workers.UploadWorker import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class ImportFilesViewModel @Inject constructor( private val appSettingsManager: AppSettingsManager, - private val appIntegrityManager: AppIntegrityManager, private val savedStateHandle: SavedStateHandle, private val importationFilesManager: ImportationFilesManager, - private val sharedApiUrlCreator: SharedApiUrlCreator, private val uploadManager: UploadManager, - private val uploadWorkerScheduler: UploadWorker.Scheduler, + private val transferSendManager: TransferSendManager, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { - private val _sendActionResult = MutableStateFlow(SendActionResult.NotStarted) - val sendActionResult = _sendActionResult.asStateFlow() - - private val _integrityCheckResult = MutableStateFlow(AppIntegrityResult.Idle) - val integrityCheckResult = _integrityCheckResult.asStateFlow() + val sendActionResult by transferSendManager::sendActionResult + val integrityCheckResult by transferSendManager::integrityCheckResult @OptIn(FlowPreview::class) val importedFilesDebounced = importationFilesManager.importedFiles @@ -139,86 +129,19 @@ class ImportFilesViewModel @Inject constructor( } } - private fun sendTransfer(attestationToken: String) { - _sendActionResult.update { SendActionResult.Pending } + fun sendTransfer() { viewModelScope.launch(ioDispatcher) { - runCatching { - val uuid = uploadManager.createAndGetUpload(generateNewUploadSession()).uuid - uploadManager.initUploadSession( - attestationHeaderName = AppIntegrityManager.ATTESTATION_TOKEN_HEADER, - attestationToken = attestationToken, - )!! // TODO: Handle ContainerErrorsException here - uploadWorkerScheduler.scheduleWork(uuid) - _sendActionResult.update { - val totalSize = importationFilesManager.importedFiles.value.sumOf { it.fileSize } - SendActionResult.Success(totalSize) - } - }.onFailure { exception -> - SentryLog.e(TAG, "Failed to start the upload", exception) - val result = when (exception) { - is ContainerErrorsException.EmailValidationRequired -> SendActionResult.RequireEmailValidation - else -> SendActionResult.Failure - } - _sendActionResult.update { result } - } + transferSendManager.sendTransfer(generateNewUploadSession()) } } fun setDefaultSendActionResult() { - _sendActionResult.value = SendActionResult.NotStarted - } - - //region App Integrity - fun checkAppIntegrity() { - _integrityCheckResult.value = AppIntegrityResult.Ongoing - viewModelScope.launch(ioDispatcher) { - runCatching { - appIntegrityManager.getChallenge( - onSuccess = { requestAppIntegrityToken(appIntegrityManager) }, - onFailure = ::setFailedIntegrityResult, - ) - }.onFailure { exception -> - SentryLog.e(TAG, "Failed to start the upload", exception) - _sendActionResult.update { SendActionResult.Failure } - } - } - } - - private fun requestAppIntegrityToken(appIntegrityManager: AppIntegrityManager) { - appIntegrityManager.requestClassicIntegrityVerdictToken( - onSuccess = { token -> - SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "request for app integrity token successful $token") - getApiIntegrityVerdict(appIntegrityManager, token) - }, - onFailure = ::setFailedIntegrityResult, - ) - } - - private fun getApiIntegrityVerdict(appIntegrityManager: AppIntegrityManager, appIntegrityToken: String) { - viewModelScope.launch(ioDispatcher) { - appIntegrityManager.getApiIntegrityVerdict( - integrityToken = appIntegrityToken, - packageName = BuildConfig.APPLICATION_ID, - targetUrl = sharedApiUrlCreator.createUploadContainerUrl, - onSuccess = { attestationToken -> - SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "Api verdict check") - Log.i(APP_INTEGRITY_MANAGER_TAG, "getApiIntegrityVerdict: $attestationToken") - _integrityCheckResult.value = AppIntegrityResult.Success - sendTransfer(attestationToken) - }, - onFailure = ::setFailedIntegrityResult, - ) - } - } - - private fun setFailedIntegrityResult() { - _integrityCheckResult.value = AppIntegrityResult.Fail + transferSendManager.setDefaultSendActionResult() } fun resetIntegrityCheckResult() { - _integrityCheckResult.value = AppIntegrityResult.Idle + transferSendManager.resetIntegrityCheckResult() } - //endregion private suspend fun removeOldData() { importationFilesManager.removeLocalCopyFolder() diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/TransferSendManager.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/TransferSendManager.kt new file mode 100644 index 000000000..998a6fd51 --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/TransferSendManager.kt @@ -0,0 +1,148 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.swisstransfer.ui.screen.newtransfer + +import android.util.Log +import com.infomaniak.core2.appintegrity.AppIntegrityManager +import com.infomaniak.core2.appintegrity.AppIntegrityManager.Companion.APP_INTEGRITY_MANAGER_TAG +import com.infomaniak.multiplatform_swisstransfer.SharedApiUrlCreator +import com.infomaniak.multiplatform_swisstransfer.data.NewUploadSession +import com.infomaniak.multiplatform_swisstransfer.managers.UploadManager +import com.infomaniak.multiplatform_swisstransfer.network.exceptions.ContainerErrorsException +import com.infomaniak.sentry.SentryLog +import com.infomaniak.swisstransfer.BuildConfig +import com.infomaniak.swisstransfer.ui.screen.newtransfer.ImportFilesViewModel.AppIntegrityResult +import com.infomaniak.swisstransfer.ui.screen.newtransfer.ImportFilesViewModel.SendActionResult +import com.infomaniak.swisstransfer.workers.UploadWorker +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ViewModelScoped +class TransferSendManager @Inject constructor( + private val appIntegrityManager: AppIntegrityManager, + private val importationFilesManager: ImportationFilesManager, + private val sharedApiUrlCreator: SharedApiUrlCreator, + private val uploadManager: UploadManager, + private val uploadWorkerScheduler: UploadWorker.Scheduler, +) { + + // TODO: Merge these two ui states in a single one for the whole flow of logic + private val _sendActionResult = MutableStateFlow(SendActionResult.NotStarted) + val sendActionResult = _sendActionResult.asStateFlow() + + private val _integrityCheckResult = MutableStateFlow(AppIntegrityResult.Idle) + val integrityCheckResult = _integrityCheckResult.asStateFlow() + + //region App Integrity + suspend fun sendTransfer(newUploadSession: NewUploadSession) { + _integrityCheckResult.value = AppIntegrityResult.Ongoing + coroutineScope { + runCatching { + appIntegrityManager.getChallenge( + onSuccess = { requestAppIntegrityToken(appIntegrityManager, newUploadSession) }, + onFailure = ::setFailedIntegrityResult, + ) + }.onFailure { exception -> + SentryLog.e(TAG, "Failed to start the upload", exception) + val result = when (exception) { + is ContainerErrorsException.EmailValidationRequired -> SendActionResult.RequireEmailValidation + else -> SendActionResult.Failure + } + _sendActionResult.update { result } + } + } + } + + private fun CoroutineScope.requestAppIntegrityToken( + appIntegrityManager: AppIntegrityManager, + newUploadSession: NewUploadSession, + ) { + appIntegrityManager.requestClassicIntegrityVerdictToken( + onSuccess = { token -> + SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "request for app integrity token successful $token") + getApiIntegrityVerdict(appIntegrityManager, token, newUploadSession) + }, + onFailure = ::setFailedIntegrityResult, + ) + } + + private fun CoroutineScope.getApiIntegrityVerdict( + appIntegrityManager: AppIntegrityManager, + appIntegrityToken: String, + newUploadSession: NewUploadSession, + ) { + launch { + appIntegrityManager.getApiIntegrityVerdict( + integrityToken = appIntegrityToken, + packageName = BuildConfig.APPLICATION_ID, + targetUrl = sharedApiUrlCreator.createUploadContainerUrl, + onSuccess = { attestationToken -> + SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "Api verdict check") + Log.i(APP_INTEGRITY_MANAGER_TAG, "getApiIntegrityVerdict: $attestationToken") + _integrityCheckResult.value = AppIntegrityResult.Success + sendTransfer(attestationToken, newUploadSession) + }, + onFailure = ::setFailedIntegrityResult, + ) + } + } + + private fun setFailedIntegrityResult() { + _integrityCheckResult.value = AppIntegrityResult.Fail + } + + fun resetIntegrityCheckResult() { + _integrityCheckResult.value = AppIntegrityResult.Idle + } + //endregion + + fun setDefaultSendActionResult() { + _sendActionResult.value = SendActionResult.NotStarted + } + + private fun CoroutineScope.sendTransfer(attestationToken: String, newUploadSession: NewUploadSession) { + _sendActionResult.update { SendActionResult.Pending } + launch { + runCatching { + val uuid = uploadManager.createAndGetUpload(newUploadSession).uuid + uploadManager.initUploadSession( + attestationHeaderName = AppIntegrityManager.ATTESTATION_TOKEN_HEADER, + attestationToken = attestationToken, + )!! // TODO: Handle ContainerErrorsException here + uploadWorkerScheduler.scheduleWork(uuid) + _sendActionResult.update { + val totalSize = importationFilesManager.importedFiles.value.sumOf { it.fileSize } + SendActionResult.Success(totalSize) + } + }.onFailure { exception -> + SentryLog.e(TAG, "Failed to start the upload", exception) + _sendActionResult.update { SendActionResult.Failure } + } + } + } + + companion object { + private val TAG = TransferSendManager::class.java.simpleName + } +} diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/ImportFilesScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/ImportFilesScreen.kt index c3f220a71..2a0cc9b77 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/ImportFilesScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/ImportFilesScreen.kt @@ -145,7 +145,7 @@ fun ImportFilesScreen( addFiles = importFilesViewModel::importFiles, closeActivity = closeActivity, integrityCheckResult = { integrityCheckResult }, - checkAppIntegrity = importFilesViewModel::checkAppIntegrity, + sendTransfer = importFilesViewModel::sendTransfer, shouldStartByPromptingUserForFiles = true, isTransferStarted = { sendActionResult != SendActionResult.NotStarted }, snackbarHostState = snackbarHostState, @@ -211,7 +211,7 @@ private fun ImportFilesScreen( closeActivity: () -> Unit, shouldStartByPromptingUserForFiles: Boolean, integrityCheckResult: () -> AppIntegrityResult, - checkAppIntegrity: () -> Unit, + sendTransfer: () -> Unit, isTransferStarted: () -> Boolean, snackbarHostState: SnackbarHostState? = null, ) { @@ -235,7 +235,7 @@ private fun ImportFilesScreen( shouldShowEmailAddressesFields = { shouldShowEmailAddressesFields }, transferAuthorEmail = transferAuthorEmail, integrityCheckResult = integrityCheckResult, - checkAppIntegrityThenSendTransfer = checkAppIntegrity, + sendTransfer = sendTransfer, isTransferStarted = isTransferStarted, ) }, @@ -415,7 +415,7 @@ private fun SendButton( shouldShowEmailAddressesFields: () -> Boolean, transferAuthorEmail: GetSetCallbacks, integrityCheckResult: () -> AppIntegrityResult, - checkAppIntegrityThenSendTransfer: () -> Unit, + sendTransfer: () -> Unit, isTransferStarted: () -> Boolean, ) { val remainingFilesCount = filesToImportCount() @@ -440,7 +440,7 @@ private fun SendButton( showIndeterminateProgress = { integrityCheckResult() == AppIntegrityResult.Ongoing || isTransferStarted() }, enabled = { importedFiles().isNotEmpty() && !isImporting && isSenderEmailCorrect && !isTransferStarted() }, progress = progress, - onClick = { checkAppIntegrityThenSendTransfer() }, + onClick = { sendTransfer() }, ) } @@ -507,7 +507,7 @@ private fun Preview(@PreviewParameter(FileUiListPreviewParameter::class) files: closeActivity = {}, shouldStartByPromptingUserForFiles = false, integrityCheckResult = { AppIntegrityResult.Idle }, - checkAppIntegrity = {}, + sendTransfer = {}, isTransferStarted = { false }, ) } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/ImportedFilesCard.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/ImportedFilesCard.kt index d33fb85a6..342e53c9f 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/ImportedFilesCard.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/ImportedFilesCard.kt @@ -18,7 +18,6 @@ package com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.components import android.os.Parcelable -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -73,7 +72,7 @@ fun ImportedFilesCard( } } - SwissTransferCard(modifier.clickable { navigateToFileDetails() }) { + SwissTransferCard(modifier = modifier, onClick = navigateToFileDetails) { Row(verticalAlignment = Alignment.CenterVertically) { TextDotText( firstText = { diff --git a/app/src/test/java/com/infomaniak/swisstransfer/NavigationDestinationUnitTest.kt b/app/src/test/java/com/infomaniak/swisstransfer/NavigationDestinationUnitTest.kt new file mode 100644 index 000000000..4b1751062 --- /dev/null +++ b/app/src/test/java/com/infomaniak/swisstransfer/NavigationDestinationUnitTest.kt @@ -0,0 +1,36 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.infomaniak.swisstransfer + +import com.infomaniak.swisstransfer.ui.navigation.MainNavigation +import org.junit.Assert.assertNotNull +import org.junit.Test + +class NavigationDestinationUnitTest { + + /** + * Make sure that NavigationDestination names are not changed without changing the `entries` list, because of minification issue. + */ + @Test + fun check_destinations_names_are_correct() { + val destinationsNames = MainNavigation.Companion.destinationsNames + assertNotNull(destinationsNames.find { it == MainNavigation.SentDestination::class.simpleName }) + assertNotNull(destinationsNames.find { it == MainNavigation.ReceivedDestination::class.simpleName }) + assertNotNull(destinationsNames.find { it == MainNavigation.SettingsDestination::class.simpleName }) + } +}