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
This commit is contained in:
Kapil Baser
2024-06-05 22:53:14 +05:30
committed by GitHub
parent 88515323bb
commit 993ab75b40
12 changed files with 118 additions and 40 deletions

View File

@@ -41,12 +41,11 @@ android {
applicationIdSuffix = ".staging" applicationIdSuffix = ".staging"
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
} }
} }
productFlavors { productFlavors {
create("free") { create("free") {
isDefault=true isDefault = true
} }
create("pro") { create("pro") {
applicationIdSuffix = ".pro" applicationIdSuffix = ".pro"
@@ -61,7 +60,7 @@ android {
jvmTarget = "17" jvmTarget = "17"
freeCompilerArgs = listOf( freeCompilerArgs = listOf(
"-Xopt-in=androidx.compose.material3.ExperimentalMaterial3Api" "-Xopt-in=androidx.compose.material3.ExperimentalMaterial3Api",
) )
} }
buildFeatures { buildFeatures {
@@ -71,8 +70,6 @@ android {
flavorDimensions("default") flavorDimensions("default")
packagingOptions { packagingOptions {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
@@ -89,11 +86,10 @@ android {
} }
} }
lint{ lint {
disable += "MissingTranslation" disable += "MissingTranslation"
abortOnError = true abortOnError = true
} }
} }
ruler { ruler {
@@ -103,10 +99,9 @@ ruler {
sdkVersion.set(27) sdkVersion.set(27)
} }
dependencies { dependencies {
//api(project(":shared")) // api(project(":shared"))
api(project(":common")) api(project(":common"))
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")
@@ -151,12 +146,10 @@ dependencies {
kapt("androidx.hilt:hilt-compiler:1.2.0") kapt("androidx.hilt:hilt-compiler:1.2.0")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0") implementation("androidx.hilt:hilt-navigation-compose:1.2.0")
// zxing library // zxing library
// implementation "com.googl.ezxing:android-core:3.4.1" // implementation "com.googl.ezxing:android-core:3.4.1"
implementation("com.journeyapps:zxing-android-embedded:4.3.0") implementation("com.journeyapps:zxing-android-embedded:4.3.0")
// For instrumented tests. // For instrumented tests.
androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1") androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1")
// ...with Kotlin. // ...with Kotlin.
@@ -171,6 +164,4 @@ dependencies {
implementation("me.saket.cascade:cascade-compose:2.2.0") implementation("me.saket.cascade:cascade-compose:2.2.0")
implementation("androidx.biometric:biometric:1.1.0") implementation("androidx.biometric:biometric:1.1.0")
} }

View File

@@ -26,7 +26,7 @@ fun KeyPassInputField(
leadingIcon: @Composable (() -> Unit)? = null, leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null, trailingIcon: @Composable (() -> Unit)? = null,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
copyToClipboardClicked: ((String) -> Unit) ? = null copyToClipboardClicked: ((String) -> Unit)? = null
) { ) {
OutlinedTextField( OutlinedTextField(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),

View File

@@ -89,20 +89,22 @@ fun Fields(
}, },
leadingIcon = if (accountModel.id != null) { leadingIcon = if (accountModel.id != null) {
null null
} else ( } else {
{ (
IconButton( {
onClick = { IconButton(
updateAccountModel(accountModel.copy(password = PasswordGenerator(passwordConfig).generatePassword())) 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, visualTransformation = visualTransformation,
copyToClipboardClicked = copyToClipboardClicked copyToClipboardClicked = copyToClipboardClicked
) )

View File

@@ -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) { fun onPasswordLengthSliderChange(value: Float) {
_viewState.update { _viewState.update {
it.copy(length = value) it.copy(length = value)

View File

@@ -3,6 +3,8 @@ package com.yogeshpaliyal.keypass.ui.generate.ui
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@@ -14,16 +16,19 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.accompanist.themeadapter.material3.Mdc3Theme import com.google.accompanist.themeadapter.material3.Mdc3Theme
import com.yogeshpaliyal.common.data.PasswordConfig 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.CheckboxWithLabel
import com.yogeshpaliyal.keypass.ui.generate.ui.components.PasswordLengthInput import com.yogeshpaliyal.keypass.ui.generate.ui.components.PasswordLengthInput
@@ -37,6 +42,7 @@ fun GeneratePasswordContent(
onLowercaseCheckedChange: (Boolean) -> Unit, onLowercaseCheckedChange: (Boolean) -> Unit,
onNumbersCheckedChange: (Boolean) -> Unit, onNumbersCheckedChange: (Boolean) -> Unit,
onSymbolsCheckedChange: (Boolean) -> Unit, onSymbolsCheckedChange: (Boolean) -> Unit,
selectSymbolForPassword: (Char) -> Unit = {},
onBlankSpacesCheckedChange: (Boolean) -> Unit onBlankSpacesCheckedChange: (Boolean) -> Unit
) { ) {
Scaffold( Scaffold(
@@ -56,6 +62,7 @@ fun GeneratePasswordContent(
onLowercaseCheckedChange = onLowercaseCheckedChange, onLowercaseCheckedChange = onLowercaseCheckedChange,
onNumbersCheckedChange = onNumbersCheckedChange, onNumbersCheckedChange = onNumbersCheckedChange,
onSymbolsCheckedChange = onSymbolsCheckedChange, onSymbolsCheckedChange = onSymbolsCheckedChange,
selectSymbolForPassword = selectSymbolForPassword,
onBlankSpacesCheckedChange = onBlankSpacesCheckedChange onBlankSpacesCheckedChange = onBlankSpacesCheckedChange
) )
} }
@@ -84,6 +91,7 @@ private fun FormInputCard(
onLowercaseCheckedChange: (Boolean) -> Unit, onLowercaseCheckedChange: (Boolean) -> Unit,
onNumbersCheckedChange: (Boolean) -> Unit, onNumbersCheckedChange: (Boolean) -> Unit,
onSymbolsCheckedChange: (Boolean) -> Unit, onSymbolsCheckedChange: (Boolean) -> Unit,
selectSymbolForPassword: (Char) -> Unit = {},
onBlankSpacesCheckedChange: (Boolean) -> Unit onBlankSpacesCheckedChange: (Boolean) -> Unit
) { ) {
OutlinedCard( OutlinedCard(
@@ -107,7 +115,7 @@ private fun FormInputCard(
NumberInput(viewState.includeNumbers, onNumbersCheckedChange) NumberInput(viewState.includeNumbers, onNumbersCheckedChange)
SymbolInput(viewState.includeSymbols, onSymbolsCheckedChange) SymbolInput(viewState.includeSymbols, onSymbolsCheckedChange, selectSymbolForPassword, viewState.listOfSymbols)
BlankSpaceInput(viewState.includeBlankSpaces, onBlankSpacesCheckedChange) BlankSpaceInput(viewState.includeBlankSpaces, onBlankSpacesCheckedChange)
} }
@@ -176,16 +184,55 @@ private fun NumberInput(
) )
} }
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun SymbolInput( private fun SymbolInput(
includeSymbols: Boolean, includeSymbols: Boolean,
onSymbolsCheckedChange: (Boolean) -> Unit onSymbolsCheckedChange: (Boolean) -> Unit,
selectSymbolForPassword: (Char) -> Unit = {},
selectedSymbols: List<Char>
) { ) {
CheckboxWithLabel( Column {
label = "Symbols", CheckboxWithLabel(
checked = includeSymbols, label = "Symbols",
onCheckedChange = onSymbolsCheckedChange 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 @Composable

View File

@@ -31,6 +31,7 @@ fun GeneratePasswordScreen(viewModel: GeneratePasswordViewModel = hiltViewModel(
onLowercaseCheckedChange = viewModel::onLowercaseCheckedChange, onLowercaseCheckedChange = viewModel::onLowercaseCheckedChange,
onNumbersCheckedChange = viewModel::onNumbersCheckedChange, onNumbersCheckedChange = viewModel::onNumbersCheckedChange,
onSymbolsCheckedChange = viewModel::onSymbolsCheckedChange, onSymbolsCheckedChange = viewModel::onSymbolsCheckedChange,
selectSymbolForPassword = viewModel::selectSymbolForPassword,
onBlankSpacesCheckedChange = viewModel::onBlankSpacesCheckedChange onBlankSpacesCheckedChange = viewModel::onBlankSpacesCheckedChange
) )
} }

View File

@@ -41,7 +41,7 @@ dependencies {
// apache common codec // apache common codec
implementation("commons-codec:commons-codec:1.17.0") implementation("commons-codec:commons-codec:1.17.0")
//Androidx Security // Androidx Security
implementation("androidx.security:security-crypto:1.1.0-alpha06") implementation("androidx.security:security-crypto:1.1.0-alpha06")
api("androidx.documentfile:documentfile:1.0.1") api("androidx.documentfile:documentfile:1.0.1")
@@ -76,6 +76,4 @@ dependencies {
implementation("androidx.sqlite:sqlite:2.3.1") implementation("androidx.sqlite:sqlite:2.3.1")
api("com.opencsv:opencsv:5.8") api("com.opencsv:opencsv:5.8")
} }

View File

@@ -3,6 +3,7 @@ package com.yogeshpaliyal.common.constants
annotation class AccountType { annotation class AccountType {
companion object { companion object {
const val DEFAULT = 1 // used to store password and user information const val DEFAULT = 1 // used to store password and user information
@Deprecated("TOTP type removed, added TOTP support in Default") @Deprecated("TOTP type removed, added TOTP support in Default")
const val TOTP = 2 // used to store Time base - One time Password const val TOTP = 2 // used to store Time base - One time Password
/* const val HOTP = 3 /* const val HOTP = 3

View File

@@ -1,6 +1,7 @@
package com.yogeshpaliyal.common.data package com.yogeshpaliyal.common.data
import androidx.annotation.Keep import androidx.annotation.Keep
import com.yogeshpaliyal.common.utils.PasswordGenerator
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Keep @Keep
@@ -10,6 +11,7 @@ data class PasswordConfig(
val includeUppercaseLetters: Boolean, val includeUppercaseLetters: Boolean,
val includeLowercaseLetters: Boolean, val includeLowercaseLetters: Boolean,
val includeSymbols: Boolean, val includeSymbols: Boolean,
val listOfSymbols: List<Char>,
val includeNumbers: Boolean, val includeNumbers: Boolean,
val includeBlankSpaces: Boolean, val includeBlankSpaces: Boolean,
val password: String val password: String
@@ -20,6 +22,7 @@ data class PasswordConfig(
includeUppercaseLetters = true, includeUppercaseLetters = true,
includeLowercaseLetters = true, includeLowercaseLetters = true,
includeSymbols = true, includeSymbols = true,
listOfSymbols = PasswordGenerator.totalSymbol,
includeNumbers = true, includeNumbers = true,
includeBlankSpaces = true, includeBlankSpaces = true,
password = "" password = ""

View File

@@ -11,6 +11,10 @@ class PasswordGenerator(
private val SYMBOLS = 3 private val SYMBOLS = 3
private val BLANKSPACES = 4 private val BLANKSPACES = 4
companion object {
val totalSymbol = listOf('!', '@', '#', '$', '%', '&', '*', '+', '=', '-', '~', '?', '/', '_')
}
fun generatePassword(): String { fun generatePassword(): String {
var password = "" var password = ""
val list = ArrayList<Int>() val list = ArrayList<Int>()
@@ -37,7 +41,7 @@ class PasswordGenerator(
UPPER_CASE -> password += ('A'..'Z').random().toString() UPPER_CASE -> password += ('A'..'Z').random().toString()
LOWER_CASE -> password += ('a'..'z').random().toString() LOWER_CASE -> password += ('a'..'z').random().toString()
NUMBERS -> password += ('0'..'9').random().toString() NUMBERS -> password += ('0'..'9').random().toString()
SYMBOLS -> password += listOf('!', '@', '#', '$', '%', '&', '*', '+', '=', '-', '~', '?', '/', '_').random().toString() SYMBOLS -> password += passwordConfig.listOfSymbols.random().toString()
BLANKSPACES -> password += (' ').toString() BLANKSPACES -> password += (' ').toString()
} }
} }

View File

@@ -4,5 +4,5 @@
# Location of the SDK. This is only used by Gradle. # Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the # For customization when using a Version Control System, please read the
# header note. # header note.
#Tue May 09 21:09:59 IST 2023 #Tue Jun 04 13:10:19 IST 2024
sdk.dir=/Users/yogesh.choudhary3/Library/Android/sdk sdk.dir=C\:\\Users\\win10\\AppData\\Local\\Android\\Sdk

View File

@@ -39,7 +39,6 @@ kotlin {
} }
val desktopMain by getting { val desktopMain by getting {
dependencies { dependencies {
} }
} }
val desktopTest by getting val desktopTest by getting