From 54ae2a5f3b46a2633bbc5268517e3735d9a7a556 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 6 Mar 2026 16:34:25 +0100 Subject: [PATCH 1/3] refactor: Use spacedBy instead of manually adding spacer everywhere --- .../newtransfer/pickfiles/PickFilesScreen.kt | 90 +++++++++++-------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt index dd84b8a1c..6e14e6e5c 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt @@ -25,18 +25,22 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState @@ -52,7 +56,10 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState @@ -61,6 +68,7 @@ import com.infomaniak.core.common.mapSync import com.infomaniak.core.ui.compose.bottomstickybuttonscaffolds.BottomStickyButtonScaffold import com.infomaniak.core.ui.compose.margin.Margin import com.infomaniak.core.ui.compose.preview.PreviewAllWindows +import com.infomaniak.core.ui.compose.preview.previewparameter.usersPreviewData import com.infomaniak.multiplatform_swisstransfer.common.interfaces.ui.FileUi import com.infomaniak.multiplatform_swisstransfer.common.matomo.MatomoScreen import com.infomaniak.swisstransfer.R @@ -287,7 +295,7 @@ private fun FilesToImport( } @Composable -private fun ColumnScope.ImportTextFields( +private fun ImportTextFields( horizontalPaddingModifier: Modifier, transferTitleState: MutableState, emailTextFieldCallbacks: EmailTextFieldCallbacks, @@ -295,39 +303,46 @@ private fun ColumnScope.ImportTextFields( shouldShowEmailAddressesFields: () -> Boolean, ) { val modifier = horizontalPaddingModifier.fillMaxWidth() - if (LocalUser.current.isApiV2()) { + + val textFieldSpacing = Margin.Medium + Column(verticalArrangement = Arrangement.spacedBy(textFieldSpacing)) { + if (LocalUser.current.isApiV2()) { + SwissTransferTextField( + modifier = modifier, + label = stringResource(R.string.transferTitlePlaceholder), + isRequired = false, + maxLineNumber = 1, + onValueChange = { transferTitleState.value = it }, + ) + } + + EmailAddressesTextFields(modifier, emailTextFieldCallbacks, shouldShowEmailAddressesFields, textFieldSpacing) + SwissTransferTextField( modifier = modifier, - label = stringResource(R.string.transferTitlePlaceholder), + label = stringResource(R.string.transferMessagePlaceholder), isRequired = false, - maxLineNumber = 1, - onValueChange = { transferTitleState.value = it } + minLineNumber = 3, + capitalization = KeyboardCapitalization.Sentences, + onValueChange = transferMessageCallbacks.set, ) } - EmailAddressesTextFields(modifier, emailTextFieldCallbacks, shouldShowEmailAddressesFields) - SwissTransferTextField( - modifier = modifier, - label = stringResource(R.string.transferMessagePlaceholder), - isRequired = false, - minLineNumber = 3, - capitalization = KeyboardCapitalization.Sentences, - onValueChange = transferMessageCallbacks.set, - ) } @Composable -private fun ColumnScope.EmailAddressesTextFields( +private fun EmailAddressesTextFields( modifier: Modifier, emailTextFieldCallbacks: EmailTextFieldCallbacks, shouldShowEmailAddressesFields: () -> Boolean, + textFieldSpacing: Dp, ) = with(emailTextFieldCallbacks) { - AnimatedVisibility(visible = shouldShowEmailAddressesFields()) { - Column { + AnimatedVisibility(visible = shouldShowEmailAddressesFields(), modifier = modifier) { + Column(verticalArrangement = Arrangement.spacedBy(textFieldSpacing)) { val isAuthorError = checkEmailError(isAuthor = true) val isRecipientError = checkEmailError(isAuthor = false) SwissTransferTextField( - modifier = modifier, + modifier = Modifier.fillMaxWidth(), label = stringResource(R.string.transferSenderAddressPlaceholder), initialValue = transferAuthorEmail.get(), keyboardType = KeyboardType.Email, @@ -338,9 +353,9 @@ private fun ColumnScope.EmailAddressesTextFields( supportingText = getEmailError(isAuthorError), onValueChange = transferAuthorEmail.set, ) - Spacer(Modifier.height(Margin.Medium)) + EmailAddressTextField( - modifier = modifier, + modifier = Modifier.fillMaxWidth(), label = stringResource(R.string.transferRecipientAddressPlaceholder), initialValue = recipientEmail.get(), validatedRecipientsEmails = validatedRecipientsEmails, @@ -348,7 +363,6 @@ private fun ColumnScope.EmailAddressesTextFields( isError = isRecipientError, supportingText = getEmailError(isRecipientError), ) - Spacer(Modifier.height(Margin.Medium)) } } } @@ -524,20 +538,22 @@ private fun Preview(@PreviewParameter(FileUiListPreviewParameter::class) files: validatedRecipientsEmails = GetSetCallbacks(get = { setOf("test.test@ik.me") }, set = {}), ) - SwissTransferTheme { - PickFilesScreen( - files = { files }, - canSendStatus = { CanSendStatus.Yes }, - transferTitleState = remember { mutableStateOf("") }, - emailTextFieldCallbacks = emailTextFieldCallbacks, - transferMessageCallbacks = GetSetCallbacks(get = { "" }, set = {}), - selectedTransferType = GetSetCallbacks(get = { TransferTypeUi.Mail }, set = {}), - transferOptionsCallbacks = transferOptionsCallbacks, - pickFiles = {}, - exitNewTransfer = {}, - onSendButtonClick = {}, - isAwaitingSend = { true }, - navigateToFilesDetails = {}, - ) + CompositionLocalProvider(LocalUser provides usersPreviewData.first()) { + SwissTransferTheme { + PickFilesScreen( + files = { files }, + canSendStatus = { CanSendStatus.Yes }, + transferTitleState = remember { mutableStateOf("") }, + emailTextFieldCallbacks = emailTextFieldCallbacks, + transferMessageCallbacks = GetSetCallbacks(get = { "" }, set = {}), + selectedTransferType = GetSetCallbacks(get = { TransferTypeUi.Mail }, set = {}), + transferOptionsCallbacks = transferOptionsCallbacks, + pickFiles = {}, + exitNewTransfer = {}, + onSendButtonClick = {}, + isAwaitingSend = { true }, + navigateToFilesDetails = {}, + ) + } } } From a989b061a9aaaa5f3055af9a3df9ff50a4258ed9 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 6 Mar 2026 16:35:18 +0100 Subject: [PATCH 2/3] fix: Make it so EmailAddressTextField has the same top padding as OutlinedTextFields --- .../components/EmailAddressTextField.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/components/EmailAddressTextField.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/components/EmailAddressTextField.kt index 0f18261d6..29af3f739 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/components/EmailAddressTextField.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/components/EmailAddressTextField.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -57,6 +58,7 @@ import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -68,10 +70,12 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TransformedText import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.infomaniak.core.common.utils.isEmailRfc5321Compliant import com.infomaniak.core.ui.compose.margin.Margin import com.infomaniak.core.ui.compose.preview.PreviewLightAndDark -import com.infomaniak.core.common.utils.isEmailRfc5321Compliant import com.infomaniak.swisstransfer.R import com.infomaniak.swisstransfer.ui.components.SwissTransferInputChip import com.infomaniak.swisstransfer.ui.components.SwissTransferTextFieldDefaults @@ -130,7 +134,7 @@ fun EmailAddressTextField( } BasicTextField( - modifier = emailAddressTextFieldModifier, + modifier = emailAddressTextFieldModifier.padding(top = minimizedLabelHalfHeight()), value = textFieldValue, onValueChange = state::updateUiTextValue, textStyle = TextStyle(color = SwissTransferTheme.colors.primaryTextColor), @@ -155,6 +159,17 @@ fun EmailAddressTextField( ) } +/** + * Copied from OutlineTextField so the BasicTextField can have the same spacing as OutlineTextField with a label + */ +@Composable +private fun minimizedLabelHalfHeight(): Dp { + val compositionLocalValue = MaterialTheme.typography.bodySmall.lineHeight + val fallbackValue = 16.sp + val value = if (compositionLocalValue.isSp) compositionLocalValue else fallbackValue + return with(LocalDensity.current) { value.toDp() / 2 } +} + private class EmailAddressTextFieldState( initialText: String, private val validatedRecipientsEmails: GetSetCallbacks>, From 4ce85873c37c2ac37aa6c8f24a11a93c8dc62ce5 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Fri, 6 Mar 2026 16:51:04 +0100 Subject: [PATCH 3/3] refactor: Fix preview parameters --- .../newtransfer/pickfiles/PickFilesScreen.kt | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt index 6e14e6e5c..92f767639 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/pickfiles/PickFilesScreen.kt @@ -26,17 +26,14 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -56,19 +53,18 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.rememberPermissionState +import com.infomaniak.core.auth.models.user.User import com.infomaniak.core.common.mapSync import com.infomaniak.core.ui.compose.bottomstickybuttonscaffolds.BottomStickyButtonScaffold import com.infomaniak.core.ui.compose.margin.Margin import com.infomaniak.core.ui.compose.preview.PreviewAllWindows -import com.infomaniak.core.ui.compose.preview.previewparameter.usersPreviewData +import com.infomaniak.core.ui.compose.preview.previewparameter.UserListPreviewParameterProvider import com.infomaniak.multiplatform_swisstransfer.common.interfaces.ui.FileUi import com.infomaniak.multiplatform_swisstransfer.common.matomo.MatomoScreen import com.infomaniak.swisstransfer.R @@ -79,7 +75,7 @@ import com.infomaniak.swisstransfer.ui.components.LargeButton import com.infomaniak.swisstransfer.ui.components.SwissTransferTextField import com.infomaniak.swisstransfer.ui.components.SwissTransferTopAppBar import com.infomaniak.swisstransfer.ui.components.TopAppBarButtons -import com.infomaniak.swisstransfer.ui.previewparameter.FileUiListPreviewParameter +import com.infomaniak.swisstransfer.ui.previewparameter.filesPreviewData 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.ValidityPeriodOption @@ -503,7 +499,8 @@ enum class PasswordTransferOption( @PreviewAllWindows @Composable -private fun Preview(@PreviewParameter(FileUiListPreviewParameter::class) files: List) { +private fun Preview(@PreviewParameter(UserListPreviewParameterProvider::class) users: List) { + val files = filesPreviewData val transferOptionsCallbacks = TransferOptionsCallbacks( transferOptionsStates = { listOf( @@ -538,7 +535,7 @@ private fun Preview(@PreviewParameter(FileUiListPreviewParameter::class) files: validatedRecipientsEmails = GetSetCallbacks(get = { setOf("test.test@ik.me") }, set = {}), ) - CompositionLocalProvider(LocalUser provides usersPreviewData.first()) { + CompositionLocalProvider(LocalUser provides users.first()) { SwissTransferTheme { PickFilesScreen( files = { files },