Add password config in account detail (#861)

* feat: WIP password configs from AccountDetail

* feat: spotless fixes
This commit is contained in:
Yogesh Choudhary Paliyal
2024-06-02 16:27:31 +05:30
committed by GitHub
parent 59fc381a9d
commit 892f65fc10
18 changed files with 126 additions and 65 deletions

View File

@@ -5,7 +5,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.common.utils.getUserSettings import com.yogeshpaliyal.common.utils.getUserSettings
import com.yogeshpaliyal.common.utils.setDefaultPasswordLength import com.yogeshpaliyal.common.utils.setDefaultPasswordLength
import com.yogeshpaliyal.keypass.ui.generate.ui.components.DEFAULT_PASSWORD_LENGTH
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@@ -24,7 +23,7 @@ class ChangeDefaultPasswordLengthViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
_viewState.update { _viewState.update {
val oldPasswordLength = val oldPasswordLength =
context.getUserSettings().defaultPasswordLength ?: DEFAULT_PASSWORD_LENGTH context.getUserSettings().passwordConfig.length
ChangeDefaultPasswordLengthState(length = oldPasswordLength) ChangeDefaultPasswordLengthState(length = oldPasswordLength)
} }
} }

View File

@@ -17,6 +17,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -29,12 +30,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.yogeshpaliyal.common.constants.ScannerType import com.yogeshpaliyal.common.constants.ScannerType
import com.yogeshpaliyal.common.data.AccountModel
import com.yogeshpaliyal.common.utils.TOTPHelper import com.yogeshpaliyal.common.utils.TOTPHelper
import com.yogeshpaliyal.keypass.ui.detail.components.BottomBar import com.yogeshpaliyal.keypass.ui.detail.components.BottomBar
import com.yogeshpaliyal.keypass.ui.detail.components.Fields import com.yogeshpaliyal.keypass.ui.detail.components.Fields
import com.yogeshpaliyal.keypass.ui.redux.actions.CopyToClipboard import com.yogeshpaliyal.keypass.ui.redux.actions.CopyToClipboard
import com.yogeshpaliyal.keypass.ui.redux.actions.GoBackAction import com.yogeshpaliyal.keypass.ui.redux.actions.GoBackAction
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
import com.yogeshpaliyal.keypass.ui.redux.states.PasswordGeneratorState
import org.reduxkotlin.compose.rememberDispatcher import org.reduxkotlin.compose.rememberDispatcher
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@@ -60,17 +62,11 @@ fun AccountDetailPage(
val showDialog = remember { mutableStateOf(false) } val showDialog = remember { mutableStateOf(false) }
// task value state // task value state
val (accountModel, setAccountModel) = remember { val accountModel = viewModel.accountModel.collectAsState().value
mutableStateOf(
AccountModel()
)
}
// Set initial object // Set initial object
LaunchedEffect(key1 = id) { LaunchedEffect(key1 = id) {
viewModel.loadAccount(id) { viewModel.loadAccount(id)
setAccountModel(it.copy())
}
} }
val goBack: () -> Unit = { val goBack: () -> Unit = {
@@ -80,7 +76,7 @@ fun AccountDetailPage(
val launcher = rememberLauncherForActivityResult(QRScanner()) { val launcher = rememberLauncherForActivityResult(QRScanner()) {
when (it.type) { when (it.type) {
ScannerType.Password -> { ScannerType.Password -> {
setAccountModel(accountModel.copy(password = it.scannedText)) viewModel.setAccountModel(accountModel.copy(password = it.scannedText))
} }
ScannerType.Secret -> { ScannerType.Secret -> {
it.scannedText ?: return@rememberLauncherForActivityResult it.scannedText ?: return@rememberLauncherForActivityResult
@@ -94,7 +90,7 @@ fun AccountDetailPage(
if (newAccountModel.username.isNullOrEmpty()) { if (newAccountModel.username.isNullOrEmpty()) {
newAccountModel = newAccountModel.copy(username = totp.issuer) newAccountModel = newAccountModel.copy(username = totp.issuer)
} }
setAccountModel(newAccountModel) viewModel.setAccountModel(newAccountModel)
} }
} }
} }
@@ -111,6 +107,9 @@ fun AccountDetailPage(
val qrCodeBitmap = viewModel.generateQrCode(accountModel) val qrCodeBitmap = viewModel.generateQrCode(accountModel)
generatedQrCodeBitmap.value = qrCodeBitmap generatedQrCodeBitmap.value = qrCodeBitmap
},
openPasswordConfiguration = {
dispatchAction(NavigationAction(PasswordGeneratorState()))
} }
) { ) {
viewModel.insertOrUpdate(accountModel, goBack) viewModel.insertOrUpdate(accountModel, goBack)
@@ -121,7 +120,7 @@ fun AccountDetailPage(
Fields( Fields(
accountModel = accountModel, accountModel = accountModel,
updateAccountModel = { newAccountModel -> updateAccountModel = { newAccountModel ->
setAccountModel(newAccountModel) viewModel.setAccountModel(newAccountModel)
}, },
copyToClipboardClicked = { value -> copyToClipboardClicked = { value ->
dispatchAction(CopyToClipboard(value)) dispatchAction(CopyToClipboard(value))

View File

@@ -3,8 +3,6 @@ package com.yogeshpaliyal.keypass.ui.detail
import android.app.Application import android.app.Application
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.gson.Gson import com.google.gson.Gson
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
@@ -16,6 +14,8 @@ import com.yogeshpaliyal.common.data.AccountModel
import com.yogeshpaliyal.common.worker.executeAutoBackup import com.yogeshpaliyal.common.worker.executeAutoBackup
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.* import java.util.*
@@ -33,15 +33,23 @@ class DetailViewModel @Inject constructor(
val appDb: com.yogeshpaliyal.common.AppDatabase val appDb: com.yogeshpaliyal.common.AppDatabase
) : AndroidViewModel(app) { ) : AndroidViewModel(app) {
private val _accountModel by lazy { MutableLiveData<AccountModel>() } private val _accountModel by lazy { MutableStateFlow<AccountModel>(AccountModel()) }
val accountModel: LiveData<AccountModel> = _accountModel val accountModel: StateFlow<AccountModel> = _accountModel
fun loadAccount(id: Long?, getAccount: (AccountModel) -> Unit) { fun loadAccount(id: Long?) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
getAccount(appDb.getDao().getAccount(id) ?: AccountModel()) if (id == null) {
_accountModel.emit(AccountModel())
} else {
_accountModel.emit(appDb.getDao().getAccount(id) ?: AccountModel())
}
} }
} }
fun setAccountModel(accountModel: AccountModel) {
_accountModel.value = accountModel
}
fun deleteAccount(accountModel: AccountModel, onExecCompleted: () -> Unit) { fun deleteAccount(accountModel: AccountModel, onExecCompleted: () -> Unit) {
viewModelScope.launch { viewModelScope.launch {
accountModel.let { accountModel.let {

View File

@@ -1,6 +1,7 @@
package com.yogeshpaliyal.keypass.ui.detail.components package com.yogeshpaliyal.keypass.ui.detail.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Password
import androidx.compose.material.icons.filled.QrCode import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material.icons.rounded.ArrowBackIosNew import androidx.compose.material.icons.rounded.ArrowBackIosNew
import androidx.compose.material.icons.rounded.Delete import androidx.compose.material.icons.rounded.Delete
@@ -24,6 +25,7 @@ fun BottomBar(
backPressed: () -> Unit, backPressed: () -> Unit,
onDeleteAccount: () -> Unit, onDeleteAccount: () -> Unit,
generateQrCodeClicked: () -> Unit, generateQrCodeClicked: () -> Unit,
openPasswordConfiguration: () -> Unit,
onSaveClicked: () -> Unit onSaveClicked: () -> Unit
) { ) {
val openDialog = remember { mutableStateOf(false) } val openDialog = remember { mutableStateOf(false) }
@@ -38,6 +40,17 @@ fun BottomBar(
) )
} }
IconButton(
modifier = Modifier.testTag("action_configure_password"),
onClick = { openPasswordConfiguration() }
) {
Icon(
painter = rememberVectorPainter(image = Icons.Default.Password),
contentDescription = "Open Password Configuration",
tint = MaterialTheme.colorScheme.onSurface
)
}
if (accountModel.id != null) { if (accountModel.id != null) {
IconButton( IconButton(
modifier = Modifier.testTag("action_delete"), modifier = Modifier.testTag("action_delete"),

View File

@@ -30,6 +30,7 @@ import com.yogeshpaliyal.common.utils.PasswordGenerator
import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.ui.commonComponents.KeyPassInputField import com.yogeshpaliyal.keypass.ui.commonComponents.KeyPassInputField
import com.yogeshpaliyal.keypass.ui.commonComponents.PasswordTrailingIcon import com.yogeshpaliyal.keypass.ui.commonComponents.PasswordTrailingIcon
import com.yogeshpaliyal.keypass.ui.nav.LocalUserSettings
@Composable @Composable
fun Fields( fun Fields(
@@ -39,6 +40,8 @@ fun Fields(
copyToClipboardClicked: (String) -> Unit, copyToClipboardClicked: (String) -> Unit,
scanClicked: (scannerType: Int) -> Unit scanClicked: (scannerType: Int) -> Unit
) { ) {
val passwordConfig = LocalUserSettings.current.passwordConfig
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@@ -90,7 +93,7 @@ fun Fields(
{ {
IconButton( IconButton(
onClick = { onClick = {
updateAccountModel(accountModel.copy(password = PasswordGenerator().generatePassword())) updateAccountModel(accountModel.copy(password = PasswordGenerator(passwordConfig).generatePassword()))
} }
) { ) {
Icon( Icon(

View File

@@ -14,7 +14,6 @@ class GeneratePasswordActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel.retrieveSavedPasswordLength(baseContext)
setContent { setContent {
Mdc3Theme { Mdc3Theme {
GeneratePasswordScreen(viewModel) GeneratePasswordScreen(viewModel)

View File

@@ -3,10 +3,10 @@ package com.yogeshpaliyal.keypass.ui.generate
import android.content.Context import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.yogeshpaliyal.common.data.PasswordConfig
import com.yogeshpaliyal.common.utils.PasswordGenerator import com.yogeshpaliyal.common.utils.PasswordGenerator
import com.yogeshpaliyal.common.utils.getUserSettings import com.yogeshpaliyal.common.utils.getUserSettings
import com.yogeshpaliyal.common.utils.setDefaultPasswordLength import com.yogeshpaliyal.common.utils.setPasswordConfig
import com.yogeshpaliyal.keypass.ui.generate.ui.components.DEFAULT_PASSWORD_LENGTH
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
@@ -23,18 +23,18 @@ class GeneratePasswordViewModel @Inject constructor(
@ApplicationContext context: Context @ApplicationContext context: Context
) : ViewModel() { ) : ViewModel() {
private val _viewState = MutableStateFlow(GeneratePasswordViewState.Initial) private val _viewState = MutableStateFlow(PasswordConfig.Initial)
val viewState = _viewState.asStateFlow() val viewState = _viewState.asStateFlow()
init { init {
observeState(context) observeState(context)
} }
fun retrieveSavedPasswordLength(context: Context) { fun retrieveSavedPasswordConfig(context: Context) {
viewModelScope.launch { viewModelScope.launch {
val savedPasswordLength = context.getUserSettings().defaultPasswordLength ?: DEFAULT_PASSWORD_LENGTH val passwordConfig = context.getUserSettings().passwordConfig
_viewState.update { _viewState.update {
_viewState.value.copy(length = savedPasswordLength) passwordConfig
} }
} }
} }
@@ -43,12 +43,7 @@ class GeneratePasswordViewModel @Inject constructor(
val currentViewState = _viewState.value val currentViewState = _viewState.value
val passwordGenerator = PasswordGenerator( val passwordGenerator = PasswordGenerator(
length = currentViewState.length.toInt(), currentViewState
includeUpperCaseLetters = currentViewState.includeUppercaseLetters,
includeLowerCaseLetters = currentViewState.includeLowercaseLetters,
includeSymbols = currentViewState.includeSymbols,
includeNumbers = currentViewState.includeNumbers,
includeBlankSpaces = currentViewState.includeBlankSpaces
) )
_viewState.update { _viewState.update {
@@ -100,7 +95,7 @@ class GeneratePasswordViewModel @Inject constructor(
_viewState _viewState
.debounce(400) .debounce(400)
.collectLatest { state -> .collectLatest { state ->
context.setDefaultPasswordLength(state.length) context.setPasswordConfig(state)
} }
} }
} }

View File

@@ -1,7 +1,9 @@
package com.yogeshpaliyal.keypass.ui.generate package com.yogeshpaliyal.keypass.ui.generate
import androidx.annotation.Keep
import com.yogeshpaliyal.keypass.ui.generate.ui.components.DEFAULT_PASSWORD_LENGTH import com.yogeshpaliyal.keypass.ui.generate.ui.components.DEFAULT_PASSWORD_LENGTH
@Keep
data class GeneratePasswordViewState( data class GeneratePasswordViewState(
val length: Float, val length: Float,
val includeUppercaseLetters: Boolean, val includeUppercaseLetters: Boolean,

View File

@@ -23,13 +23,13 @@ import androidx.compose.ui.Modifier
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.keypass.ui.generate.GeneratePasswordViewState import com.yogeshpaliyal.common.data.PasswordConfig
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
@Composable @Composable
fun GeneratePasswordContent( fun GeneratePasswordContent(
viewState: GeneratePasswordViewState, viewState: PasswordConfig,
onCopyPasswordClick: () -> Unit, onCopyPasswordClick: () -> Unit,
onGeneratePasswordClick: () -> Unit, onGeneratePasswordClick: () -> Unit,
onPasswordLengthChange: (Float) -> Unit, onPasswordLengthChange: (Float) -> Unit,
@@ -77,7 +77,7 @@ private fun GeneratePasswordFab(onGeneratePasswordClick: () -> Unit) {
@Composable @Composable
private fun FormInputCard( private fun FormInputCard(
viewState: GeneratePasswordViewState, viewState: PasswordConfig,
onCopyPasswordClick: () -> Unit, onCopyPasswordClick: () -> Unit,
onPasswordLengthChange: (Float) -> Unit, onPasswordLengthChange: (Float) -> Unit,
onUppercaseCheckedChange: (Boolean) -> Unit, onUppercaseCheckedChange: (Boolean) -> Unit,
@@ -211,7 +211,7 @@ private fun BlankSpaceInput(
@Composable @Composable
@Suppress("UnusedPrivateMember", "MagicNumber") @Suppress("UnusedPrivateMember", "MagicNumber")
private fun GeneratePasswordContentPreview() { private fun GeneratePasswordContentPreview() {
val viewState = GeneratePasswordViewState.Initial val viewState = PasswordConfig.Initial
Mdc3Theme { Mdc3Theme {
GeneratePasswordContent( GeneratePasswordContent(

View File

@@ -4,22 +4,22 @@ import android.content.Context
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.ui.generate.GeneratePasswordViewModel import com.yogeshpaliyal.keypass.ui.generate.GeneratePasswordViewModel
import com.yogeshpaliyal.keypass.ui.generate.ui.utils.copyTextToClipboard import com.yogeshpaliyal.keypass.ui.generate.ui.utils.copyTextToClipboard
@Composable @Composable
fun GeneratePasswordScreen(viewModel: GeneratePasswordViewModel) { fun GeneratePasswordScreen(viewModel: GeneratePasswordViewModel = hiltViewModel()) {
val context = LocalContext.current val context = LocalContext.current
// replace collectAsState() with collectAsStateWithLifecycle() when compose version and kotlin version are bumped up. val viewState by viewModel.viewState.collectAsStateWithLifecycle()
val viewState by viewModel.viewState.collectAsState()
LaunchedEffect(key1 = Unit) { LaunchedEffect(Unit) {
viewModel.generatePassword() viewModel.retrieveSavedPasswordConfig(context)
} }
GeneratePasswordContent( GeneratePasswordContent(

View File

@@ -31,6 +31,7 @@ import com.yogeshpaliyal.keypass.ui.backupsImport.BackupImporter
import com.yogeshpaliyal.keypass.ui.changeDefaultPasswordLength.ChangeDefaultPasswordLengthScreen import com.yogeshpaliyal.keypass.ui.changeDefaultPasswordLength.ChangeDefaultPasswordLengthScreen
import com.yogeshpaliyal.keypass.ui.changePassword.ChangePassword import com.yogeshpaliyal.keypass.ui.changePassword.ChangePassword
import com.yogeshpaliyal.keypass.ui.detail.AccountDetailPage import com.yogeshpaliyal.keypass.ui.detail.AccountDetailPage
import com.yogeshpaliyal.keypass.ui.generate.ui.GeneratePasswordScreen
import com.yogeshpaliyal.keypass.ui.home.Homepage import com.yogeshpaliyal.keypass.ui.home.Homepage
import com.yogeshpaliyal.keypass.ui.nav.components.DashboardBottomSheet import com.yogeshpaliyal.keypass.ui.nav.components.DashboardBottomSheet
import com.yogeshpaliyal.keypass.ui.nav.components.KeyPassBottomBar import com.yogeshpaliyal.keypass.ui.nav.components.KeyPassBottomBar
@@ -46,6 +47,7 @@ import com.yogeshpaliyal.keypass.ui.redux.states.ChangeAppPasswordState
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState
import com.yogeshpaliyal.keypass.ui.redux.states.HomeState import com.yogeshpaliyal.keypass.ui.redux.states.HomeState
import com.yogeshpaliyal.keypass.ui.redux.states.KeyPassState import com.yogeshpaliyal.keypass.ui.redux.states.KeyPassState
import com.yogeshpaliyal.keypass.ui.redux.states.PasswordGeneratorState
import com.yogeshpaliyal.keypass.ui.redux.states.ScreenState import com.yogeshpaliyal.keypass.ui.redux.states.ScreenState
import com.yogeshpaliyal.keypass.ui.redux.states.SettingsState import com.yogeshpaliyal.keypass.ui.redux.states.SettingsState
import com.yogeshpaliyal.keypass.ui.settings.MySettingCompose import com.yogeshpaliyal.keypass.ui.settings.MySettingCompose
@@ -169,6 +171,7 @@ fun CurrentPage() {
is BackupImporterState -> BackupImporter(state = it) is BackupImporterState -> BackupImporter(state = it)
is AboutState -> AboutScreen() is AboutState -> AboutScreen()
is PasswordGeneratorState -> GeneratePasswordScreen()
} }
} }
} }

View File

@@ -0,0 +1,3 @@
package com.yogeshpaliyal.keypass.ui.redux.states
class PasswordGeneratorState : ScreenState(false)

View File

@@ -64,7 +64,7 @@ fun MySettingCompose() {
// Retrieving saved password length // Retrieving saved password length
var savedPasswordLength by remember { mutableStateOf(DEFAULT_PASSWORD_LENGTH) } var savedPasswordLength by remember { mutableStateOf(DEFAULT_PASSWORD_LENGTH) }
LaunchedEffect(key1 = Unit) { LaunchedEffect(key1 = Unit) {
userSettings.defaultPasswordLength.let { value -> savedPasswordLength = value } userSettings.passwordConfig.length.let { value -> savedPasswordLength = value }
} }
Column(modifier = Modifier.fillMaxSize(1f).verticalScroll(rememberScrollState())) { Column(modifier = Modifier.fillMaxSize(1f).verticalScroll(rememberScrollState())) {

View File

@@ -28,7 +28,7 @@ class SharedPreferenceUtilsTest {
@Test @Test
fun getKeyPassPasswordLength_test() = runBlocking { fun getKeyPassPasswordLength_test() = runBlocking {
val result = context.getUserSettings().defaultPasswordLength val result = context.getUserSettings().passwordConfig.length
assertEquals(DEFAULT_PASSWORD_LENGTH, result) assertEquals(DEFAULT_PASSWORD_LENGTH, result)
} }
@@ -36,7 +36,7 @@ class SharedPreferenceUtilsTest {
fun setKeyPassPasswordLength_test() = runBlocking { fun setKeyPassPasswordLength_test() = runBlocking {
val expectedLength = 8f val expectedLength = 8f
context.setDefaultPasswordLength(expectedLength) context.setDefaultPasswordLength(expectedLength)
val result = context.getUserSettings().defaultPasswordLength val result = context.getUserSettings().passwordConfig.length
assertEquals(expectedLength, result) assertEquals(expectedLength, result)
} }

View File

@@ -0,0 +1,28 @@
package com.yogeshpaliyal.common.data
import androidx.annotation.Keep
import kotlinx.serialization.Serializable
@Keep
@Serializable
data class PasswordConfig(
val length: Float,
val includeUppercaseLetters: Boolean,
val includeLowercaseLetters: Boolean,
val includeSymbols: Boolean,
val includeNumbers: Boolean,
val includeBlankSpaces: Boolean,
val password: String
) {
companion object {
val Initial = PasswordConfig(
length = DEFAULT_PASSWORD_LENGTH,
includeUppercaseLetters = true,
includeLowercaseLetters = true,
includeSymbols = true,
includeNumbers = true,
includeBlankSpaces = true,
password = ""
)
}
}

View File

@@ -10,6 +10,7 @@ const val DEFAULT_PASSWORD_LENGTH = 10f
data class UserSettings( data class UserSettings(
val keyPassPassword: String? = null, val keyPassPassword: String? = null,
val dbPassword: String? = null, val dbPassword: String? = null,
@Deprecated("Use passwordConfig instead")
val defaultPasswordLength: Float = DEFAULT_PASSWORD_LENGTH, val defaultPasswordLength: Float = DEFAULT_PASSWORD_LENGTH,
val backupKey: String? = null, val backupKey: String? = null,
val isBiometricEnable: Boolean = false, val isBiometricEnable: Boolean = false,
@@ -18,7 +19,8 @@ data class UserSettings(
val autoBackupEnable: Boolean = false, val autoBackupEnable: Boolean = false,
val overrideAutoBackup: Boolean = false, val overrideAutoBackup: Boolean = false,
val lastAppVersion: Int? = null, val lastAppVersion: Int? = null,
val currentAppVersion: Int? = null val currentAppVersion: Int? = null,
val passwordConfig: PasswordConfig = PasswordConfig.Initial
) { ) {
fun isKeyPresent() = backupKey != null fun isKeyPresent() = backupKey != null
} }

View File

@@ -1,15 +1,11 @@
package com.yogeshpaliyal.common.utils package com.yogeshpaliyal.common.utils
class PasswordGenerator( import com.yogeshpaliyal.common.data.PasswordConfig
private var length: Int,
private var includeUpperCaseLetters: Boolean,
private var includeLowerCaseLetters: Boolean,
private var includeSymbols: Boolean,
private var includeNumbers: Boolean,
private var includeBlankSpaces: Boolean
) {
constructor() : this(10, true, true, true, true, true) private val UPPER_CASE = 0 class PasswordGenerator(
val passwordConfig: PasswordConfig
) {
private val UPPER_CASE = 0
private val LOWER_CASE = 1 private val LOWER_CASE = 1
private val NUMBERS = 2 private val NUMBERS = 2
private val SYMBOLS = 3 private val SYMBOLS = 3
@@ -18,24 +14,24 @@ class PasswordGenerator(
fun generatePassword(): String { fun generatePassword(): String {
var password = "" var password = ""
val list = ArrayList<Int>() val list = ArrayList<Int>()
if (includeUpperCaseLetters) { if (passwordConfig.includeUppercaseLetters) {
list.add(UPPER_CASE) list.add(UPPER_CASE)
} }
if (includeLowerCaseLetters) { if (passwordConfig.includeLowercaseLetters) {
list.add(LOWER_CASE) list.add(LOWER_CASE)
} }
if (includeNumbers) { if (passwordConfig.includeNumbers) {
list.add(NUMBERS) list.add(NUMBERS)
} }
if (includeSymbols) { if (passwordConfig.includeSymbols) {
list.add(SYMBOLS) list.add(SYMBOLS)
} }
if (includeBlankSpaces) { if (passwordConfig.includeBlankSpaces) {
list.add(BLANKSPACES) list.add(BLANKSPACES)
} }
for (i in 1..length) { for (i in 1..passwordConfig.length.toInt()) {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
when (list.random()) { when (list.random()) {
UPPER_CASE -> password += ('A'..'Z').random().toString() UPPER_CASE -> password += ('A'..'Z').random().toString()

View File

@@ -9,6 +9,7 @@ import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore
import com.yogeshpaliyal.common.data.DEFAULT_PASSWORD_LENGTH import com.yogeshpaliyal.common.data.DEFAULT_PASSWORD_LENGTH
import com.yogeshpaliyal.common.data.PasswordConfig
import com.yogeshpaliyal.common.data.UserSettings import com.yogeshpaliyal.common.data.UserSettings
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -32,6 +33,12 @@ private fun Context.getUserSettingsDataStore(): DataStore<UserSettings> {
return res return res
} }
suspend fun Context.setPasswordConfig(passwordConfig: PasswordConfig) {
getUserSettingsDataStore().updateData {
it.copy(passwordConfig = passwordConfig)
}
}
suspend fun Context.getUserSettings(): UserSettings { suspend fun Context.getUserSettings(): UserSettings {
return getUserSettingsDataStore().data.firstOrNull() ?: UserSettings() return getUserSettingsDataStore().data.firstOrNull() ?: UserSettings()
} }
@@ -52,7 +59,7 @@ suspend fun Context.setKeyPassPassword(password: String?) {
suspend fun Context.setDefaultPasswordLength(password: Float) { suspend fun Context.setDefaultPasswordLength(password: Float) {
getUserSettingsDataStore().updateData { getUserSettingsDataStore().updateData {
it.copy(defaultPasswordLength = password) it.copy(passwordConfig = it.passwordConfig.copy(length = password))
} }
} }
@@ -159,6 +166,10 @@ suspend fun Context.migrateOldDataToNewerDataStore() {
userSettings = userSettings.copy(defaultPasswordLength = olderData[KEYPASS_PASSWORD_LENGTH] ?: DEFAULT_PASSWORD_LENGTH) userSettings = userSettings.copy(defaultPasswordLength = olderData[KEYPASS_PASSWORD_LENGTH] ?: DEFAULT_PASSWORD_LENGTH)
} }
if (userSettings.defaultPasswordLength != DEFAULT_PASSWORD_LENGTH) {
userSettings = userSettings.copy(passwordConfig = userSettings.passwordConfig.copy(length = userSettings.defaultPasswordLength))
}
if (olderData.contains(BACKUP_DIRECTORY)) { if (olderData.contains(BACKUP_DIRECTORY)) {
userSettings = userSettings.copy(backupDirectory = olderData[BACKUP_DIRECTORY]) userSettings = userSettings.copy(backupDirectory = olderData[BACKUP_DIRECTORY])
} }