diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferAlertDialog.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferAlertDialog.kt new file mode 100644 index 000000000..e2462c187 --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferAlertDialog.kt @@ -0,0 +1,134 @@ +/* + * 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.components + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.infomaniak.swisstransfer.R +import com.infomaniak.swisstransfer.ui.theme.Margin +import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SwissTransferAlertDialog( + modifier: Modifier = Modifier, + @StringRes titleRes: Int, + @StringRes descriptionRes: Int, + onDismiss: () -> Unit, + onConfirmation: () -> Unit, + shouldEnableConfirmButton: () -> Boolean = { true }, + content: @Composable (ColumnScope.() -> Unit)? = null, +) { + BasicAlertDialog( + onDismissRequest = onDismiss, + modifier = Modifier + .wrapContentHeight() + .fillMaxWidth(), + ) { + Card(shape = RoundedCornerShape(Margin.Medium)) { + BasicAlertDialogContent( + modifier = modifier, + titleRes = titleRes, + descriptionRes = descriptionRes, + additionalContent = content, + onDismiss = onDismiss, + onConfirmation = onConfirmation, + shouldEnableConfirmButton = shouldEnableConfirmButton, + ) + } + } +} + +@Composable +private fun BasicAlertDialogContent( + modifier: Modifier, + @StringRes titleRes: Int, + @StringRes descriptionRes: Int, + additionalContent: @Composable (ColumnScope.() -> Unit)? = null, + onDismiss: () -> Unit, + onConfirmation: () -> Unit, + shouldEnableConfirmButton: () -> Boolean = { true }, +) { + Column(modifier.padding(Margin.Large)) { + TitleAndDescription(titleRes, descriptionRes) + Spacer(Modifier.height(Margin.Large)) + additionalContent?.let { + it() + Spacer(Modifier.height(Margin.Large)) + } + ActionButtons(onDismiss, onConfirmation, shouldEnableConfirmButton) + } +} + +@Composable +private fun TitleAndDescription(titleRes: Int, descriptionRes: Int) { + Text( + text = stringResource(titleRes), + style = SwissTransferTheme.typography.bodyMedium, + color = SwissTransferTheme.colors.primaryTextColor, + ) + Spacer(Modifier.height(Margin.Large)) + Text( + text = stringResource(descriptionRes), + style = SwissTransferTheme.typography.bodyRegular, + color = SwissTransferTheme.colors.secondaryTextColor, + ) +} + +@Composable +private fun ActionButtons(onDismissRequest: () -> Unit, onConfirmation: () -> Unit, shouldEnable: () -> Boolean) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + SmallButton( + style = ButtonType.TERTIARY, + titleRes = R.string.buttonCancel, + onClick = onDismissRequest, + ) + Spacer(Modifier.width(Margin.Micro)) + SmallButton( + titleRes = R.string.buttonConfirm, + onClick = onConfirmation, + enabled = shouldEnable + ) + } +} + +@Preview +@Composable +private fun PreviewAlertDialog() { + SwissTransferTheme { + Surface { + SwissTransferAlertDialog( + titleRes = R.string.settingsOptionPassword, + descriptionRes = R.string.settingsPasswordDescription, + onDismiss = {}, + onConfirmation = {}, + ) + } + } +} diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt index 1dc9e6ada..a38b97104 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferTextField.kt @@ -20,6 +20,7 @@ package com.infomaniak.swisstransfer.ui.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.Composable @@ -30,6 +31,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation @@ -57,7 +59,9 @@ fun SwissTransferTextField( maxLineNumber: Int = if (minLineNumber > 1) Int.MAX_VALUE else 1, isPassword: Boolean = false, keyboardType: KeyboardType = KeyboardType.Text, - errorMessage: () -> String? = { null }, + imeAction: ImeAction = ImeAction.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + errorMessage: @Composable () -> String? = { null }, supportingText: String? = null, onValueChange: ((String) -> Unit)? = null, ) { @@ -72,6 +76,17 @@ fun SwissTransferTextField( unfocusedSupportingTextColor = SwissTransferTheme.colors.tertiaryTextColor, ) + @Composable + fun getSupportingText(): (@Composable () -> Unit)? { + val displayText = if (text.isEmpty()) { + supportingText + } else { + errorMessage() ?: supportingText + } + + return displayText?.let { @Composable { Text(it) } } + } + OutlinedTextField( modifier = modifier, value = text, @@ -95,9 +110,11 @@ fun SwissTransferTextField( keyboardOptions = KeyboardOptions( keyboardType = if (isPassword) KeyboardType.Password else keyboardType, autoCorrectEnabled = true, + imeAction = imeAction, ), - isError = errorMessage() != null, - supportingText = (errorMessage() ?: supportingText)?.let { { Text(it) } }, + keyboardActions = keyboardActions, + isError = errorMessage() != null && text.isNotEmpty(), + supportingText = getSupportingText(), ) } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/settings/SettingsViewModel.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/settings/SettingsViewModel.kt index bd66f0b39..732d2ce31 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/main/settings/SettingsViewModel.kt @@ -25,7 +25,6 @@ import com.infomaniak.multiplatform_swisstransfer.common.models.Theme import com.infomaniak.multiplatform_swisstransfer.common.models.ValidityPeriod import com.infomaniak.multiplatform_swisstransfer.managers.AppSettingsManager import com.infomaniak.swisstransfer.di.IoDispatcher -import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.PasswordTransferOption import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.launch @@ -54,11 +53,4 @@ class SettingsViewModel @Inject constructor( fun setEmailLanguage(emailLanguage: EmailLanguage) = viewModelScope.launch(ioDispatcher) { appSettingsManager.setEmailLanguage(emailLanguage) } - - data class AppSettingsData( - val validityPeriod: ValidityPeriod, - val downloadLimit: DownloadLimit, - val passwordOption: PasswordTransferOption, - val emailLanguage: EmailLanguage, - ) } 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 264b23edc..7375b875f 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,15 +18,16 @@ package com.infomaniak.swisstransfer.ui.screen.newtransfer import android.net.Uri +import androidx.compose.runtime.* import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.infomaniak.multiplatform_swisstransfer.common.interfaces.appSettings.AppSettings import com.infomaniak.multiplatform_swisstransfer.common.interfaces.upload.RemoteUploadFile import com.infomaniak.multiplatform_swisstransfer.common.interfaces.upload.UploadFileSession import com.infomaniak.multiplatform_swisstransfer.common.models.EmailLanguage 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.sentry.SentryLog import com.infomaniak.swisstransfer.di.IoDispatcher @@ -36,8 +37,12 @@ import com.infomaniak.swisstransfer.ui.screen.main.settings.EmailLanguageOption import com.infomaniak.swisstransfer.ui.screen.main.settings.EmailLanguageOption.Companion.toAdvancedOption import com.infomaniak.swisstransfer.ui.screen.main.settings.ValidityPeriodOption import com.infomaniak.swisstransfer.ui.screen.main.settings.ValidityPeriodOption.Companion.toAdvancedOption +import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingOption +import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.AdvancedOptionsCallbacks +import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.AdvancedOptionsState import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.PasswordTransferOption import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.components.TransferType +import com.infomaniak.swisstransfer.ui.utils.GetSetCallbacks import com.infomaniak.swisstransfer.workers.UploadWorker import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher @@ -48,6 +53,7 @@ import javax.inject.Inject @HiltViewModel class ImportFilesViewModel @Inject constructor( + private val appSettingsManager: AppSettingsManager, private val savedStateHandle: SavedStateHandle, private val importationFilesManager: ImportationFilesManager, private val uploadManager: UploadManager, @@ -104,10 +110,6 @@ class ImportFilesViewModel @Inject constructor( } } - fun selectTransferType(type: TransferType) { - _selectedTransferType.value = type - } - fun sendTransfer() { viewModelScope.launch(ioDispatcher) { runCatching { @@ -146,53 +148,109 @@ class ImportFilesViewModel @Inject constructor( } //region Transfer Type - private val _selectedTransferType = MutableStateFlow(TransferType.LINK) - val selectedTransferType: StateFlow = _selectedTransferType + val selectedTransferType = savedStateHandle.getStateFlow(SELECTED_TRANSFER_TYPE, TransferType.LINK) - sealed class SendActionResult { - data class Success(val totalSize: Long) : SendActionResult() - data object Failure : SendActionResult() + fun selectTransferType(type: TransferType) { + savedStateHandle[SELECTED_TRANSFER_TYPE] = type } //endregion //region Transfer Advanced Options - private val _selectedValidityPeriodOption = MutableStateFlow(null) - val selectedValidityPeriodOption: StateFlow = _selectedValidityPeriodOption.asStateFlow() + val selectedValidityPeriodOption = savedStateHandle.getStateFlow( + key = SELECTED_VALIDITY_PERIOD_KEY, + initialValue = ValidityPeriodOption.THIRTY + ) - private val _selectedDownloadLimitOption = MutableStateFlow(null) - val selectedDownloadLimitOption: StateFlow = _selectedDownloadLimitOption.asStateFlow() + val selectedDownloadLimitOption = savedStateHandle.getStateFlow( + key = SELECTED_DOWNLOAD_LIMIT_KEY, + initialValue = DownloadLimitOption.TWO_HUNDRED_FIFTY, + ) - private val _selectedPasswordOption = MutableStateFlow(PasswordTransferOption.NONE) - val selectedPasswordOption: StateFlow = _selectedPasswordOption.asStateFlow() + val selectedPasswordOption = savedStateHandle.getStateFlow( + key = SELECTED_PASSWORD_OPTION_KEY, + initialValue = PasswordTransferOption.NONE, + ) - private val _selectedLanguageOption = MutableStateFlow(null) - val selectedLanguageOption: StateFlow = _selectedLanguageOption.asStateFlow() + val selectedLanguageOption = savedStateHandle.getStateFlow( + key = SELECTED_LANGUAGE_KEY, + initialValue = EmailLanguageOption.FRENCH, + ) - fun selectTransferValidityPeriod(validityPeriodOption: ValidityPeriodOption) { - _selectedValidityPeriodOption.value = validityPeriodOption + private fun selectTransferValidityPeriod(validityPeriodOption: ValidityPeriodOption) { + savedStateHandle[SELECTED_VALIDITY_PERIOD_KEY] = validityPeriodOption } - fun selectTransferDownloadLimit(downloadLimit: DownloadLimitOption) { - _selectedDownloadLimitOption.value = downloadLimit + private fun selectTransferDownloadLimit(downloadLimit: DownloadLimitOption) { + savedStateHandle[SELECTED_DOWNLOAD_LIMIT_KEY] = downloadLimit } - fun selectTransferPasswordOption(passwordOption: PasswordTransferOption) { - _selectedPasswordOption.value = passwordOption + private fun selectTransferPasswordOption(passwordOption: PasswordTransferOption) { + savedStateHandle[SELECTED_PASSWORD_OPTION_KEY] = passwordOption + } + + private fun selectTransferLanguage(language: EmailLanguageOption) { + savedStateHandle[SELECTED_LANGUAGE_KEY] = language + } + + fun initTransferAdvancedOptionsValues() { + viewModelScope.launch(ioDispatcher) { + appSettingsManager.getAppSettings()?.let { + selectTransferValidityPeriod(it.validityPeriod.toAdvancedOption()) + selectTransferDownloadLimit(it.downloadLimit.toAdvancedOption()) + selectTransferLanguage(it.emailLanguage.toAdvancedOption()) + } ?: run { + SentryLog.e(TAG, "AppSettings is null in ImportFilesScreen, it should not happened") + selectTransferValidityPeriod(ValidityPeriodOption.THIRTY) + selectTransferDownloadLimit(DownloadLimitOption.TWO_HUNDRED_FIFTY) + selectTransferLanguage(EmailLanguageOption.FRENCH) + } + } } - fun selectTransferLanguage(language: EmailLanguageOption) { - _selectedLanguageOption.value = language + fun getTransferAdvancedOptionsCallbacks(advancedOptionsStates: () -> List): AdvancedOptionsCallbacks { + return AdvancedOptionsCallbacks( + advancedOptionsStates = advancedOptionsStates, + onAdvancedOptionsValueSelected = ::onAdvancedOptionsValueSelected, + password = GetSetCallbacks( + get = { transferPassword }, + set = { transferPassword = it }, + ), + isPasswordValid = { isPasswordValid }, + ) } - fun initTransferAdvancedOptionsValues(safeAppSettings: AppSettings) { - selectTransferValidityPeriod(safeAppSettings.validityPeriod.toAdvancedOption()) - selectTransferDownloadLimit(safeAppSettings.downloadLimit.toAdvancedOption()) - selectTransferLanguage(safeAppSettings.emailLanguage.toAdvancedOption()) + private fun onAdvancedOptionsValueSelected(option: SettingOption) { + when (option) { + is ValidityPeriodOption -> selectTransferValidityPeriod(option) + is DownloadLimitOption -> selectTransferDownloadLimit(option) + is PasswordTransferOption -> selectTransferPasswordOption(option) + is EmailLanguageOption -> selectTransferLanguage(option) + } } //endregion + //region Password + private var transferPassword by mutableStateOf("") + + private val isPasswordValid by derivedStateOf { transferPassword.length in PASSWORD_MIN_LENGTH..PASSWORD_MAX_LENGTH } + //endregion + + sealed class SendActionResult { + data class Success(val totalSize: Long) : SendActionResult() + data object Failure : SendActionResult() + } + companion object { private val TAG = ImportFilesViewModel::class.java.simpleName + + private const val PASSWORD_MIN_LENGTH = 6 + private const val PASSWORD_MAX_LENGTH = 25 + private const val IS_VIEW_MODEL_RESTORED_KEY = "IS_VIEW_MODEL_RESTORED_KEY" + private const val SELECTED_TRANSFER_TYPE = "SELECTED_TRANSFER_TYPE" + private const val SELECTED_VALIDITY_PERIOD_KEY = "SELECTED_VALIDITY_PERIOD_KEY" + private const val SELECTED_DOWNLOAD_LIMIT_KEY = "SELECTED_DOWNLOAD_LIMIT_KEY" + private const val SELECTED_PASSWORD_OPTION_KEY = "SELECTED_PASSWORD_OPTION_KEY" + private const val SELECTED_LANGUAGE_KEY = "SELECTED_TRANSFER_LANGUAGE_KEY" } } 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 0030e5444..58c80a789 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 @@ -41,7 +41,6 @@ import com.infomaniak.swisstransfer.ui.components.* import com.infomaniak.swisstransfer.ui.previewparameter.FileUiListPreviewParameter import com.infomaniak.swisstransfer.ui.screen.main.settings.DownloadLimitOption import com.infomaniak.swisstransfer.ui.screen.main.settings.EmailLanguageOption -import com.infomaniak.swisstransfer.ui.screen.main.settings.SettingsViewModel import com.infomaniak.swisstransfer.ui.screen.main.settings.ValidityPeriodOption import com.infomaniak.swisstransfer.ui.screen.main.settings.components.SettingOption import com.infomaniak.swisstransfer.ui.screen.newtransfer.ImportFilesViewModel @@ -58,75 +57,64 @@ private const val TOTAL_FILE_SIZE: Long = 50_000_000_000L @Composable fun ImportFilesScreen( importFilesViewModel: ImportFilesViewModel = hiltViewModel(), - settingsViewModel: SettingsViewModel = hiltViewModel(), closeActivity: () -> Unit, navigateToUploadProgress: (transferType: TransferType, totalSize: Long) -> Unit, ) { val files by importFilesViewModel.importedFilesDebounced.collectAsStateWithLifecycle() val filesToImportCount by importFilesViewModel.filesToImportCount.collectAsStateWithLifecycle() val currentSessionFilesCount by importFilesViewModel.currentSessionFilesCount.collectAsStateWithLifecycle() + val selectedTransferType by importFilesViewModel.selectedTransferType.collectAsStateWithLifecycle() + val validityPeriodState by importFilesViewModel.selectedValidityPeriodOption.collectAsStateWithLifecycle() val downloadLimitState by importFilesViewModel.selectedDownloadLimitOption.collectAsStateWithLifecycle() val passwordOptionState by importFilesViewModel.selectedPasswordOption.collectAsStateWithLifecycle() val emailLanguageState by importFilesViewModel.selectedLanguageOption.collectAsStateWithLifecycle() - val appSettings by settingsViewModel.appSettingsFlow.collectAsStateWithLifecycle(null) + val sendActionResult by importFilesViewModel.sendActionResult.collectAsStateWithLifecycle() HandleSendActionResult({ sendActionResult }, { selectedTransferType }, navigateToUploadProgress) - fun onAdvancedOptionsValueSelected(option: SettingOption) { - when (option) { - is ValidityPeriodOption -> importFilesViewModel.selectTransferValidityPeriod(option) - is DownloadLimitOption -> importFilesViewModel.selectTransferDownloadLimit(option) - is PasswordTransferOption -> importFilesViewModel.selectTransferPasswordOption(option) - is EmailLanguageOption -> importFilesViewModel.selectTransferLanguage(option) - } - } - - appSettings?.let { safeAppSettings -> - importFilesViewModel.initTransferAdvancedOptionsValues(safeAppSettings) - - val advancedOptionsCallbacks = AdvancedOptionsCallbacks( - advancedOptionsStates = { - listOf( - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.VALIDITY_DURATION, - settingState = { validityPeriodState }, - ), - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.DOWNLOAD_NUMBER_LIMIT, - settingState = { downloadLimitState }, - ), - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.PASSWORD, - settingState = { passwordOptionState }, - ), - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.LANGUAGE, - settingState = { emailLanguageState }, - ), - ) - }, - onAdvancedOptionsValueSelected = ::onAdvancedOptionsValueSelected, - ) + LaunchedEffect(Unit) { importFilesViewModel.initTransferAdvancedOptionsValues() } + + val advancedOptionsCallbacks = importFilesViewModel.getTransferAdvancedOptionsCallbacks( + advancedOptionsStates = { + listOf( + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.VALIDITY_DURATION, + settingState = { validityPeriodState }, + ), + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.DOWNLOAD_NUMBER_LIMIT, + settingState = { downloadLimitState }, + ), + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.PASSWORD, + settingState = { passwordOptionState }, + ), + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.LANGUAGE, + settingState = { emailLanguageState }, + ), + ) + }, + ) - ImportFilesScreen( - files = { files }, - filesToImportCount = { filesToImportCount }, - currentSessionFilesCount = { currentSessionFilesCount }, - selectedTransferType = GetSetCallbacks( - get = { selectedTransferType }, - set = importFilesViewModel::selectTransferType, - ), - advancedOptionsCallbacks = advancedOptionsCallbacks, - removeFileByUid = importFilesViewModel::removeFileByUid, - addFiles = importFilesViewModel::importFiles, - closeActivity = closeActivity, - sendTransfer = importFilesViewModel::sendTransfer, - initialShowUploadSourceChoiceBottomSheet = true, - ) - } + ImportFilesScreen( + files = { files }, + filesToImportCount = { filesToImportCount }, + currentSessionFilesCount = { currentSessionFilesCount }, + selectedTransferType = GetSetCallbacks( + get = { selectedTransferType }, + set = importFilesViewModel::selectTransferType, + ), + advancedOptionsCallbacks = advancedOptionsCallbacks, + removeFileByUid = importFilesViewModel::removeFileByUid, + addFiles = importFilesViewModel::importFiles, + closeActivity = closeActivity, + sendTransfer = importFilesViewModel::sendTransfer, + initialShowUploadSourceChoiceBottomSheet = true, + ) } @Composable @@ -159,7 +147,7 @@ private fun ImportFilesScreen( ) { val context = LocalContext.current var showUploadSourceChoiceBottomSheet by rememberSaveable { mutableStateOf(initialShowUploadSourceChoiceBottomSheet) } - var showOptionBottomSheet by rememberSaveable { mutableStateOf(null) } + var showAdvancedOption by rememberSaveable { mutableStateOf(null) } val importedFiles = files() val humanReadableSize = remember(importedFiles) { @@ -174,8 +162,8 @@ private fun ImportFilesScreen( addFiles(uris) } - fun closeOptionBottomSheet() { - showOptionBottomSheet = null + fun closeAdvancedOption() { + showAdvancedOption = null } BottomStickyButtonScaffold( @@ -209,37 +197,11 @@ private fun ImportFilesScreen( ImportFilesTitle(Modifier.padding(vertical = Margin.Medium), titleRes = R.string.advancedSettingsTitle) TransferAdvancedSettings( advancedSettingsItemsStates = advancedOptionsCallbacks.advancedOptionsStates, - onClick = { selectedOption -> showOptionBottomSheet = selectedOption }, + onClick = { selectedOption -> showAdvancedOption = selectedOption }, ) } - ValidityPeriodBottomSheet( - isBottomSheetVisible = { showOptionBottomSheet == TransferAdvancedSettingType.VALIDITY_DURATION }, - onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, - closeBottomSheet = ::closeOptionBottomSheet, - initialValue = advancedOptionsCallbacks.advancedOptionsStates()[0].settingState(), - ) - - DownloadLimitBottomSheet( - isBottomSheetVisible = { showOptionBottomSheet == TransferAdvancedSettingType.DOWNLOAD_NUMBER_LIMIT }, - onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, - closeBottomSheet = ::closeOptionBottomSheet, - initialValue = advancedOptionsCallbacks.advancedOptionsStates()[1].settingState(), - ) - - PasswordOptionBottomSheet( - isBottomSheetVisible = { showOptionBottomSheet == TransferAdvancedSettingType.PASSWORD }, - onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, - closeBottomSheet = ::closeOptionBottomSheet, - initialValue = advancedOptionsCallbacks.advancedOptionsStates()[2].settingState(), - ) - - EmailLanguageBottomSheet( - isBottomSheetVisible = { showOptionBottomSheet == TransferAdvancedSettingType.LANGUAGE }, - onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, - closeBottomSheet = ::closeOptionBottomSheet, - initialValue = advancedOptionsCallbacks.advancedOptionsStates()[3].settingState(), - ) + AdvancedOptions({ showAdvancedOption }, advancedOptionsCallbacks, ::closeAdvancedOption) UploadSourceChoiceBottomSheet( isVisible = { showUploadSourceChoiceBottomSheet }, @@ -284,6 +246,41 @@ private fun ColumnScope.EmailAddressesTextFields(selectedTransferType: () -> Tra } } +@Composable +private fun AdvancedOptions( + selectedTransferType: () -> TransferAdvancedSettingType?, + advancedOptionsCallbacks: AdvancedOptionsCallbacks, + closeAdvancedOption: () -> Unit, +) { + when (selectedTransferType()) { + TransferAdvancedSettingType.VALIDITY_DURATION -> ValidityPeriodBottomSheet( + onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, + closeBottomSheet = closeAdvancedOption, + initialValue = advancedOptionsCallbacks.advancedOptionsStates()[0].settingState(), + ) + TransferAdvancedSettingType.DOWNLOAD_NUMBER_LIMIT -> DownloadLimitBottomSheet( + onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, + closeBottomSheet = closeAdvancedOption, + initialValue = advancedOptionsCallbacks.advancedOptionsStates()[1].settingState(), + ) + TransferAdvancedSettingType.PASSWORD -> PasswordOptionAlertDialog( + password = advancedOptionsCallbacks.password, + onConfirmation = { passwordOption -> + advancedOptionsCallbacks.onAdvancedOptionsValueSelected(passwordOption) + closeAdvancedOption() + }, + closeAlertDialog = closeAdvancedOption, + isPasswordValid = advancedOptionsCallbacks.isPasswordValid, + ) + TransferAdvancedSettingType.LANGUAGE -> EmailLanguageBottomSheet( + onOptionClicked = { advancedOptionsCallbacks.onAdvancedOptionsValueSelected(it) }, + closeBottomSheet = closeAdvancedOption, + initialValue = advancedOptionsCallbacks.advancedOptionsStates()[3].settingState(), + ) + null -> Unit + } +} + @Composable private fun SendButton( filesToImportCount: () -> Int, @@ -326,6 +323,8 @@ private fun ImportFilesTitle(modifier: Modifier = Modifier, @StringRes titleRes: data class AdvancedOptionsCallbacks( val advancedOptionsStates: () -> List, val onAdvancedOptionsValueSelected: (SettingOption) -> Unit, + val password: GetSetCallbacks, + val isPasswordValid: () -> Boolean, ) data class AdvancedOptionsState( @@ -345,35 +344,39 @@ enum class PasswordTransferOption( @PreviewAllWindows @Composable private fun ImportFilesScreenPreview(@PreviewParameter(FileUiListPreviewParameter::class) files: List) { + val advancedOptionsCallbacks = AdvancedOptionsCallbacks( + advancedOptionsStates = { + listOf( + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.VALIDITY_DURATION, + settingState = { ValidityPeriodOption.THIRTY }, + ), + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.DOWNLOAD_NUMBER_LIMIT, + settingState = { DownloadLimitOption.TWO_HUNDRED_FIFTY }, + ), + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.PASSWORD, + settingState = { PasswordTransferOption.NONE }, + ), + AdvancedOptionsState( + advancedSettingType = TransferAdvancedSettingType.LANGUAGE, + settingState = { EmailLanguageOption.FRENCH }, + ), + ) + }, + onAdvancedOptionsValueSelected = {}, + password = GetSetCallbacks(get = { "password" }, set = {}), + isPasswordValid = { true }, + ) + SwissTransferTheme { ImportFilesScreen( files = { files }, filesToImportCount = { 0 }, currentSessionFilesCount = { 0 }, selectedTransferType = GetSetCallbacks(get = { TransferType.QR_CODE }, set = {}), - advancedOptionsCallbacks = AdvancedOptionsCallbacks( - advancedOptionsStates = { - listOf( - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.VALIDITY_DURATION, - settingState = { ValidityPeriodOption.THIRTY }, - ), - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.DOWNLOAD_NUMBER_LIMIT, - settingState = { DownloadLimitOption.TWO_HUNDRED_FIFTY }, - ), - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.PASSWORD, - settingState = { PasswordTransferOption.NONE }, - ), - AdvancedOptionsState( - advancedSettingType = TransferAdvancedSettingType.LANGUAGE, - settingState = { EmailLanguageOption.FRENCH }, - ), - ) - }, - onAdvancedOptionsValueSelected = {}, - ), + advancedOptionsCallbacks = advancedOptionsCallbacks, removeFileByUid = {}, addFiles = {}, closeActivity = {}, diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/TransferOptionsBottomSheets.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/TransferOptionsBottomSheets.kt index c87c85628..158cd60c6 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/TransferOptionsBottomSheets.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/TransferOptionsBottomSheets.kt @@ -36,7 +36,6 @@ import com.infomaniak.swisstransfer.ui.utils.PreviewAllWindows @Composable private fun TransferOptionBottomSheetScaffold( - isBottomSheetVisible: () -> Boolean, onOptionClicked: (SettingOption) -> Unit, closeBottomSheet: () -> Unit, initialValue: SettingOption?, @@ -53,35 +52,31 @@ private fun TransferOptionBottomSheetScaffold( else -> 0 } - if (isBottomSheetVisible()) { - SwissTransferBottomSheet( - onDismissRequest = closeBottomSheet, - titleRes = titleRes, - content = { - SingleSelectOptions( - items = optionEntries, - selectedItem = { selectedPosition }, - setSelectedItem = { position -> - val selectedValue = optionEntries[position] - selectedItem = selectedValue - onOptionClicked(selectedValue) - closeBottomSheet() - }, - ) - }, - ) - } + SwissTransferBottomSheet( + onDismissRequest = closeBottomSheet, + titleRes = titleRes, + content = { + SingleSelectOptions( + items = optionEntries, + selectedItem = { selectedPosition }, + setSelectedItem = { position -> + val selectedValue = optionEntries[position] + selectedItem = selectedValue + onOptionClicked(selectedValue) + closeBottomSheet() + }, + ) + }, + ) } @Composable fun ValidityPeriodBottomSheet( - isBottomSheetVisible: () -> Boolean, onOptionClicked: (ValidityPeriodOption) -> Unit, closeBottomSheet: () -> Unit, initialValue: SettingOption?, ) { TransferOptionBottomSheetScaffold( - isBottomSheetVisible = isBottomSheetVisible, closeBottomSheet = closeBottomSheet, initialValue = initialValue, titleRes = R.string.settingsOptionValidityPeriod, @@ -92,13 +87,11 @@ fun ValidityPeriodBottomSheet( @Composable fun DownloadLimitBottomSheet( - isBottomSheetVisible: () -> Boolean, onOptionClicked: (DownloadLimitOption) -> Unit, closeBottomSheet: () -> Unit, initialValue: SettingOption?, ) { TransferOptionBottomSheetScaffold( - isBottomSheetVisible = isBottomSheetVisible, closeBottomSheet = closeBottomSheet, initialValue = initialValue, titleRes = R.string.settingsOptionDownloadLimit, @@ -107,32 +100,13 @@ fun DownloadLimitBottomSheet( ) } -@Composable -fun PasswordOptionBottomSheet( - isBottomSheetVisible: () -> Boolean, - onOptionClicked: (PasswordTransferOption) -> Unit, - closeBottomSheet: () -> Unit, - initialValue: SettingOption?, -) { - TransferOptionBottomSheetScaffold( - isBottomSheetVisible = isBottomSheetVisible, - closeBottomSheet = closeBottomSheet, - initialValue = initialValue, - titleRes = R.string.settingsOptionPassword, - optionEntries = PasswordTransferOption.entries, - onOptionClicked = { onOptionClicked(it as PasswordTransferOption) }, - ) -} - @Composable fun EmailLanguageBottomSheet( - isBottomSheetVisible: () -> Boolean, onOptionClicked: (EmailLanguageOption) -> Unit, closeBottomSheet: () -> Unit, initialValue: SettingOption?, ) { TransferOptionBottomSheetScaffold( - isBottomSheetVisible = isBottomSheetVisible, closeBottomSheet = closeBottomSheet, initialValue = initialValue, titleRes = R.string.settingsOptionEmailLanguage, @@ -147,7 +121,6 @@ private fun ValidityPeriodOptionBottomSheetPreview() { SwissTransferTheme { Surface { ValidityPeriodBottomSheet( - isBottomSheetVisible = { true }, onOptionClicked = {}, closeBottomSheet = {}, initialValue = ValidityPeriodOption.SEVEN, diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/PasswordOptionAlertDialog.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/PasswordOptionAlertDialog.kt new file mode 100644 index 000000000..798f47356 --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/PasswordOptionAlertDialog.kt @@ -0,0 +1,130 @@ +/* + * 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.importfiles.components + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.tooling.preview.Preview +import com.infomaniak.swisstransfer.R +import com.infomaniak.swisstransfer.ui.components.SwissTransferAlertDialog +import com.infomaniak.swisstransfer.ui.components.SwissTransferTextField +import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.PasswordTransferOption +import com.infomaniak.swisstransfer.ui.screen.newtransfer.upload.components.WeightOneSpacer +import com.infomaniak.swisstransfer.ui.theme.Margin +import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme +import com.infomaniak.swisstransfer.ui.utils.GetSetCallbacks + +@Composable +fun PasswordOptionAlertDialog( + password: GetSetCallbacks, + closeAlertDialog: () -> Unit, + onConfirmation: (PasswordTransferOption) -> Unit, + isPasswordValid: () -> Boolean, +) { + + var isPasswordActivated by rememberSaveable { mutableStateOf(password.get().isNotEmpty()) } + var lastValidPassword by remember { mutableStateOf("") } + + fun onDismiss() { + isPasswordActivated = lastValidPassword.isNotEmpty() + password.set(lastValidPassword) + closeAlertDialog() + } + + fun onConfirmButtonClicked() { + val passwordOption = if (isPasswordActivated) { + PasswordTransferOption.ACTIVATED + } else { + password.set("") + PasswordTransferOption.NONE + } + + lastValidPassword = password.get() + onConfirmation(passwordOption) + } + + SwissTransferAlertDialog( + titleRes = R.string.settingsOptionPassword, + descriptionRes = R.string.settingsPasswordDescription, + onDismiss = ::onDismiss, + onConfirmation = ::onConfirmButtonClicked, + shouldEnableConfirmButton = { if (isPasswordActivated) isPasswordValid() else true }, + ) { + ActivatePasswordSwitch(isChecked = isPasswordActivated, onCheckedChange = { isPasswordActivated = it }) + AnimatedPasswordInput(isPasswordActivated, password, isPasswordValid) + } +} + +@Composable +private fun ActivatePasswordSwitch(isChecked: Boolean, onCheckedChange: (Boolean) -> Unit) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = stringResource(R.string.settingsPasswordToggleDescription), + style = SwissTransferTheme.typography.bodyMedium, + color = SwissTransferTheme.colors.primaryTextColor, + ) + WeightOneSpacer(minWidth = Margin.Medium) + Switch(isChecked, onCheckedChange) + } +} + +@Composable +private fun ColumnScope.AnimatedPasswordInput( + isChecked: Boolean, + password: GetSetCallbacks, + isPasswordValid: () -> Boolean +) { + AnimatedVisibility(isChecked) { + Spacer(Modifier.height(Margin.Mini)) + SwissTransferTextField( + label = stringResource(R.string.settingsOptionPassword), + isPassword = true, + initialValue = password.get(), + imeAction = ImeAction.Done, + errorMessage = { if (isPasswordValid()) null else stringResource(R.string.errorTransferPasswordLength) }, + onValueChange = { password.set(it) }, + ) + } +} + +@Preview +@Composable +fun Preview() { + SwissTransferTheme { + Surface { + PasswordOptionAlertDialog( + password = GetSetCallbacks(get = { "pass" }, set = {}), + closeAlertDialog = {}, + onConfirmation = {}, + isPasswordValid = { false }, + ) + } + } +} diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/WeightOneSpacer.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/WeightOneSpacer.kt index a3ffffc86..b61046b71 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/WeightOneSpacer.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/upload/components/WeightOneSpacer.kt @@ -17,9 +17,7 @@ */ package com.infomaniak.swisstransfer.ui.screen.newtransfer.upload.components -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp @@ -29,3 +27,9 @@ fun ColumnScope.WeightOneSpacer(minHeight: Dp) { Spacer(modifier = Modifier.height(minHeight)) Spacer(modifier = Modifier.weight(1f)) } + +@Composable +fun RowScope.WeightOneSpacer(minWidth: Dp) { + Spacer(modifier = Modifier.width(minWidth)) + Spacer(modifier = Modifier.weight(1f)) +} diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 462a84421..07953fbaf 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -18,6 +18,8 @@ Erweiterte Einstellungen Dateien hinzufügen + Abbrechen + Bestätigen Link kopieren Download Auswahl herunterladen @@ -30,7 +32,9 @@ Datei entfernen Passwort anzeigen Neuer Transfer - Heruntergeladene Übertragung: %d/%d + Heruntergeladene Übertragung : %d/%d + Das Passwort muss zwischen 6 und 25 Zeichen lang sein + Abgelaufen am %s Verfällt in %d Tagen %d Datei @@ -80,6 +84,8 @@ Hell System Dauer der Gültigkeit + Deine Empfänger müssen das Passwort eingeben, um die Dateien herunterzuladen. + Schutz aktivieren Sentry ist ein Tool, das ausschliesslich von Infomaniak gehostet und verwaltet wird, um die Stabilität der Anwendung in Echtzeit zu überwachen und unseren Entwicklern automatisch alle technischen Fehler zu melden.\n\nAnhand dieser Daten kann unser Team die Anwendung schnell korrigieren und optimieren, was zu einem besseren Nutzererlebnis für Sie führt. Wähle ein Thema aus Einstellungen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7dc09751b..8840028d5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -18,6 +18,8 @@ Ajustes avanzados Añadir archivos + Cancelar + Confirme Copiar enlace Descargar Descargar la selección @@ -30,8 +32,9 @@ Eliminar archivo Mostrar contraseña Nueva transferencia - Transferencia descargada: %d/%d + Transferencia descargada : %d/%d La contraseña debe tener entre 6 y 25 caracteres + Expirado el %s Caduca en %d días %d archivo @@ -81,6 +84,8 @@ Luz Sistema Periodo de validez + Los destinatarios tendrán que introducir la contraseña para descargar los archivos. + Activar la protección Sentry es una herramienta alojada y gestionada en exclusiva por Infomaniak para supervisar la estabilidad de la aplicación en tiempo real e informar automáticamente de cualquier error técnico a nuestros desarrolladores.\n\nEstos datos permiten a nuestro equipo corregir y optimizar rápidamente la aplicación, lo que se traduce en una mejor experiencia de usuario para usted. Seleccione un tema Parámetros diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e3c924940..5c505fc63 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -18,6 +18,8 @@ Paramètres avancés Ajouter des fichiers + Annuler + Confirmer Copier le lien Télécharger Télécharger la sélection @@ -31,6 +33,8 @@ Afficher le mot de passe Nouveau transfert Transfert téléchargé : %d/%d\u0020 + Le mot de passe doit comporter entre 6 et 25 caractères + Expiré le %s Expire dans %d jours %d fichier @@ -81,6 +85,8 @@ Clair Système Durée de validité + Tes destinataires devront saisir le mot de passe pour télécharger les fichiers. + Activer la protection Sentry est un outil hébergé et géré exclusivement par Infomaniak pour surveiller en temps réel la stabilité de l’application et signaler automatiquement d’éventuelles erreurs techniques à nos développeurs.\n\nCes données permettent à notre équipe de rapidement corriger et optimiser l’application, ce qui se traduit par une meilleure expérience d’utilisation pour vous. Sélectionne un thème Paramètres diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ff79304c9..659893cb6 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -18,6 +18,8 @@ Impostazioni avanzate Aggiunta di file + Annulla + Conferma Copia link Scaricare Scarica la selezione @@ -30,7 +32,9 @@ Rimuovi il file Mostra password Nuovo trasferimento - Trasferimento scaricato: %d/%d + Trasferimento scaricato : %d/%d + La password deve essere compresa tra 6 e 24 caratteri + Scaduto il %s Scade tra %d giorni %d file @@ -80,6 +84,8 @@ Chiaro Sistema Periodo di validità + I destinatari dovranno inserire la password per scaricare i file. + Attivare la protezione Sentry è uno strumento ospitato e gestito esclusivamente da Infomaniak per monitorare la stabilità dell’applicazione in tempo reale e segnalare automaticamente eventuali errori tecnici ai nostri sviluppatori.\n\nQuesti dati consentono al nostro team di correggere e ottimizzare rapidamente l’applicazione, migliorando l’esperienza dell’utente. Seleziona un tema Parametri diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52cf92544..4b878b5e1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,6 +23,8 @@ Advanced settings Add files + Cancel + Confirm Copy link Download Download selection @@ -36,6 +38,8 @@ Show password New transfer Downloaded transfer: %d/%d\u0020 + The password must be between 6 and 25 characters + Expired on %s Expires in %d days %d file @@ -85,6 +89,8 @@ Light System Validity period + Your recipients will need to enter the password to download the files. + Activate protection Sentry is a tool hosted and managed exclusively by Infomaniak to monitor the stability of the application in real time and automatically report any technical errors to our developers.\n\nThis data enables our team to quickly correct and optimise the application, resulting in a better user experience for you. Select a theme Settings diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5ac4e109d..2e95b54ec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ qrose = "1.0.1" recaptcha = "18.6.1" sentry = "4.12.0" serialization = "1.7.1" -swisstransfer = "0.7.0" +swisstransfer = "0.7.1" workmanager = "2.9.1" [libraries]