Add password hint field (#970)

* Add password hint field

Fixes #957

Add a password hint field to the authentication screen.

* **UserSettings.kt**: Add a new property `passwordHint` of type `String?` to the `UserSettings` data class.
* **SharedPreferenceUtils.kt**: Add functions `setPasswordHint` and `getPasswordHint` to save and retrieve the password hint in the `UserSettings` data store.
* **AuthScreen.kt**: Add a new state variable `passwordHint` to store the password hint. Add an `OutlinedTextField` for the password hint in the `Column` layout. Update the `ButtonBar` call to pass the `passwordHint` state variable.
* **ButtonBar.kt**: Add a new parameter `passwordHint` to the `ButtonBar` composable function. Update the `onClick` handler for the `Button` to save the password hint using the `setPasswordHint` function.
* **strings.xml**: Add a new string resource `enter_password_hint` with the value "Enter password hint".

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/yogeshpaliyal/KeyPass/issues/957?shareId=XXXX-XXXX-XXXX-XXXX).

* feat: add password hint
This commit is contained in:
Yogesh Choudhary Paliyal
2024-09-14 16:57:04 +05:30
committed by GitHub
parent 62ff3d1812
commit d5e2608159
10 changed files with 149 additions and 14 deletions

View File

@@ -48,6 +48,10 @@ fun AuthScreen(state: AuthState) {
mutableStateOf<Int?>(null)
}
val (passwordHint, setPasswordHint) = remember(state) {
mutableStateOf("")
}
BackHandler(state is AuthState.ConfirmPassword) {
dispatchAction(NavigationAction(AuthState.CreatePassword, true))
}
@@ -99,10 +103,11 @@ fun AuthScreen(state: AuthState) {
setPassword = setPassword,
passwordVisible = passwordVisible,
setPasswordVisible = setPasswordVisible,
passwordError = passwordError
passwordError = passwordError,
hint = if (state is AuthState.Login && userSettings.passwordHint != null) userSettings.passwordHint else null
)
ButtonBar(state, password, setPasswordError) {
ButtonBar(state, password, setPasswordError, passwordHint) {
dispatchAction(it)
}
}

View File

@@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.yogeshpaliyal.common.utils.setKeyPassPassword
import com.yogeshpaliyal.common.utils.setPasswordHint
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.ui.nav.LocalUserSettings
import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction
@@ -27,6 +28,7 @@ fun ButtonBar(
state: AuthState,
password: String,
setPasswordError: (Int?) -> Unit,
passwordHint: String, // New parameter for password hint
dispatchAction: (NavigationAction) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
@@ -69,6 +71,7 @@ fun ButtonBar(
if (state.password == password) {
coroutineScope.launch {
context.setKeyPassPassword(password)
context.setPasswordHint(passwordHint) // Save the password hint
dispatchAction(NavigationAction(HomeState(), true))
}
} else {

View File

@@ -1,8 +1,10 @@
package com.yogeshpaliyal.keypass.ui.auth.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.rounded.Visibility
import androidx.compose.material.icons.rounded.VisibilityOff
import androidx.compose.material3.Icon
@@ -12,11 +14,15 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.ui.redux.actions.Action
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastActionStr
import org.reduxkotlin.compose.rememberTypedDispatcher
@Composable
fun PasswordInputField(
@@ -25,8 +31,12 @@ fun PasswordInputField(
setPassword: (String) -> Unit,
passwordVisible: Boolean,
setPasswordVisible: (Boolean) -> Unit,
passwordError: Int?
passwordError: Int?,
hint: String? = null
) {
val context = LocalContext.current
val dispatchAction = rememberTypedDispatcher<Action>()
OutlinedTextField(
modifier = Modifier.fillMaxWidth(1f),
value = password,
@@ -54,9 +64,17 @@ fun PasswordInputField(
// Please provide localized description for accessibility services
val description = if (passwordVisible) "Hide password" else "Show password"
IconButton(onClick = { setPasswordVisible(!passwordVisible) }) {
Icon(imageVector = image, description)
Row {
IconButton(onClick = { setPasswordVisible(!passwordVisible) }) {
Icon(imageVector = image, description)
}
hint?.let {
IconButton(onClick = {
dispatchAction(ToastActionStr(hint))
}) {
Icon(imageVector = Icons.Outlined.Info, "Hint")
}
}
}
}
)

View File

@@ -35,6 +35,7 @@ import com.yogeshpaliyal.keypass.ui.generate.ui.GeneratePasswordScreen
import com.yogeshpaliyal.keypass.ui.home.Homepage
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.UpdateContextAction
@@ -43,6 +44,7 @@ import com.yogeshpaliyal.keypass.ui.redux.states.AccountDetailState
import com.yogeshpaliyal.keypass.ui.redux.states.AuthState
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.HomeState
@@ -172,6 +174,7 @@ fun CurrentPage() {
is BackupImporterState -> BackupImporter(state = it)
is AboutState -> AboutScreen()
is PasswordGeneratorState -> GeneratePasswordScreen()
ChangeAppHintState -> PasswordHintScreen()
}
}
}

View File

@@ -0,0 +1,80 @@
package com.yogeshpaliyal.keypass.ui.passwordHint
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
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 com.yogeshpaliyal.common.utils.setPasswordHint
import com.yogeshpaliyal.keypass.R
import com.yogeshpaliyal.keypass.ui.commonComponents.DefaultBottomAppBar
import com.yogeshpaliyal.keypass.ui.commonComponents.KeyPassInputField
import com.yogeshpaliyal.keypass.ui.nav.LocalUserSettings
import com.yogeshpaliyal.keypass.ui.redux.actions.Action
import com.yogeshpaliyal.keypass.ui.redux.actions.GoBackAction
import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction
import kotlinx.coroutines.launch
import org.reduxkotlin.compose.rememberTypedDispatcher
@Composable
fun PasswordHintScreen() {
val dispatchAction = rememberTypedDispatcher<Action>()
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val userSettings = LocalUserSettings.current
val (passwordHint, setPasswordHint) = rememberSaveable { mutableStateOf(userSettings.passwordHint) }
Scaffold(bottomBar = { DefaultBottomAppBar() }) { contentPadding ->
Surface(
modifier = Modifier
.padding(contentPadding)
.fillMaxWidth()
) {
Column(modifier = Modifier.fillMaxSize(1f)) {
KeyPassInputField(
placeholder = R.string.enter_password_hint,
value = passwordHint,
setValue = setPasswordHint
)
Button(modifier = Modifier.fillMaxWidth(1f), onClick = {
coroutineScope.launch {
context.setPasswordHint(passwordHint)
dispatchAction(ToastAction(R.string.hint_change_success))
dispatchAction(GoBackAction)
}
}) {
Text(text = stringResource(id = R.string.change_app_hint))
}
OutlinedButton(modifier = Modifier.fillMaxWidth(1f), onClick = {
coroutineScope.launch {
context.setPasswordHint(null)
dispatchAction(ToastAction(R.string.hint_removed_success))
dispatchAction(GoBackAction)
}
}) {
Text(text = stringResource(id = R.string.remove_app_hint))
}
}
}
}
}
@Preview
@Composable
fun PPasswordHintScreen() {
PasswordHintScreen()
}

View File

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

View File

@@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.rounded.Feedback
import androidx.compose.material.icons.rounded.Fingerprint
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.Password
import androidx.compose.material.icons.rounded.Share
import androidx.compose.material3.Divider
@@ -50,6 +51,7 @@ import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction
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 kotlinx.coroutines.launch
@@ -90,6 +92,15 @@ fun MySettingCompose() {
) {
dispatchAction(NavigationAction(ChangeAppPasswordState()))
}
PreferenceItem(
title = R.string.app_password_hint,
summary = if (userSettings.passwordHint != null) R.string.change_app_password_hint else R.string.set_app_password_hint,
icon = Icons.Outlined.Info
) {
dispatchAction(NavigationAction(ChangeAppHintState))
}
val changePasswordLengthSummary = context.getString(R.string.default_password_length)
PreferenceItem(
title = R.string.change_password_length,

View File

@@ -87,6 +87,11 @@
<string name="password_no_match">The password did not match</string>
<string name="auto_backup_desc">Your accounts will be backed up whenever account is added or modified</string>
<string name="change_app_password">Change App Password</string>
<string name="change_app_hint">Change Hint</string>
<string name="remove_app_hint">Remove Hint</string>
<string name="app_password_hint">App Password Hint</string>
<string name="set_app_password_hint">Set App Password Hint</string>
<string name="change_app_password_hint">Change App Password Hint</string>
<string name="old_password">Old Password</string>
<string name="new_password">New Password</string>
<string name="blank_old_password">Please enter your old password</string>
@@ -94,6 +99,8 @@
<string name="blank_new_password">Please enter your new password</string>
<string name="blank_confirm_password">Please enter confirm password</string>
<string name="password_change_success">Password changed successfully</string>
<string name="hint_change_success">Hint changed successfully</string>
<string name="hint_removed_success">Hint removed successfully</string>
<string name="biometric_error_no_hardware">No biometric features available on this device.</string>
<string name="biometric_error_hw_unavailable">Biometric features are currently unavailable.</string>
@@ -113,5 +120,6 @@
<string name="invalid_secret_key">Invalid secret key format</string>
<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>
</resources>

View File

@@ -20,7 +20,8 @@ data class UserSettings(
val overrideAutoBackup: Boolean = false,
val lastAppVersion: Int? = null,
val currentAppVersion: Int? = null,
val passwordConfig: PasswordConfig = PasswordConfig.Initial
val passwordConfig: PasswordConfig = PasswordConfig.Initial,
val passwordHint: String? = null
) {
fun isKeyPresent() = backupKey != null
}

View File

@@ -15,13 +15,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
/*
* @author Yogesh Paliyal
* techpaliyal@gmail.com
* https://techpaliyal.com
* created on 21-02-2021 11:18
*/
val Context.dataStore by preferencesDataStore(
name = "settings"
)
@@ -136,6 +129,16 @@ suspend fun Context.getOrCreateBackupKey(reset: Boolean = false): Pair<Boolean,
}
}
suspend fun Context.setPasswordHint(passwordHint: String?) {
getUserSettingsDataStore().updateData {
it.copy(passwordHint = passwordHint)
}
}
suspend fun Context.getPasswordHint(): String? {
return getUserSettings().passwordHint
}
private val BACKUP_KEY = stringPreferencesKey("backup_key")
private val BIOMETRIC_ENABLE = booleanPreferencesKey("biometric_enable")
private val KEYPASS_PASSWORD = stringPreferencesKey("keypass_password")