mirror of
https://github.com/yogeshpaliyal/KeyPass.git
synced 2026-01-07 00:49:46 -06:00
Add Remember pass key (#1046)
* feat: validate pass key * feat: validate passkeys * feat: improve padding in hint screen * feat: minor changes * feat: cleanup
This commit is contained in:
committed by
GitHub
parent
2b9452f8dd
commit
5be19d1600
@@ -53,12 +53,11 @@ fun AuthScreen(state: AuthState) {
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = userSettings.keyPassPassword, block = {
|
||||
if (userSettings.isDefault) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val mPassword = userSettings.keyPassPassword
|
||||
if (mPassword == null) {
|
||||
dispatchAction(NavigationAction(AuthState.CreatePassword, true))
|
||||
} else {
|
||||
dispatchAction(NavigationAction(AuthState.Login, true))
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.yogeshpaliyal.common.utils.clearBackupKey
|
||||
import com.yogeshpaliyal.common.utils.setAutoBackupEnabled
|
||||
import com.yogeshpaliyal.common.utils.setBackupDirectory
|
||||
import com.yogeshpaliyal.common.utils.setOverrideAutoBackup
|
||||
import com.yogeshpaliyal.common.utils.updateLastKeyPhraseEnterTime
|
||||
import com.yogeshpaliyal.keypass.ui.backup.components.BackSettingOptions
|
||||
import com.yogeshpaliyal.keypass.ui.backup.components.BackupDialogs
|
||||
import com.yogeshpaliyal.keypass.ui.commonComponents.DefaultBottomAppBar
|
||||
@@ -80,6 +81,7 @@ fun BackupScreen(state: BackupScreenState) {
|
||||
if (it.not()) {
|
||||
context.clearBackupKey()
|
||||
}
|
||||
context.updateLastKeyPhraseEnterTime(System.currentTimeMillis())
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -22,10 +22,14 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.yogeshpaliyal.keypass.ui.home.components.AccountsList
|
||||
import com.yogeshpaliyal.keypass.ui.home.components.SearchBar
|
||||
import com.yogeshpaliyal.keypass.ui.nav.LocalUserSettings
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.StateUpdateAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.HomeState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ValidateKeyPhrase
|
||||
import org.reduxkotlin.compose.rememberDispatcher
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/*
|
||||
* @author Yogesh Paliyal
|
||||
@@ -44,6 +48,8 @@ fun Homepage(
|
||||
val sortField = homeState.sortField
|
||||
val sortAscendingOrder = homeState.sortAscending
|
||||
|
||||
val userSettings = LocalUserSettings.current
|
||||
|
||||
val listOfAccountsLiveData by mViewModel.mediator.observeAsState()
|
||||
|
||||
val dispatchAction = rememberDispatcher()
|
||||
@@ -52,6 +58,20 @@ fun Homepage(
|
||||
mViewModel.queryUpdated(keyword, tag, sortField, sortAscendingOrder)
|
||||
})
|
||||
|
||||
LaunchedEffect(Unit, {
|
||||
if (userSettings.backupKey == null) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val lastPasswordLoginTime = userSettings.lastKeyPhraseEnterTime ?: -1
|
||||
val diff = currentTime - lastPasswordLoginTime
|
||||
val diffInDays = TimeUnit.MILLISECONDS.toDays(diff)
|
||||
if (diffInDays >= 7) {
|
||||
// Show the modal
|
||||
dispatchAction(UpdateDialogState(dialogState = ValidateKeyPhrase))
|
||||
}
|
||||
})
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
SearchBar(keyword, {
|
||||
dispatchAction(StateUpdateAction(homeState.copy(keyword = it)))
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.yogeshpaliyal.keypass.ui.home.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.yogeshpaliyal.common.utils.updateLastKeyPhraseEnterTime
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.ui.nav.LocalUserSettings
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.Action
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogState
|
||||
import kotlinx.coroutines.launch
|
||||
import org.reduxkotlin.compose.rememberTypedDispatcher
|
||||
|
||||
@Composable
|
||||
fun ValidateKeyPhraseDialog() {
|
||||
val (keyphrase, setKeyPhrase) = remember {
|
||||
mutableStateOf("")
|
||||
}
|
||||
|
||||
val dispatchAction = rememberTypedDispatcher<Action>()
|
||||
|
||||
val userSettings = LocalUserSettings.current
|
||||
val context = LocalContext.current
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val hideDialog: () -> Unit = {
|
||||
dispatchAction(UpdateDialogState(null))
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = {
|
||||
hideDialog()
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.validate_keyphrase))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (keyphrase.isEmpty()) {
|
||||
dispatchAction(ToastAction(R.string.alert_blank_keyphrase))
|
||||
return@TextButton
|
||||
}
|
||||
|
||||
if (userSettings.backupKey != keyphrase) {
|
||||
dispatchAction(ToastAction(R.string.mismatch_keyphrase))
|
||||
return@TextButton
|
||||
}
|
||||
|
||||
coroutineScope.launch {
|
||||
context.updateLastKeyPhraseEnterTime(System.currentTimeMillis())
|
||||
hideDialog()
|
||||
dispatchAction(ToastAction(R.string.valid_keyphrase))
|
||||
}
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.validate))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = hideDialog) {
|
||||
Text(text = stringResource(id = R.string.cancel))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Column(modifier = Modifier.fillMaxWidth(1f)) {
|
||||
Text(text = stringResource(id = R.string.keyphrase_validate_info))
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth(1f),
|
||||
value = keyphrase,
|
||||
onValueChange = setKeyPhrase,
|
||||
placeholder = {
|
||||
Text(text = stringResource(id = R.string.enter_keyphrase))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -18,8 +18,6 @@ import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.LifecycleEventEffect
|
||||
import com.yogeshpaliyal.common.data.UserSettings
|
||||
import com.yogeshpaliyal.common.utils.getUserSettings
|
||||
import com.yogeshpaliyal.common.utils.getUserSettingsFlow
|
||||
@@ -35,12 +33,12 @@ import com.yogeshpaliyal.keypass.ui.changePassword.ChangePassword
|
||||
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.components.ValidateKeyPhraseDialog
|
||||
import com.yogeshpaliyal.keypass.ui.nav.components.DashboardBottomSheet
|
||||
import com.yogeshpaliyal.keypass.ui.nav.components.KeyPassBottomBar
|
||||
import com.yogeshpaliyal.keypass.ui.passwordHint.PasswordHintScreen
|
||||
import com.yogeshpaliyal.keypass.ui.redux.KeyPassRedux
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.GoBackAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateContextAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.AboutState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.AccountDetailState
|
||||
@@ -53,8 +51,8 @@ import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthStat
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.HomeState
|
||||
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.SettingsState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ValidateKeyPhrase
|
||||
import com.yogeshpaliyal.keypass.ui.settings.MySettingCompose
|
||||
import com.yogeshpaliyal.keypass.ui.style.KeyPassTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -77,7 +75,7 @@ class DashboardComposeActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
setContent {
|
||||
val localUserSettings by getUserSettingsFlow().collectAsState(initial = UserSettings(true))
|
||||
val localUserSettings by getUserSettingsFlow().collectAsState(initial = UserSettings())
|
||||
|
||||
CompositionLocalProvider(LocalUserSettings provides localUserSettings) {
|
||||
KeyPassTheme {
|
||||
@@ -117,9 +115,9 @@ fun Dashboard() {
|
||||
}
|
||||
|
||||
// Call this like any other SideEffect in your composable
|
||||
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
|
||||
dispatch(NavigationAction(AuthState.Login))
|
||||
}
|
||||
// LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
|
||||
// dispatch(NavigationAction(AuthState.Login))
|
||||
// }
|
||||
|
||||
LaunchedEffect(key1 = systemBackPress, block = {
|
||||
if (systemBackPress) {
|
||||
@@ -148,9 +146,11 @@ fun Dashboard() {
|
||||
|
||||
@Composable
|
||||
fun CurrentPage() {
|
||||
val currentScreen by selectState<KeyPassState, ScreenState> { this.currentScreen }
|
||||
val currentScreen by selectState<KeyPassState, KeyPassState> { this }
|
||||
|
||||
currentScreen.let {
|
||||
// val currentDialog by selectState<KeyPassState, DialogState?> { this.dialog }
|
||||
|
||||
currentScreen.currentScreen.let {
|
||||
when (it) {
|
||||
is HomeState -> {
|
||||
Homepage(homeState = it)
|
||||
@@ -186,4 +186,12 @@ fun CurrentPage() {
|
||||
is ChangeAppHintState -> PasswordHintScreen()
|
||||
}
|
||||
}
|
||||
|
||||
currentScreen.dialog?.let {
|
||||
when (it) {
|
||||
is ValidateKeyPhrase -> {
|
||||
ValidateKeyPhraseDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.yogeshpaliyal.common.utils.setPasswordHint
|
||||
import com.yogeshpaliyal.keypass.R
|
||||
import com.yogeshpaliyal.keypass.ui.commonComponents.DefaultBottomAppBar
|
||||
@@ -42,6 +43,7 @@ fun PasswordHintScreen() {
|
||||
modifier = Modifier
|
||||
.padding(contentPadding)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize(1f)) {
|
||||
KeyPassInputField(
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.yogeshpaliyal.keypass.ui.redux.actions.StateUpdateAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastActionStr
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateContextAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.middlewares.intentNavigationMiddleware
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.BottomSheetState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.KeyPassState
|
||||
@@ -101,6 +102,9 @@ object KeyPassRedux {
|
||||
)
|
||||
)
|
||||
}
|
||||
is UpdateDialogState -> {
|
||||
state.copy(dialog = action.dialogState)
|
||||
}
|
||||
|
||||
else -> state
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.yogeshpaliyal.keypass.ui.redux.actions
|
||||
|
||||
import android.content.Context
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.DialogState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ScreenState
|
||||
|
||||
sealed interface Action
|
||||
@@ -9,3 +10,4 @@ class UpdateContextAction(val context: Context?) : Action
|
||||
|
||||
data class NavigationAction(val state: ScreenState, val clearBackStack: Boolean = false) : Action
|
||||
data class StateUpdateAction(val state: ScreenState) : Action
|
||||
data class UpdateDialogState(val dialogState: DialogState? = null) : Action
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.yogeshpaliyal.keypass.ui.redux.states
|
||||
|
||||
sealed class DialogState()
|
||||
|
||||
object ValidateKeyPhrase : DialogState()
|
||||
@@ -7,6 +7,7 @@ data class KeyPassState(
|
||||
val context: Context? = null,
|
||||
val currentScreen: ScreenState,
|
||||
val bottomSheet: BottomSheetState? = null,
|
||||
val dialog: DialogState? = null,
|
||||
val systemBackPress: Boolean = false
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package com.yogeshpaliyal.keypass.ui.redux.states
|
||||
|
||||
sealed class ScreenState(val showMainBottomAppBar: Boolean = false)
|
||||
sealed class ScreenState(val showMainBottomAppBar: Boolean = false, val dialogState: DialogState? = null)
|
||||
|
||||
@@ -48,12 +48,14 @@ import com.yogeshpaliyal.keypass.ui.redux.actions.Action
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.IntentNavigation
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction
|
||||
import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.AboutState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.BackupImporterState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.BackupScreenState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeAppHintState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeAppPasswordState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ChangeDefaultPasswordLengthState
|
||||
import com.yogeshpaliyal.keypass.ui.redux.states.ValidateKeyPhrase
|
||||
import kotlinx.coroutines.launch
|
||||
import org.reduxkotlin.compose.rememberTypedDispatcher
|
||||
|
||||
@@ -109,6 +111,13 @@ fun MySettingCompose() {
|
||||
dispatchAction(NavigationAction(ChangeDefaultPasswordLengthState()))
|
||||
}
|
||||
|
||||
PreferenceItem(
|
||||
title = R.string.validate_keyphrase,
|
||||
summary = R.string.validate_keyphrase
|
||||
) {
|
||||
dispatchAction(UpdateDialogState(ValidateKeyPhrase))
|
||||
}
|
||||
|
||||
BiometricsOption()
|
||||
|
||||
Divider(
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
<string name="enter_keyphrase">Enter Keyphrase</string>
|
||||
<string name="restore">Restore</string>
|
||||
<string name="keyphrase_restore_info">Please enter keyphrase you get when you backed up</string>
|
||||
<string name="keyphrase_validate_info">Please enter the keyphrase you used to create your backup. We will occasionally show this screen to help you remember it.</string>
|
||||
<string name="custom_generated_keyphrase_info">Do you want to create your own keyphrase for backups or should I generate for you?</string>
|
||||
|
||||
<string name="ascending">Ascending</string>
|
||||
@@ -123,6 +124,10 @@
|
||||
<string name="invalid_csv_file">Invalid CSV File</string>
|
||||
<string name="file_not_found">File not found (please try again)</string>
|
||||
<string name="enter_password_hint">Enter password hint</string>
|
||||
<string name="validate_keyphrase">Keyphrase Check</string>
|
||||
<string name="valid_keyphrase">Valid Keyphrase</string>
|
||||
<string name="mismatch_keyphrase">Entered Keyphrase does not match with backup keyphrase</string>
|
||||
<string name="validate">Check</string>
|
||||
<string name="biometric_disabled_due_to_timeout">Please login via password because you haven\'t used password in last 24 hours</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -8,7 +8,6 @@ const val DEFAULT_PASSWORD_LENGTH = 10f
|
||||
@Keep
|
||||
@Serializable
|
||||
data class UserSettings(
|
||||
val isDefault: Boolean = false,
|
||||
val keyPassPassword: String? = null,
|
||||
val dbPassword: String? = null,
|
||||
@Deprecated("Use passwordConfig instead")
|
||||
@@ -23,7 +22,8 @@ data class UserSettings(
|
||||
val currentAppVersion: Int? = null,
|
||||
val passwordConfig: PasswordConfig = PasswordConfig.Initial,
|
||||
val passwordHint: String? = null,
|
||||
val lastPasswordLoginTime: Long? = null
|
||||
val lastPasswordLoginTime: Long? = null,
|
||||
val lastKeyPhraseEnterTime: Long? = null
|
||||
) {
|
||||
fun isKeyPresent() = backupKey != null
|
||||
}
|
||||
|
||||
@@ -145,6 +145,12 @@ suspend fun Context.updateLastPasswordLoginTime(lastPasswordLoginTime: Long?) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun Context.updateLastKeyPhraseEnterTime(lastKeyPhraseEnterTime: Long?) {
|
||||
getUserSettingsDataStore().updateData {
|
||||
it.copy(lastKeyPhraseEnterTime = lastKeyPhraseEnterTime)
|
||||
}
|
||||
}
|
||||
|
||||
private val BACKUP_KEY = stringPreferencesKey("backup_key")
|
||||
private val BIOMETRIC_ENABLE = booleanPreferencesKey("biometric_enable")
|
||||
private val KEYPASS_PASSWORD = stringPreferencesKey("keypass_password")
|
||||
|
||||
Reference in New Issue
Block a user