mirror of
https://github.com/yogeshpaliyal/KeyPass.git
synced 2026-01-12 17:30:33 -06:00
Add password config in account detail (#861)
* feat: WIP password configs from AccountDetail * feat: spotless fixes
This commit is contained in:
committed by
GitHub
parent
59fc381a9d
commit
892f65fc10
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package com.yogeshpaliyal.keypass.ui.redux.states
|
||||||
|
|
||||||
|
class PasswordGeneratorState : ScreenState(false)
|
||||||
@@ -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())) {
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user