diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthScreen.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthScreen.kt index fcb1a729..a4476f22 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthScreen.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/AuthScreen.kt @@ -48,6 +48,10 @@ fun AuthScreen(state: AuthState) { mutableStateOf(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) } } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt index 06908527..dc0be4c4 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/ButtonBar.kt @@ -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 { diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt index 66a5897b..e4a7ffa6 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/auth/components/PasswordInputField.kt @@ -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() + 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") + } + } } } ) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt index e821b06e..1bac5f09 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/nav/DashboardComposeActivity.kt @@ -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() } } } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/passwordHint/PasswordHintScreen.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/passwordHint/PasswordHintScreen.kt new file mode 100644 index 00000000..ea8619bb --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/passwordHint/PasswordHintScreen.kt @@ -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() + + 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() +} diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ChangeAppHintState.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ChangeAppHintState.kt new file mode 100644 index 00000000..85fe0480 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ChangeAppHintState.kt @@ -0,0 +1,3 @@ +package com.yogeshpaliyal.keypass.ui.redux.states + +object ChangeAppHintState : ScreenState(false) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt index 1110ce0f..305038bf 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/settings/MySettingsFragment.kt @@ -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, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2cd06983..989bdfb0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,6 +87,11 @@ The password did not match Your accounts will be backed up whenever account is added or modified Change App Password + Change Hint + Remove Hint + App Password Hint + Set App Password Hint + Change App Password Hint Old Password New Password Please enter your old password @@ -94,6 +99,8 @@ Please enter your new password Please enter confirm password Password changed successfully + Hint changed successfully + Hint removed successfully No biometric features available on this device. Biometric features are currently unavailable. @@ -113,5 +120,6 @@ Invalid secret key format Invalid CSV File File not found (please try again) + Enter password hint diff --git a/common/src/main/java/com/yogeshpaliyal/common/data/UserSettings.kt b/common/src/main/java/com/yogeshpaliyal/common/data/UserSettings.kt index b20b06db..32e74926 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/data/UserSettings.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/data/UserSettings.kt @@ -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 } diff --git a/common/src/main/java/com/yogeshpaliyal/common/utils/SharedPreferenceUtils.kt b/common/src/main/java/com/yogeshpaliyal/common/utils/SharedPreferenceUtils.kt index 0ce6f8dc..9f949ede 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/utils/SharedPreferenceUtils.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/utils/SharedPreferenceUtils.kt @@ -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