diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/BottomStickyButtonScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/BottomStickyButtonScaffold.kt index 6c7294c7e..7237ac290 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/BottomStickyButtonScaffold.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/BottomStickyButtonScaffold.kt @@ -17,12 +17,22 @@ */ package com.infomaniak.swisstransfer.ui.components +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import com.infomaniak.swisstransfer.R +import com.infomaniak.swisstransfer.ui.theme.Dimens +import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme +import com.infomaniak.swisstransfer.ui.utils.PreviewLargeWindow +import com.infomaniak.swisstransfer.ui.utils.PreviewSmallWindow @Composable fun BottomStickyButtonScaffold( @@ -33,7 +43,7 @@ fun BottomStickyButtonScaffold( bottomButton: @Composable ((Modifier) -> Unit)? = null, content: @Composable BoxScope.() -> Unit, ) { - Scaffold( + SinglePaneScaffold( snackbarHost = { snackbarHostState?.let { SnackbarHost(hostState = it) } }, topBar = topBar, ) { contentPaddings -> @@ -41,9 +51,36 @@ fun BottomStickyButtonScaffold( modifier = modifier .fillMaxWidth() .padding(contentPaddings), + horizontalAlignment = Alignment.CenterHorizontally, ) { Box(modifier = Modifier.weight(1.0f), content = content) - DoubleButtonCombo(topButton, bottomButton) + DoubleButtonCombo( + modifier = Modifier.padding(vertical = Dimens.ButtonComboVerticalPadding), + topButton = topButton, + bottomButton = bottomButton, + ) + } + } +} + +@PreviewSmallWindow +@PreviewLargeWindow +@Composable +private fun Preview() { + SwissTransferTheme { + Surface { + BottomStickyButtonScaffold( + topBar = { BrandTopAppBar() }, + topButton = { modifier -> LargeButton(R.string.appName, onClick = {}, modifier = modifier) }, + ) { + Text( + modifier = Modifier + .fillMaxSize() + .background(Color.LightGray), + text = "content", + textAlign = TextAlign.Center, + ) + } } } } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/DoubleButtonCombo.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/DoubleButtonCombo.kt index ab7071426..e524f4ddd 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/DoubleButtonCombo.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/DoubleButtonCombo.kt @@ -32,11 +32,12 @@ private val WIDTH_THRESHOLD = 500.dp @Composable fun ColumnScope.DoubleButtonCombo( + modifier: Modifier = Modifier, topButton: @Composable ((Modifier) -> Unit)? = null, bottomButton: @Composable ((Modifier) -> Unit)? = null ) { BoxWithConstraints( - modifier = Modifier + modifier = modifier .widthIn(max = Dimens.DoubleButtonMaxWidth) .align(Alignment.CenterHorizontally), ) { @@ -71,7 +72,7 @@ private fun VerticallyStackedButtons( bottomButton( Modifier .fillMaxWidth() - .padding(start = Margin.Medium, end = Margin.Medium, bottom = Margin.Large), + .padding(horizontal = Margin.Medium), ) } } @@ -84,7 +85,7 @@ private fun HorizontallyStackedButtons( Row( modifier = Modifier .fillMaxWidth() - .padding(bottom = Margin.Large, start = Margin.Medium, end = Margin.Medium), + .padding(horizontal = Margin.Medium), horizontalArrangement = Arrangement.spacedBy(Margin.Medium), verticalAlignment = Alignment.CenterVertically, ) { @@ -99,7 +100,7 @@ private fun SingleButton(button: @Composable (Modifier) -> Unit) { button( Modifier .fillMaxWidth() - .padding(bottom = Margin.Large, start = Margin.Medium, end = Margin.Medium), + .padding(horizontal = Margin.Medium), ) } } @@ -125,6 +126,7 @@ private fun DoubleButtonComboPreview() { ) }, ) + Spacer(Modifier.height(Margin.Medium)) DoubleButtonCombo( bottomButton = { LargeButton( diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SinglePaneScaffold.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SinglePaneScaffold.kt new file mode 100644 index 000000000..545dea914 --- /dev/null +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SinglePaneScaffold.kt @@ -0,0 +1,71 @@ +/* + * 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.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import com.infomaniak.swisstransfer.ui.theme.Dimens +import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme +import com.infomaniak.swisstransfer.ui.utils.PreviewLargeWindow +import com.infomaniak.swisstransfer.ui.utils.PreviewLightAndDark + +@Composable +fun SinglePaneScaffold( + topBar: @Composable () -> Unit = {}, + bottomBar: @Composable () -> Unit = {}, + snackbarHost: @Composable () -> Unit = {}, + content: @Composable (PaddingValues) -> Unit, +) { + Scaffold( + topBar = topBar, + bottomBar = bottomBar, + snackbarHost = snackbarHost, + ) { contentPadding -> + Box( + contentAlignment = Alignment.TopCenter, + modifier = Modifier.fillMaxWidth(), + ) { + Box(Modifier.widthIn(max = Dimens.MaxSinglePaneScreenWidth)) { + content(contentPadding) + } + } + } +} + +@PreviewLightAndDark +@PreviewLargeWindow +@Composable +private fun Preview() { + SwissTransferTheme { + Surface { + SinglePaneScaffold { + Box( + Modifier + .fillMaxSize() + .background(Color.Cyan) + ) + } + } + } +} 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 index c1edffd88..a6faae7ce 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferAlertDialog.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferAlertDialog.kt @@ -39,7 +39,7 @@ fun SwissTransferAlertDialog( @StringRes descriptionRes: Int, onDismiss: () -> Unit, onConfirmation: () -> Unit, - shouldEnableConfirmButton: () -> Boolean = { true }, + isConfirmButtonEnabled: () -> Boolean = { true }, content: @Composable (ColumnScope.() -> Unit)? = null, ) { BasicAlertDialog( @@ -56,7 +56,7 @@ fun SwissTransferAlertDialog( additionalContent = content, onDismiss = onDismiss, onConfirmation = onConfirmation, - shouldEnableConfirmButton = shouldEnableConfirmButton, + isConfirmButtonEnabled = isConfirmButtonEnabled, ) } } @@ -70,16 +70,16 @@ private fun BasicAlertDialogContent( additionalContent: @Composable (ColumnScope.() -> Unit)? = null, onDismiss: () -> Unit, onConfirmation: () -> Unit, - shouldEnableConfirmButton: () -> Boolean = { true }, + isConfirmButtonEnabled: () -> Boolean = { true }, ) { Column(modifier.padding(Margin.Large)) { TitleAndDescription(titleRes, descriptionRes) Spacer(Modifier.height(Margin.Large)) additionalContent?.let { it() - Spacer(Modifier.height(Margin.Large)) + Spacer(Modifier.height(Margin.Mini)) } - ActionButtons(onDismiss, onConfirmation, shouldEnableConfirmButton) + ActionButtons(onDismiss, onConfirmation, isConfirmButtonEnabled) } } @@ -99,7 +99,7 @@ private fun TitleAndDescription(titleRes: Int, descriptionRes: Int) { } @Composable -private fun ActionButtons(onDismissRequest: () -> Unit, onConfirmation: () -> Unit, shouldEnable: () -> Boolean) { +private fun ActionButtons(onDismissRequest: () -> Unit, onConfirmation: () -> Unit, isEnabled: () -> Boolean) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End, @@ -114,7 +114,7 @@ private fun ActionButtons(onDismissRequest: () -> Unit, onConfirmation: () -> Un SmallButton( titleRes = R.string.buttonConfirm, onClick = onConfirmation, - enabled = shouldEnable + enabled = isEnabled ) } } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferBottomSheet.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferBottomSheet.kt index 6c1e6caf3..1fbc249a5 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferBottomSheet.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/components/SwissTransferBottomSheet.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp import com.infomaniak.swisstransfer.R import com.infomaniak.swisstransfer.ui.images.AppImages.AppIllus import com.infomaniak.swisstransfer.ui.images.illus.ArrowDownRightCurved +import com.infomaniak.swisstransfer.ui.theme.Dimens import com.infomaniak.swisstransfer.ui.theme.Margin import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme @@ -133,7 +134,11 @@ private fun BottomSheetContent( Spacer(Modifier.height(Margin.Large)) } - DoubleButtonCombo(topButton = topButton, bottomButton = bottomButton) + DoubleButtonCombo( + modifier = Modifier.padding(bottom = Dimens.ButtonComboVerticalPadding), + topButton = topButton, + bottomButton = bottomButton, + ) } } 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 b79628195..694778247 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 @@ -46,8 +46,12 @@ import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme /** * Wrapper for Material's [OutlinedTextField] that enforce our design needs. + * * By default, this TextField is single lined. You can specify [maxLineNumber] or [minLineNumber] to make it multi-lined + * * If [isPassword] value is true, the [keyboardType] field will be ignored to force [KeyboardType.Password] + * + * To set an error message, you need to pass this message as [supportingText] and set [isError] to true */ @Composable fun SwissTransferTextField( @@ -62,8 +66,8 @@ fun SwissTransferTextField( imeAction: ImeAction = ImeAction.Default, keyboardActions: KeyboardActions = KeyboardActions.Default, isReadOnly: Boolean = false, - errorMessage: @Composable () -> String? = { null }, - supportingText: String? = null, + isError: Boolean = false, + supportingText: @Composable (() -> Unit)? = null, onValueChange: ((String) -> Unit)? = null, ) { @@ -80,17 +84,6 @@ fun SwissTransferTextField( disabledTrailingIconColor = SwissTransferTheme.colors.iconColor, ) - @Composable - fun getSupportingText(): (@Composable () -> Unit)? { - val displayText = if (text.isEmpty()) { - supportingText - } else { - errorMessage() ?: supportingText - } - - return displayText?.let { @Composable { Text(it) } } - } - OutlinedTextField( modifier = modifier, readOnly = isReadOnly, @@ -119,8 +112,8 @@ fun SwissTransferTextField( imeAction = imeAction, ), keyboardActions = keyboardActions, - isError = errorMessage() != null && text.isNotEmpty(), - supportingText = getSupportingText(), + isError = isError, + supportingText = supportingText, ) } @@ -139,18 +132,21 @@ private fun getShowPasswordButton(shouldShowPassword: Boolean, onClick: () -> Un @Composable @Preview private fun Preview() { + val supportingText = "supporting Text" + val initialValue = "initialValue" + SwissTransferTheme { Surface { Column(Modifier.padding(Margin.Medium)) { SwissTransferTextField( label = stringResource(R.string.transferMessagePlaceholder), initialValue = "test", - errorMessage = { null }, ) SwissTransferTextField( keyboardType = KeyboardType.Email, initialValue = "a@a@.com", - errorMessage = { "Invalid address" }, + supportingText = { Text("Invalid address") }, + isError = true, label = stringResource(R.string.transferRecipientAddressPlaceholder), ) SwissTransferTextField( @@ -160,25 +156,25 @@ private fun Preview() { ) SwissTransferTextField( maxLineNumber = 10, - initialValue = "initial value", + initialValue = initialValue, isRequired = false, label = stringResource(R.string.transferMessagePlaceholder), - supportingText = "supporting Text", + supportingText = { Text(supportingText) }, ) SwissTransferTextField( maxLineNumber = 10, - initialValue = "initial value", + initialValue = initialValue, isPassword = true, label = stringResource(R.string.settingsOptionPassword), - supportingText = "supporting Text", + supportingText = { Text(supportingText) }, ) SwissTransferTextField( maxLineNumber = 10, - initialValue = "initial value", + initialValue = initialValue, isPassword = true, label = stringResource(R.string.settingsOptionPassword), - errorMessage = { "Wrong password" }, - supportingText = "supporting Text", + isError = true, + supportingText = { Text("Wrong password") }, ) } } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/images/icons/QrCode.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/images/icons/QrCode.kt index f0f038ed8..be14ab524 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/images/icons/QrCode.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/images/icons/QrCode.kt @@ -27,7 +27,6 @@ import androidx.compose.ui.graphics.PathFillType.Companion.NonZero import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector.Builder -import androidx.compose.ui.graphics.vector.group import androidx.compose.ui.graphics.vector.path import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -48,63 +47,60 @@ val AppIcons.QrCode: ImageVector viewportWidth = 24.0f, viewportHeight = 25.0f, ).apply { - group { - path( - fill = null, - stroke = SolidColor(Color(0xFF9F9F9F)), - strokeLineWidth = 1.5f, - strokeLineCap = strokeCapRound, - strokeLineJoin = strokeJoinRound, - strokeLineMiter = 4.0f, - pathFillType = NonZero, - ) { - moveTo(5.25f, 6.033f) - horizontalLineToRelative(3.0f) - verticalLineToRelative(3.0f) - horizontalLineToRelative(-3.0f) - close() - moveToRelative(0.0f, 10.5f) - horizontalLineToRelative(3.0f) - verticalLineToRelative(3.0f) - horizontalLineToRelative(-3.0f) - close() - moveToRelative(10.5f, -10.5f) - horizontalLineToRelative(3.0f) - verticalLineToRelative(3.0f) - horizontalLineToRelative(-3.0f) - close() - moveToRelative(-10.5f, 7.5f) - horizontalLineToRelative(6.0f) - verticalLineToRelative(1.5f) - moveToRelative(3.0f, 0.0f) - verticalLineToRelative(4.5f) - horizontalLineToRelative(4.5f) - verticalLineToRelative(-4.5f) - horizontalLineToRelative(-1.5f) - moveToRelative(-6.0f, 3.0f) - verticalLineToRelative(1.5f) - moveToRelative(0.0f, -13.5f) - verticalLineToRelative(4.5f) - horizontalLineToRelative(1.5f) - moveToRelative(3.0f, 1.5f) - horizontalLineToRelative(3.0f) - moveToRelative(-18.0f, -5.25f) - verticalLineToRelative(-3.75f) - arcToRelative(1.5f, 1.5f, 0.0f, false, true, 1.5f, -1.5f) - horizontalLineTo(6.0f) - moveToRelative(12.0f, 0.0f) - horizontalLineToRelative(3.75f) - arcToRelative(1.5f, 1.5f, 0.0f, false, true, 1.5f, 1.5f) - verticalLineToRelative(3.75f) - moveToRelative(0.0f, 12.0f) - verticalLineToRelative(3.75f) - arcToRelative(1.5f, 1.5f, 0.0f, false, true, -1.5f, 1.5f) - horizontalLineTo(18.0f) - moveToRelative(-12.0f, 0.0f) - horizontalLineTo(2.25f) - arcToRelative(1.5f, 1.5f, 0.0f, false, true, -1.5f, -1.5f) - verticalLineToRelative(-3.75f) - } + path( + stroke = SolidColor(Color(0xFF9F9F9F)), + strokeLineWidth = 1.5f, + strokeLineCap = strokeCapRound, + strokeLineJoin = strokeJoinRound, + strokeLineMiter = 4.0f, + pathFillType = NonZero, + ) { + moveTo(5.25f, 5.866f) + horizontalLineToRelative(3.0f) + verticalLineToRelative(3.0f) + horizontalLineToRelative(-3.0f) + close() + moveTo(5.25f, 16.366f) + horizontalLineToRelative(3.0f) + verticalLineToRelative(3.0f) + horizontalLineToRelative(-3.0f) + close() + moveTo(15.75f, 5.866f) + horizontalLineToRelative(3.0f) + verticalLineToRelative(3.0f) + horizontalLineToRelative(-3.0f) + close() + moveTo(5.25f, 13.366f) + horizontalLineToRelative(6.0f) + verticalLineToRelative(1.5f) + moveTo(14.25f, 14.866f) + verticalLineToRelative(4.5f) + horizontalLineToRelative(4.5f) + verticalLineToRelative(-4.5f) + horizontalLineToRelative(-1.5f) + moveTo(11.25f, 17.866f) + verticalLineToRelative(1.5f) + moveTo(11.25f, 5.866f) + verticalLineToRelative(4.5f) + horizontalLineToRelative(1.5f) + moveTo(15.75f, 11.866f) + horizontalLineToRelative(3.0f) + moveTo(0.75f, 6.616f) + verticalLineToRelative(-3.75f) + arcToRelative(1.5f, 1.5f, 0.0f, false, true, 1.5f, -1.5f) + horizontalLineTo(6.0f) + moveTo(18.0f, 1.366f) + horizontalLineToRelative(3.75f) + arcToRelative(1.5f, 1.5f, 0.0f, false, true, 1.5f, 1.5f) + verticalLineToRelative(3.75f) + moveTo(23.25f, 18.616f) + verticalLineToRelative(3.75f) + arcToRelative(1.5f, 1.5f, 0.0f, false, true, -1.5f, 1.5f) + horizontalLineTo(18.0f) + moveTo(6.0f, 23.866f) + horizontalLineTo(2.25f) + arcToRelative(1.5f, 1.5f, 0.0f, false, true, -1.5f, -1.5f) + verticalLineToRelative(-3.75f) } }.build() 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 a540d5e18..c8372578b 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 @@ -30,7 +30,6 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.hilt.navigation.compose.hiltViewModel @@ -49,11 +48,8 @@ import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.components import com.infomaniak.swisstransfer.ui.theme.Margin import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme import com.infomaniak.swisstransfer.ui.utils.GetSetCallbacks -import com.infomaniak.swisstransfer.ui.utils.HumanReadableSizeUtils.getHumanReadableSize import com.infomaniak.swisstransfer.ui.utils.PreviewAllWindows -private const val TOTAL_FILE_SIZE: Long = 50_000_000_000L - @Composable fun ImportFilesScreen( importFilesViewModel: ImportFilesViewModel = hiltViewModel(), @@ -147,34 +143,6 @@ private fun ImportFilesScreen( shouldStartByPromptingUserForFiles: Boolean, sendTransfer: () -> Unit, ) { - val context = LocalContext.current - var shouldShowInitialFilePick by rememberSaveable { mutableStateOf(shouldStartByPromptingUserForFiles) } - var showTransferOption by rememberSaveable { mutableStateOf(null) } - - val importedFiles = files() - val humanReadableSize = remember(importedFiles) { - val usedSpace = importedFiles.sumOf { it.fileSize } - val spaceLeft = (TOTAL_FILE_SIZE - usedSpace).coerceAtLeast(0) - getHumanReadableSize(context, spaceLeft) - } - - val filePickerLauncher = rememberLauncherForActivityResult( - contract = ActivityResultContracts.OpenMultipleDocuments() - ) { uris: List -> - addFiles(uris) - } - - fun pickFiles() { - shouldShowInitialFilePick = false - filePickerLauncher.launch(arrayOf("*/*")) - } - - fun closeTransferOption() { - showTransferOption = null - } - - LaunchedEffect(Unit) { if (shouldShowInitialFilePick) pickFiles() } - BottomStickyButtonScaffold( topBar = { SwissTransferTopAppBar( @@ -187,63 +155,84 @@ private fun ImportFilesScreen( SendButton(filesToImportCount, currentSessionFilesCount, files, modifier, sendTransfer) }, content = { - Column( - modifier = Modifier - .padding(horizontal = Margin.Medium, vertical = Margin.Large) - .verticalScroll(rememberScrollState()), - ) { - ImportFilesTitle(titleRes = R.string.myFilesTitle) - ImportedFilesCard( - modifier = Modifier.padding(vertical = Margin.Medium), - files = files, - humanReadableSize = { humanReadableSize }, - pickFiles = ::pickFiles, - removeFileByUid = removeFileByUid, - ) - ImportTextFields(transferMessage, selectedTransferType.get) - ImportFilesTitle(Modifier.padding(vertical = Margin.Medium), titleRes = R.string.transferTypeTitle) - TransferTypeButtons(selectedTransferType) - ImportFilesTitle(Modifier.padding(vertical = Margin.Medium), titleRes = R.string.advancedSettingsTitle) - TransferOptionsTypes( - transferOptionsStates = transferOptionsCallbacks.transferOptionsStates, - onClick = { selectedOptionType -> showTransferOption = selectedOptionType }, + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + FilesToImport(files, removeFileByUid, addFiles, shouldStartByPromptingUserForFiles) + Spacer(Modifier.height(Margin.Medium)) + ImportTextFields( + modifier = Modifier.padding(horizontal = Margin.Medium), + transferMessage = transferMessage, + selectedTransferType = selectedTransferType.get, ) + SendByOptions(selectedTransferType) + TransferOptions(transferOptionsCallbacks) } - - TransferOptions({ showTransferOption }, transferOptionsCallbacks, ::closeTransferOption) } ) } @Composable -private fun ColumnScope.ImportTextFields(transferMessage: GetSetCallbacks, selectedTransferType: () -> TransferTypeUi) { +private fun FilesToImport( + files: () -> List, + removeFileByUid: (uid: String) -> Unit, + addFiles: (List) -> Unit, + shouldStartByPromptingUserForFiles: Boolean, +) { + var shouldShowInitialFilePick by rememberSaveable { mutableStateOf(shouldStartByPromptingUserForFiles) } - EmailAddressesTextFields(selectedTransferType) + val filePickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenMultipleDocuments(), + onResult = addFiles, + ) - SwissTransferTextField( - modifier = Modifier.fillMaxWidth(), - label = stringResource(R.string.transferMessagePlaceholder), - isRequired = false, - minLineNumber = 3, - onValueChange = transferMessage.set, + fun pickFiles() { + shouldShowInitialFilePick = false + filePickerLauncher.launch(arrayOf("*/*")) + } + + LaunchedEffect(Unit) { if (shouldShowInitialFilePick) pickFiles() } + + ImportFilesTitle(modifier = Modifier.padding(horizontal = Margin.Medium), titleRes = R.string.myFilesTitle) + ImportedFilesCard( + modifier = Modifier.padding(horizontal = Margin.Medium), + files = files, + pickFiles = ::pickFiles, + removeFileByUid = removeFileByUid, ) } @Composable -private fun ColumnScope.EmailAddressesTextFields(selectedTransferType: () -> TransferTypeUi) { +private fun ImportTextFields( + modifier: Modifier, + transferMessage: GetSetCallbacks, + selectedTransferType: () -> TransferTypeUi, +) { + Column(modifier) { + EmailAddressesTextFields(Modifier.fillMaxWidth(), selectedTransferType) + SwissTransferTextField( + modifier = Modifier.fillMaxWidth(), + label = stringResource(R.string.transferMessagePlaceholder), + isRequired = false, + minLineNumber = 3, + onValueChange = transferMessage.set, + ) + } +} + +@Composable +private fun ColumnScope.EmailAddressesTextFields(modifier: Modifier, selectedTransferType: () -> TransferTypeUi) { val shouldShowEmailAddressesFields by remember { derivedStateOf { selectedTransferType() == TransferTypeUi.MAIL } } AnimatedVisibility(visible = shouldShowEmailAddressesFields) { Column { SwissTransferTextField( - modifier = Modifier.fillMaxWidth(), + modifier = modifier, label = stringResource(R.string.transferSenderAddressPlaceholder), onValueChange = { /* TODO */ }, ) Spacer(Modifier.height(Margin.Medium)) SwissTransferTextField( - modifier = Modifier.fillMaxWidth(), + modifier = modifier, label = stringResource(R.string.transferRecipientAddressPlaceholder), onValueChange = { /* TODO */ }, ) @@ -253,7 +242,31 @@ private fun ColumnScope.EmailAddressesTextFields(selectedTransferType: () -> Tra } @Composable -private fun TransferOptions( +private fun SendByOptions(selectedTransferType: GetSetCallbacks) { + ImportFilesTitle(Modifier.padding(horizontal = Margin.Medium), titleRes = R.string.transferTypeTitle) + TransferTypeButtons(selectedTransferType) +} + +@Composable +private fun TransferOptions(transferOptionsCallbacks: TransferOptionsCallbacks) { + + var showTransferOption by rememberSaveable { mutableStateOf(null) } + + fun closeTransferOption() { + showTransferOption = null + } + + ImportFilesTitle(Modifier.padding(horizontal = Margin.Medium), titleRes = R.string.advancedSettingsTitle) + TransferOptionsTypes( + modifier = Modifier.padding(horizontal = Margin.Medium), + transferOptionsStates = transferOptionsCallbacks.transferOptionsStates, + onClick = { selectedOptionType -> showTransferOption = selectedOptionType }, + ) + TransferOptionsDialogs({ showTransferOption }, transferOptionsCallbacks, ::closeTransferOption) +} + +@Composable +private fun TransferOptionsDialogs( selectedOptionType: () -> TransferOptionType?, transferOptionsCallbacks: TransferOptionsCallbacks, closeTransferOption: () -> Unit, @@ -287,6 +300,15 @@ private fun TransferOptions( } } +@Composable +private fun ImportFilesTitle(modifier: Modifier = Modifier, @StringRes titleRes: Int) { + Text( + modifier = modifier.padding(vertical = Margin.Medium), + style = SwissTransferTheme.typography.bodySmallRegular, + text = stringResource(titleRes), + ) +} + @Composable private fun SendButton( filesToImportCount: () -> Int, @@ -317,15 +339,6 @@ private fun SendButton( ) } -@Composable -private fun ImportFilesTitle(modifier: Modifier = Modifier, @StringRes titleRes: Int) { - Text( - text = stringResource(titleRes), - style = SwissTransferTheme.typography.bodySmallRegular, - modifier = modifier, - ) -} - data class TransferOptionsCallbacks( val transferOptionsStates: () -> List, val onTransferOptionValueSelected: (SettingOption) -> Unit, @@ -382,7 +395,7 @@ private fun Preview(@PreviewParameter(FileUiListPreviewParameter::class) files: filesToImportCount = { 0 }, currentSessionFilesCount = { 0 }, transferMessage = GetSetCallbacks(get = { "" }, set = {}), - selectedTransferType = GetSetCallbacks(get = { TransferTypeUi.QR_CODE }, set = {}), + selectedTransferType = GetSetCallbacks(get = { TransferTypeUi.MAIL }, set = {}), transferOptionsCallbacks = transferOptionsCallbacks, removeFileByUid = {}, addFiles = {}, 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 390d72efe..3d88f8137 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 @@ -25,7 +25,11 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Icon import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp @@ -40,17 +44,30 @@ import com.infomaniak.swisstransfer.ui.theme.CustomShapes import com.infomaniak.swisstransfer.ui.theme.Margin import com.infomaniak.swisstransfer.ui.theme.SwissTransferTheme import com.infomaniak.swisstransfer.ui.utils.HumanReadableSizeUtils.formatSpaceLeft +import com.infomaniak.swisstransfer.ui.utils.HumanReadableSizeUtils.getHumanReadableSize import com.infomaniak.swisstransfer.ui.utils.PreviewLightAndDark import kotlinx.parcelize.Parcelize +private const val TOTAL_FILE_SIZE: Long = 50_000_000_000L + @Composable fun ImportedFilesCard( modifier: Modifier = Modifier, files: () -> List, - humanReadableSize: () -> String, pickFiles: () -> Unit, removeFileByUid: (uid: String) -> Unit, ) { + + val context = LocalContext.current + + val humanReadableSize by remember { + derivedStateOf { + val usedSpace = files().sumOf { it.fileSize } + val spaceLeft = (TOTAL_FILE_SIZE - usedSpace).coerceAtLeast(0) + getHumanReadableSize(context, spaceLeft) + } + } + SwissTransferCard(modifier) { SharpRippleButton(onClick = { /* TODO */ }) { TextDotText( @@ -58,7 +75,7 @@ fun ImportedFilesCard( val fileCount = files().count() pluralStringResource(R.plurals.filesCount, fileCount, fileCount) }, - secondText = { formatSpaceLeft(humanReadableSize) }, + secondText = { formatSpaceLeft { humanReadableSize } }, modifier = Modifier.padding(start = Margin.Medium), ) Spacer(Modifier.weight(1.0f)) @@ -130,7 +147,6 @@ private fun ImportedFilesCardPreview(@PreviewParameter(FileUiListPreviewParamete ImportedFilesCard( modifier = Modifier.padding(Margin.Medium), files = { files }, - humanReadableSize = { "20 GB" }, pickFiles = {}, removeFileByUid = {}, ) 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 index 709b3527a..007742331 100644 --- 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 @@ -18,10 +18,7 @@ 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.foundation.layout.* import androidx.compose.material3.Surface import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -29,10 +26,12 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha 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.MatomoSwissTransfer.toFloat import com.infomaniak.swisstransfer.ui.components.SwissTransferAlertDialog import com.infomaniak.swisstransfer.ui.components.SwissTransferTextField import com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.PasswordTransferOption @@ -75,9 +74,10 @@ fun PasswordOptionAlertDialog( descriptionRes = R.string.settingsPasswordDescription, onDismiss = ::onDismiss, onConfirmation = ::onConfirmButtonClicked, - shouldEnableConfirmButton = { if (isPasswordActivated) isPasswordValid() else true }, + isConfirmButtonEnabled = { !isPasswordActivated || isPasswordValid() }, ) { ActivatePasswordSwitch(isChecked = isPasswordActivated, onCheckedChange = { isPasswordActivated = it }) + Spacer(Modifier.height(Margin.Medium)) AnimatedPasswordInput(isPasswordActivated, password, isPasswordValid) } } @@ -101,14 +101,22 @@ private fun ColumnScope.AnimatedPasswordInput( password: GetSetCallbacks, isPasswordValid: () -> Boolean ) { + + val isError = !isPasswordValid() && password.get().isNotEmpty() AnimatedVisibility(isChecked) { - Spacer(Modifier.height(Margin.Mini)) SwissTransferTextField( + modifier = Modifier.fillMaxWidth(), label = stringResource(R.string.settingsOptionPassword), isPassword = true, initialValue = password.get(), imeAction = ImeAction.Done, - errorMessage = { if (isPasswordValid()) null else stringResource(R.string.errorTransferPasswordLength) }, + isError = isError, + supportingText = { + Text( + modifier = Modifier.alpha(isError.toFloat()), + text = stringResource(R.string.errorTransferPasswordLength), + ) + }, onValueChange = { password.set(it) }, ) } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/TransferTypeButtons.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/TransferTypeButtons.kt index e32851f3a..fd2d5d665 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/TransferTypeButtons.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/newtransfer/importfiles/components/TransferTypeButtons.kt @@ -20,13 +20,13 @@ package com.infomaniak.swisstransfer.ui.screen.newtransfer.importfiles.component import androidx.annotation.PluralsRes import androidx.annotation.StringRes import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.unit.dp import com.infomaniak.multiplatform_swisstransfer.common.models.TransferType import com.infomaniak.swisstransfer.R import com.infomaniak.swisstransfer.ui.images.AppImages.AppIcons @@ -42,7 +42,9 @@ import com.infomaniak.swisstransfer.ui.utils.PreviewLightAndDark @Composable fun TransferTypeButtons(transferType: GetSetCallbacks) { Row( - modifier = Modifier.horizontalScroll(rememberScrollState()), + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .padding(horizontal = Margin.Medium), horizontalArrangement = Arrangement.spacedBy(Margin.Mini), ) { for (transferTypeEntry in TransferTypeUi.entries) { diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Dimens.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Dimens.kt index fa589a989..547485b19 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Dimens.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/theme/Dimens.kt @@ -20,13 +20,15 @@ package com.infomaniak.swisstransfer.ui.theme import androidx.compose.ui.unit.dp object Dimens { + val MaxSinglePaneScreenWidth = 800.dp val SettingHorizontalMargin = Margin.Medium val SettingVerticalMargin = Margin.Small val DescriptionWidth = 300.dp val LargeButtonHeight = 56.dp - val DoubleButtonMaxWidth = 800.dp + val DoubleButtonMaxWidth = MaxSinglePaneScreenWidth val SingleButtonMaxWidth = DoubleButtonMaxWidth / 2 val SmallIconSize = 16.dp val IconSize = 24.dp val BorderWidth = 1.dp + val ButtonComboVerticalPadding = Margin.Small }