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 ce1838fd..2df96d29 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 @@ -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)) } }) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupScreen.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupScreen.kt index 48e040ff..49416244 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupScreen.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backup/BackupScreen.kt @@ -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()) } }) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt index 4c1f55b9..feb883e9 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/Homepage.kt @@ -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))) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ValidateKeyPhraseDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ValidateKeyPhraseDialog.kt new file mode 100644 index 00000000..830d3245 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ValidateKeyPhraseDialog.kt @@ -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() + + 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)) + } + ) + } + } + ) +} 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 ab367954..6ae35ba4 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 @@ -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 { this.currentScreen } + val currentScreen by selectState { this } - currentScreen.let { +// val currentDialog by selectState { 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() + } + } + } } 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 index ea8619bb..a2d50843 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/passwordHint/PasswordHintScreen.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/passwordHint/PasswordHintScreen.kt @@ -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( diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt index cb85eb53..2fbbcb02 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/KeyPassRedux.kt @@ -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 } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/actions/Action.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/actions/Action.kt index 2822c57d..8315e6ea 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/actions/Action.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/actions/Action.kt @@ -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 diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/DialogState.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/DialogState.kt new file mode 100644 index 00000000..842efec3 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/DialogState.kt @@ -0,0 +1,5 @@ +package com.yogeshpaliyal.keypass.ui.redux.states + +sealed class DialogState() + +object ValidateKeyPhrase : DialogState() diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/KeyPassState.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/KeyPassState.kt index 7fc3d86f..085b9b8d 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/KeyPassState.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/KeyPassState.kt @@ -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 ) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ScreenState.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ScreenState.kt index fdeb9f10..0db58e65 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ScreenState.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/redux/states/ScreenState.kt @@ -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) 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 305038bf..063895ff 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 @@ -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( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e88eff96..7ba746ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,6 +74,7 @@ Enter Keyphrase Restore Please enter keyphrase you get when you backed up + Please enter the keyphrase you used to create your backup. We will occasionally show this screen to help you remember it. Do you want to create your own keyphrase for backups or should I generate for you? Ascending @@ -123,6 +124,10 @@ Invalid CSV File File not found (please try again) Enter password hint + Keyphrase Check + Valid Keyphrase + Entered Keyphrase does not match with backup keyphrase + Check Please login via password because you haven\'t used password in last 24 hours 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 31f763a4..50cef38b 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/data/UserSettings.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/data/UserSettings.kt @@ -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 } 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 a32bb57b..1ef6eeb0 100644 --- a/common/src/main/java/com/yogeshpaliyal/common/utils/SharedPreferenceUtils.kt +++ b/common/src/main/java/com/yogeshpaliyal/common/utils/SharedPreferenceUtils.kt @@ -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")