From 993ab75b40a94e618d8abf2f032b4e4a10faefe3 Mon Sep 17 00:00:00 2001 From: Kapil Baser <94954247+kapilBaser@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:53:14 +0530 Subject: [PATCH] Add Password Generator with Specific Special Characters #862 (#865) * Precise choice about what special characters I need to use #862 * List Of Symbols Bug * Changed * Code Formatted * SpotlessApply Checked --- app/build.gradle.kts | 17 ++---- .../ui/commonComponents/KeyPassInputField.kt | 2 +- .../keypass/ui/detail/components/Fields.kt | 26 ++++---- .../ui/generate/GeneratePasswordViewModel.kt | 32 ++++++++++ .../ui/generate/ui/GeneratePasswordContent.kt | 61 ++++++++++++++++--- .../ui/generate/ui/GeneratePasswordScreen.kt | 1 + common/build.gradle.kts | 4 +- .../common/constants/AccountType.kt | 1 + .../common/data/PasswordConfig.kt | 3 + .../common/utils/PasswordGenerator.kt | 6 +- local.properties | 4 +- shared/build.gradle.kts | 1 - 12 files changed, 118 insertions(+), 40 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e09e70b7..9a168b22 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,12 +41,11 @@ android { applicationIdSuffix = ".staging" signingConfig = signingConfigs.getByName("debug") } - } productFlavors { create("free") { - isDefault=true + isDefault = true } create("pro") { applicationIdSuffix = ".pro" @@ -61,7 +60,7 @@ android { jvmTarget = "17" freeCompilerArgs = listOf( - "-Xopt-in=androidx.compose.material3.ExperimentalMaterial3Api" + "-Xopt-in=androidx.compose.material3.ExperimentalMaterial3Api", ) } buildFeatures { @@ -71,8 +70,6 @@ android { flavorDimensions("default") - - packagingOptions { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" @@ -89,11 +86,10 @@ android { } } - lint{ + lint { disable += "MissingTranslation" abortOnError = true } - } ruler { @@ -103,10 +99,9 @@ ruler { sdkVersion.set(27) } - dependencies { - //api(project(":shared")) + // api(project(":shared")) api(project(":common")) testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") @@ -151,12 +146,10 @@ dependencies { kapt("androidx.hilt:hilt-compiler:1.2.0") implementation("androidx.hilt:hilt-navigation-compose:1.2.0") - // zxing library // implementation "com.googl.ezxing:android-core:3.4.1" implementation("com.journeyapps:zxing-android-embedded:4.3.0") - // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1") // ...with Kotlin. @@ -171,6 +164,4 @@ dependencies { implementation("me.saket.cascade:cascade-compose:2.2.0") implementation("androidx.biometric:biometric:1.1.0") - } - diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/KeyPassInputField.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/KeyPassInputField.kt index 7b603cde..4d69256f 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/KeyPassInputField.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/KeyPassInputField.kt @@ -26,7 +26,7 @@ fun KeyPassInputField( leadingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null, visualTransformation: VisualTransformation = VisualTransformation.None, - copyToClipboardClicked: ((String) -> Unit) ? = null + copyToClipboardClicked: ((String) -> Unit)? = null ) { OutlinedTextField( modifier = modifier.fillMaxWidth(), diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/components/Fields.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/components/Fields.kt index fdce96d1..7418c1cc 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/components/Fields.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/detail/components/Fields.kt @@ -89,20 +89,22 @@ fun Fields( }, leadingIcon = if (accountModel.id != null) { null - } else ( - { - IconButton( - onClick = { - updateAccountModel(accountModel.copy(password = PasswordGenerator(passwordConfig).generatePassword())) + } else { + ( + { + IconButton( + onClick = { + updateAccountModel(accountModel.copy(password = PasswordGenerator(passwordConfig).generatePassword())) + } + ) { + Icon( + painter = rememberVectorPainter(image = Icons.Rounded.Refresh), + contentDescription = "" + ) } - ) { - Icon( - painter = rememberVectorPainter(image = Icons.Rounded.Refresh), - contentDescription = "" - ) } - } - ), + ) + }, visualTransformation = visualTransformation, copyToClipboardClicked = copyToClipboardClicked ) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordViewModel.kt index 642a8963..07ab5e3f 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/GeneratePasswordViewModel.kt @@ -52,6 +52,38 @@ class GeneratePasswordViewModel @Inject constructor( } } + fun selectSymbolForPassword(symbol: Char) { + val tempList = _viewState.value.listOfSymbols.toMutableList() + + if (tempList.size == PasswordGenerator.totalSymbol.size && tempList.contains(symbol)) { + tempList.clear() + } + if (symbol == 's') { + _viewState.update { + it.copy( + listOfSymbols = PasswordGenerator.totalSymbol + ) + } + return + } + if (tempList.contains(symbol)) { + tempList.remove(symbol) + + if (tempList.isEmpty()) { + selectSymbolForPassword('s') + return + } + } else { + tempList.add(symbol) + } + + _viewState.update { + it.copy( + listOfSymbols = tempList.toList() + ) + } + } + fun onPasswordLengthSliderChange(value: Float) { _viewState.update { it.copy(length = value) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordContent.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordContent.kt index d9e11af3..14d4268e 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordContent.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordContent.kt @@ -3,6 +3,8 @@ package com.yogeshpaliyal.keypass.ui.generate.ui import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -14,16 +16,19 @@ import androidx.compose.material3.CardDefaults import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.google.accompanist.themeadapter.material3.Mdc3Theme import com.yogeshpaliyal.common.data.PasswordConfig +import com.yogeshpaliyal.common.utils.PasswordGenerator import com.yogeshpaliyal.keypass.ui.generate.ui.components.CheckboxWithLabel import com.yogeshpaliyal.keypass.ui.generate.ui.components.PasswordLengthInput @@ -37,6 +42,7 @@ fun GeneratePasswordContent( onLowercaseCheckedChange: (Boolean) -> Unit, onNumbersCheckedChange: (Boolean) -> Unit, onSymbolsCheckedChange: (Boolean) -> Unit, + selectSymbolForPassword: (Char) -> Unit = {}, onBlankSpacesCheckedChange: (Boolean) -> Unit ) { Scaffold( @@ -56,6 +62,7 @@ fun GeneratePasswordContent( onLowercaseCheckedChange = onLowercaseCheckedChange, onNumbersCheckedChange = onNumbersCheckedChange, onSymbolsCheckedChange = onSymbolsCheckedChange, + selectSymbolForPassword = selectSymbolForPassword, onBlankSpacesCheckedChange = onBlankSpacesCheckedChange ) } @@ -84,6 +91,7 @@ private fun FormInputCard( onLowercaseCheckedChange: (Boolean) -> Unit, onNumbersCheckedChange: (Boolean) -> Unit, onSymbolsCheckedChange: (Boolean) -> Unit, + selectSymbolForPassword: (Char) -> Unit = {}, onBlankSpacesCheckedChange: (Boolean) -> Unit ) { OutlinedCard( @@ -107,7 +115,7 @@ private fun FormInputCard( NumberInput(viewState.includeNumbers, onNumbersCheckedChange) - SymbolInput(viewState.includeSymbols, onSymbolsCheckedChange) + SymbolInput(viewState.includeSymbols, onSymbolsCheckedChange, selectSymbolForPassword, viewState.listOfSymbols) BlankSpaceInput(viewState.includeBlankSpaces, onBlankSpacesCheckedChange) } @@ -176,16 +184,55 @@ private fun NumberInput( ) } +@OptIn(ExperimentalLayoutApi::class) @Composable private fun SymbolInput( includeSymbols: Boolean, - onSymbolsCheckedChange: (Boolean) -> Unit + onSymbolsCheckedChange: (Boolean) -> Unit, + selectSymbolForPassword: (Char) -> Unit = {}, + selectedSymbols: List ) { - CheckboxWithLabel( - label = "Symbols", - checked = includeSymbols, - onCheckedChange = onSymbolsCheckedChange - ) + Column { + CheckboxWithLabel( + label = "Symbols", + checked = includeSymbols, + onCheckedChange = onSymbolsCheckedChange + ) + if (includeSymbols) { + FlowRow(modifier = Modifier.fillMaxWidth()) { + OutlinedCard( + onClick = { + selectSymbolForPassword('s') + }, + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.outlinedCardColors( + containerColor = if (selectedSymbols.size == PasswordGenerator.totalSymbol.size) MaterialTheme.colorScheme.primary else Color.Unspecified + ) + ) { + Text( + text = "All", + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + } + PasswordGenerator.totalSymbol.forEach { symbol -> + OutlinedCard( + onClick = { + selectSymbolForPassword(symbol) + }, + shape = RoundedCornerShape(8.dp), + colors = CardDefaults.outlinedCardColors( + if (symbol in selectedSymbols && selectedSymbols.size != PasswordGenerator.totalSymbol.size) MaterialTheme.colorScheme.primary else Color.Unspecified + ) + ) { + Text( + text = "$symbol", + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + } + } + } + } + } } @Composable diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordScreen.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordScreen.kt index 58e04f99..58b06042 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordScreen.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/generate/ui/GeneratePasswordScreen.kt @@ -31,6 +31,7 @@ fun GeneratePasswordScreen(viewModel: GeneratePasswordViewModel = hiltViewModel( onLowercaseCheckedChange = viewModel::onLowercaseCheckedChange, onNumbersCheckedChange = viewModel::onNumbersCheckedChange, onSymbolsCheckedChange = viewModel::onSymbolsCheckedChange, + selectSymbolForPassword = viewModel::selectSymbolForPassword, onBlankSpacesCheckedChange = viewModel::onBlankSpacesCheckedChange ) } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index b88bee27..b63ad634 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { // apache common codec implementation("commons-codec:commons-codec:1.17.0") - //Androidx Security + // Androidx Security implementation("androidx.security:security-crypto:1.1.0-alpha06") api("androidx.documentfile:documentfile:1.0.1") @@ -76,6 +76,4 @@ dependencies { implementation("androidx.sqlite:sqlite:2.3.1") api("com.opencsv:opencsv:5.8") - - } diff --git a/common/src/main/java/com/yogeshpaliyal/common/constants/AccountType.kt b/common/src/main/java/com/yogeshpaliyal/common/constants/AccountType.kt index 3251d0df..88c3351a 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/constants/AccountType.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/constants/AccountType.kt @@ -3,6 +3,7 @@ package com.yogeshpaliyal.common.constants annotation class AccountType { companion object { const val DEFAULT = 1 // used to store password and user information + @Deprecated("TOTP type removed, added TOTP support in Default") const val TOTP = 2 // used to store Time base - One time Password /* const val HOTP = 3 diff --git a/common/src/main/java/com/yogeshpaliyal/common/data/PasswordConfig.kt b/common/src/main/java/com/yogeshpaliyal/common/data/PasswordConfig.kt index 60c71056..01b2d42c 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/data/PasswordConfig.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/data/PasswordConfig.kt @@ -1,6 +1,7 @@ package com.yogeshpaliyal.common.data import androidx.annotation.Keep +import com.yogeshpaliyal.common.utils.PasswordGenerator import kotlinx.serialization.Serializable @Keep @@ -10,6 +11,7 @@ data class PasswordConfig( val includeUppercaseLetters: Boolean, val includeLowercaseLetters: Boolean, val includeSymbols: Boolean, + val listOfSymbols: List, val includeNumbers: Boolean, val includeBlankSpaces: Boolean, val password: String @@ -20,6 +22,7 @@ data class PasswordConfig( includeUppercaseLetters = true, includeLowercaseLetters = true, includeSymbols = true, + listOfSymbols = PasswordGenerator.totalSymbol, includeNumbers = true, includeBlankSpaces = true, password = "" diff --git a/common/src/main/java/com/yogeshpaliyal/common/utils/PasswordGenerator.kt b/common/src/main/java/com/yogeshpaliyal/common/utils/PasswordGenerator.kt index a2115390..45528220 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/utils/PasswordGenerator.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/utils/PasswordGenerator.kt @@ -11,6 +11,10 @@ class PasswordGenerator( private val SYMBOLS = 3 private val BLANKSPACES = 4 + companion object { + val totalSymbol = listOf('!', '@', '#', '$', '%', '&', '*', '+', '=', '-', '~', '?', '/', '_') + } + fun generatePassword(): String { var password = "" val list = ArrayList() @@ -37,7 +41,7 @@ class PasswordGenerator( UPPER_CASE -> password += ('A'..'Z').random().toString() LOWER_CASE -> password += ('a'..'z').random().toString() NUMBERS -> password += ('0'..'9').random().toString() - SYMBOLS -> password += listOf('!', '@', '#', '$', '%', '&', '*', '+', '=', '-', '~', '?', '/', '_').random().toString() + SYMBOLS -> password += passwordConfig.listOfSymbols.random().toString() BLANKSPACES -> password += (' ').toString() } } diff --git a/local.properties b/local.properties index d944e25d..8c524fc1 100644 --- a/local.properties +++ b/local.properties @@ -4,5 +4,5 @@ # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. -#Tue May 09 21:09:59 IST 2023 -sdk.dir=/Users/yogesh.choudhary3/Library/Android/sdk +#Tue Jun 04 13:10:19 IST 2024 +sdk.dir=C\:\\Users\\win10\\AppData\\Local\\Android\\Sdk diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 556b469b..a6a2de2e 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -39,7 +39,6 @@ kotlin { } val desktopMain by getting { dependencies { - } } val desktopTest by getting