Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add password popup #149

Merged
merged 17 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ffc9982
feat(TransferPasswordAlertDialog): Add first version of component Swi…
FabianDevel Nov 1, 2024
a3d89e3
feat(TransferPasswordAlertDialog): Add action buttons to the alertDialog
FabianDevel Nov 1, 2024
ff33a08
feat(TransferPasswordAlertDialog): Add onConfirmation callback
FabianDevel Nov 4, 2024
cffd1ff
feat(TransferPasswordAlertDialog): Add PasswordOptionAlertDialog
FabianDevel Nov 4, 2024
00fd665
feat(TransferPasswordAlertDialog): Add password length check
FabianDevel Nov 4, 2024
ca38b31
feat(TransferPasswordAlertDialog): Disable confirmation button when p…
FabianDevel Nov 5, 2024
1cf9aab
feat(TransferPasswordAlertDialog): Keep password when reopening dialog
FabianDevel Nov 5, 2024
1707f37
feat(TransferPasswordAlertDialog): Move down savedPassword state to t…
FabianDevel Nov 5, 2024
08054a6
chore(TransferPasswordAlertDialog): Clean code
FabianDevel Nov 5, 2024
bc2c105
fix(TransferPasswordAlertDialog): Fix password state not being correc…
FabianDevel Nov 5, 2024
b204578
refactor(TransferPasswordAlertDialog): Move AdvancedOptionCallbacks i…
FabianDevel Nov 6, 2024
6884745
feat(TransferPasswordAlertDialog): Put advanced transfer option in sa…
FabianDevel Nov 6, 2024
48f0ad1
feat(TransferPasswordAlertDialog): Get AppSettings from viewModel ins…
FabianDevel Nov 6, 2024
e6931ba
chore(TransferPasswordAlertDialog): Apply suggestions
FabianDevel Nov 7, 2024
8427226
feat(TransferPasswordAlertDialog): Add ImeAction configuration in Tex…
FabianDevel Nov 7, 2024
ec7c5fb
refactor(TransferPasswordAlertDialog): Move all advancedOptions view …
FabianDevel Nov 7, 2024
5e4c419
chore(TransferPasswordAlertDialog): Apply suggestions
FabianDevel Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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 = {},
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
) {
Expand All @@ -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,
Expand All @@ -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(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -104,10 +110,6 @@ class ImportFilesViewModel @Inject constructor(
}
}

fun selectTransferType(type: TransferType) {
_selectedTransferType.value = type
}

fun sendTransfer() {
viewModelScope.launch(ioDispatcher) {
runCatching {
Expand Down Expand Up @@ -146,53 +148,109 @@ class ImportFilesViewModel @Inject constructor(
}

//region Transfer Type
private val _selectedTransferType = MutableStateFlow(TransferType.LINK)
val selectedTransferType: StateFlow<TransferType> = _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<ValidityPeriodOption?>(null)
val selectedValidityPeriodOption: StateFlow<ValidityPeriodOption?> = _selectedValidityPeriodOption.asStateFlow()
val selectedValidityPeriodOption = savedStateHandle.getStateFlow(
key = SELECTED_VALIDITY_PERIOD_KEY,
initialValue = ValidityPeriodOption.THIRTY
)

private val _selectedDownloadLimitOption = MutableStateFlow<DownloadLimitOption?>(null)
val selectedDownloadLimitOption: StateFlow<DownloadLimitOption?> = _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<PasswordTransferOption?> = _selectedPasswordOption.asStateFlow()
val selectedPasswordOption = savedStateHandle.getStateFlow(
key = SELECTED_PASSWORD_OPTION_KEY,
initialValue = PasswordTransferOption.NONE,
)

private val _selectedLanguageOption = MutableStateFlow<EmailLanguageOption?>(null)
val selectedLanguageOption: StateFlow<EmailLanguageOption?> = _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<AdvancedOptionsState>): 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"
}
}
Loading
Loading