From 7831511ed64dd43b500e554fe6c9624e6c5802a0 Mon Sep 17 00:00:00 2001 From: Yogesh Choudhary Paliyal Date: Sun, 26 Jan 2025 11:54:52 +0530 Subject: [PATCH] feat: standardise flow for dialogs (#1061) * feat: standardise flow for dialogs * feat: minor change --- .../keypass/importer/AccountsImporter.kt | 5 +- .../keypass/importer/ChromeAccountImporter.kt | 92 +-------------- .../importer/KeePassAccountImporter.kt | 48 +------- .../importer/KeyPassAccountImporter.kt | 108 +----------------- .../ui/backupsImport/BackupImporter.kt | 8 +- .../ui/commonComponents/LoadingDialog.kt | 40 +++++++ .../ui/dialogs/RestoreChromeBackupDialog.kt | 65 +++++++++++ .../ui/dialogs/RestoreKeePassBackupDialog.kt | 64 +++++++++++ .../ui/dialogs/RestoreKeyPassBackupDialog.kt | 102 +++++++++++++++++ .../ValidateKeyPhraseDialog.kt | 8 +- .../keypass/ui/home/DashboardViewModel.kt | 6 +- .../yogeshpaliyal/keypass/ui/home/Homepage.kt | 15 ++- .../home/components/ForgotKeyPhraseDialog.kt | 4 +- .../ui/nav/DashboardComposeActivity.kt | 14 ++- .../keypass/ui/redux/KeyPassRedux.kt | 15 ++- .../keypass/ui/redux/actions/Action.kt | 8 +- .../keypass/ui/redux/states/DialogState.kt | 8 ++ .../keypass/ui/redux/states/KeyPassState.kt | 2 + .../keypass/ui/settings/MySettingsFragment.kt | 5 +- build.gradle.kts | 2 +- 20 files changed, 355 insertions(+), 264 deletions(-) create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/LoadingDialog.kt create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreChromeBackupDialog.kt create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeePassBackupDialog.kt create mode 100644 app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeyPassBackupDialog.kt rename app/src/main/java/com/yogeshpaliyal/keypass/ui/{home/components => dialogs}/ValidateKeyPhraseDialog.kt (93%) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/importer/AccountsImporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/importer/AccountsImporter.kt index f8ffcf01..af72bac2 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/importer/AccountsImporter.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/importer/AccountsImporter.kt @@ -1,8 +1,6 @@ package com.yogeshpaliyal.keypass.importer import android.net.Uri -import androidx.compose.runtime.Composable -import com.yogeshpaliyal.common.data.AccountModel import com.yogeshpaliyal.keypass.ui.redux.actions.Action interface AccountsImporter { @@ -12,6 +10,5 @@ interface AccountsImporter { fun allowedMimeType(): String - @Composable - fun readFile(file: Uri, resolve: (List) -> Unit, onCompleteOrCancel: (action: Action?) -> Unit) + fun readFileGetAction(file: Uri): Action } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/importer/ChromeAccountImporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/importer/ChromeAccountImporter.kt index 52a7336d..67fe99b0 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/importer/ChromeAccountImporter.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/importer/ChromeAccountImporter.kt @@ -1,32 +1,10 @@ package com.yogeshpaliyal.keypass.importer import android.net.Uri -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Alignment -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 androidx.compose.ui.window.Dialog -import com.opencsv.CSVReader -import com.opencsv.exceptions.CsvMalformedLineException -import com.yogeshpaliyal.common.data.AccountModel import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.ui.redux.actions.Action -import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction -import java.io.FileNotFoundException +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreChromeBackupState class ChromeAccountImporter : AccountsImporter { override fun getImporterTitle(): Int = R.string.google_backup @@ -36,67 +14,9 @@ class ChromeAccountImporter : AccountsImporter { return "text/comma-separated-values" } - @Composable - override fun readFile(file: Uri, resolve: (List) -> Unit, onCompleteOrCancel: (action: Action?) -> Unit) { - val context = LocalContext.current - - LaunchedEffect(key1 = file, block = { - try { - val inputStream = context.contentResolver.openInputStream(file) - val reader = CSVReader(inputStream?.reader()) - val myEntries: List> = reader.readAll() - val headers = myEntries[0] - val result = myEntries.drop(1).map { data -> - headers.zip(data).toMap() - } - val listOfAccounts = ArrayList() - result.forEach { - listOfAccounts.add( - AccountModel( - title = it["name"], - notes = it["note"], - password = it["password"], - username = it["username"], - site = it["url"] - ) - ) - } - resolve(listOfAccounts) - onCompleteOrCancel(ToastAction(R.string.backup_restored)) - } catch (e: CsvMalformedLineException) { - onCompleteOrCancel(ToastAction(R.string.invalid_csv_file)) - } catch (e: FileNotFoundException) { - onCompleteOrCancel(ToastAction(R.string.file_not_found)) - } - }) - - LoadingDialog() + override fun readFileGetAction( + file: Uri + ): Action { + return UpdateDialogAction(RestoreChromeBackupState(file)) } } - -@Composable -fun LoadingDialog() { - Dialog(onDismissRequest = {}) { - Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium) - .fillMaxWidth(1f) - .padding(16.dp), - Arrangement.Center, - Alignment.CenterHorizontally - ) { - Text( - text = stringResource(id = R.string.restore), - style = MaterialTheme.typography.titleMedium - ) - Spacer(modifier = Modifier.size(16.dp)) - CircularProgressIndicator() - } - } -} - -@Preview() -@Composable -fun DialogPreview() { - LoadingDialog() -} diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeePassAccountImporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeePassAccountImporter.kt index 02ab9190..76273656 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeePassAccountImporter.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeePassAccountImporter.kt @@ -1,15 +1,10 @@ package com.yogeshpaliyal.keypass.importer import android.net.Uri -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.platform.LocalContext -import com.opencsv.CSVReader -import com.opencsv.exceptions.CsvMalformedLineException -import com.yogeshpaliyal.common.data.AccountModel import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.ui.redux.actions.Action -import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreKeePassBackupState class KeePassAccountImporter : AccountsImporter { override fun getImporterTitle(): Int = R.string.keepass_backup @@ -19,40 +14,9 @@ class KeePassAccountImporter : AccountsImporter { return "text/comma-separated-values" } - @Composable - override fun readFile(file: Uri, resolve: (List) -> Unit, onCompleteOrCancel: (action: Action?) -> Unit) { - val context = LocalContext.current - - LaunchedEffect(key1 = file, block = { - try { - val inputStream = context.contentResolver.openInputStream(file) - val reader = CSVReader(inputStream?.reader()) - val myEntries: List> = reader.readAll() - val headers = myEntries[0] - val result = myEntries.drop(1).map { data -> - headers.zip(data).toMap() - } - val listOfAccounts = ArrayList() - result.forEach { - listOfAccounts.add( - AccountModel( - title = it["Title"], - notes = it["Notes"], - password = it["Password"], - username = it["Username"], - site = it["URL"], - tags = it["Group"], - secret = if (it["TOTP"].isNullOrBlank()) null else it["TOTP"] - ) - ) - } - resolve(listOfAccounts) - onCompleteOrCancel(ToastAction(R.string.backup_restored)) - } catch (e: CsvMalformedLineException) { - onCompleteOrCancel(ToastAction(R.string.invalid_csv)) - } - }) - - LoadingDialog() + override fun readFileGetAction( + file: Uri + ): Action { + return UpdateDialogAction(RestoreKeePassBackupState(file)) } } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeyPassAccountImporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeyPassAccountImporter.kt index 383aecd5..3cae848d 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeyPassAccountImporter.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/importer/KeyPassAccountImporter.kt @@ -1,30 +1,9 @@ package com.yogeshpaliyal.keypass.importer import android.net.Uri -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.data.AccountModel -import com.yogeshpaliyal.common.dbhelper.restoreBackup -import com.yogeshpaliyal.common.utils.BACKUP_KEY_LENGTH import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.ui.redux.actions.Action -import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction -import kotlinx.coroutines.launch -import org.reduxkotlin.compose.rememberTypedDispatcher +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction class KeyPassAccountImporter : AccountsImporter { override fun getImporterTitle(): Int { @@ -37,86 +16,9 @@ class KeyPassAccountImporter : AccountsImporter { return "*/*" } - @Composable - override fun readFile(file: Uri, resolve: (List) -> Unit, onCompleteOrCancel: (action: Action?) -> Unit) { - RestoreDialog( - selectedFile = file, - hideDialog = { - onCompleteOrCancel(null) - }, - saveAccounts = resolve - ) + override fun readFileGetAction( + file: Uri + ): Action { + return UpdateDialogAction(com.yogeshpaliyal.keypass.ui.redux.states.RestoreKeyPassBackupState(file)) } } - -@Composable -fun RestoreDialog( - selectedFile: Uri, - hideDialog: () -> Unit, - saveAccounts: (list: List) -> Unit -) { - val (keyphrase, setKeyPhrase) = remember { - mutableStateOf("") - } - - val dispatchAction = rememberTypedDispatcher() - - val context = LocalContext.current - - val coroutineScope = rememberCoroutineScope() - - AlertDialog( - onDismissRequest = { - hideDialog() - }, - title = { - Text(text = stringResource(id = R.string.restore)) - }, - confirmButton = { - TextButton(onClick = { - if (keyphrase.isEmpty()) { - dispatchAction(ToastAction(R.string.alert_blank_keyphrase)) - return@TextButton - } - - if (keyphrase.length != BACKUP_KEY_LENGTH) { - dispatchAction(ToastAction(R.string.alert_invalid_keyphrase)) - return@TextButton - } - coroutineScope.launch { - val result = - restoreBackup(keyphrase, context.contentResolver, selectedFile) - - if (result != null) { - saveAccounts(result) - hideDialog() - dispatchAction(ToastAction(R.string.backup_restored)) - } else { - dispatchAction(ToastAction(R.string.invalid_keyphrase)) - } - } - }) { - Text(text = stringResource(id = R.string.restore)) - } - }, - dismissButton = { - TextButton(onClick = hideDialog) { - Text(text = stringResource(id = R.string.cancel)) - } - }, - text = { - Column(modifier = Modifier.fillMaxWidth(1f)) { - Text(text = stringResource(id = R.string.keyphrase_restore_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/backupsImport/BackupImporter.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backupsImport/BackupImporter.kt index 9c3784e2..8e274ca7 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/backupsImport/BackupImporter.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/backupsImport/BackupImporter.kt @@ -64,13 +64,7 @@ fun BackupImporter(state: BackupImporterState, mViewModel: DashboardViewModel = }) result?.let { - state.selectedImported?.readFile(result, { - setResult(null) - setRestoredAccounts(it) - }) { - it?.let(dispatchAction) - dispatchAction(StateUpdateAction(state = state.copy(selectedImported = null))) - } + state.selectedImported?.readFileGetAction(result)?.let { it1 -> dispatchAction(it1) } } Scaffold(bottomBar = { diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/LoadingDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/LoadingDialog.kt new file mode 100644 index 00000000..0915200f --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/commonComponents/LoadingDialog.kt @@ -0,0 +1,40 @@ +package com.yogeshpaliyal.keypass.ui.commonComponents + +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog + +@Composable +fun LoadingDialog(@StringRes id: Int) { + Dialog(onDismissRequest = {}) { + Column( + modifier = Modifier + .background(MaterialTheme.colorScheme.surface, MaterialTheme.shapes.medium) + .fillMaxWidth(1f) + .padding(16.dp), + Arrangement.Center, + Alignment.CenterHorizontally + ) { + Text( + text = stringResource(id = id), + style = MaterialTheme.typography.titleMedium + ) + Spacer(modifier = Modifier.size(16.dp)) + CircularProgressIndicator() + } + } +} diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreChromeBackupDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreChromeBackupDialog.kt new file mode 100644 index 00000000..5e8013cd --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreChromeBackupDialog.kt @@ -0,0 +1,65 @@ +package com.yogeshpaliyal.keypass.ui.dialogs + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext +import com.opencsv.CSVReader +import com.opencsv.exceptions.CsvMalformedLineException +import com.yogeshpaliyal.common.data.AccountModel +import com.yogeshpaliyal.keypass.R +import com.yogeshpaliyal.keypass.ui.commonComponents.LoadingDialog +import com.yogeshpaliyal.keypass.ui.redux.actions.Action +import com.yogeshpaliyal.keypass.ui.redux.actions.RestoreAccountsAction +import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreChromeBackupState +import org.reduxkotlin.compose.rememberTypedDispatcher +import java.io.FileNotFoundException + +@Composable +fun RestoreChromeBackupDialog( + state: RestoreChromeBackupState +) { + val (selectedFile) = state + + val dispatchAction = rememberTypedDispatcher() + + val context = LocalContext.current + + val onCompleteOrCancel: (ToastAction) -> Unit = { + dispatchAction(UpdateDialogAction(null)) + dispatchAction(it) + } + + LaunchedEffect(key1 = selectedFile, block = { + try { + val inputStream = context.contentResolver.openInputStream(selectedFile) + val reader = CSVReader(inputStream?.reader()) + val myEntries: List> = reader.readAll() + val headers = myEntries[0] + val result = myEntries.drop(1).map { data -> + headers.zip(data).toMap() + } + val listOfAccounts = ArrayList() + result.forEach { + listOfAccounts.add( + AccountModel( + title = it["name"], + notes = it["note"], + password = it["password"], + username = it["username"], + site = it["url"] + ) + ) + } + dispatchAction(RestoreAccountsAction(listOfAccounts)) + onCompleteOrCancel(ToastAction(R.string.backup_restored)) + } catch (e: CsvMalformedLineException) { + onCompleteOrCancel(ToastAction(R.string.invalid_csv_file)) + } catch (e: FileNotFoundException) { + onCompleteOrCancel(ToastAction(R.string.file_not_found)) + } + }) + + LoadingDialog(R.string.restore) +} diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeePassBackupDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeePassBackupDialog.kt new file mode 100644 index 00000000..bd21b250 --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeePassBackupDialog.kt @@ -0,0 +1,64 @@ +package com.yogeshpaliyal.keypass.ui.dialogs + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext +import com.opencsv.CSVReader +import com.opencsv.exceptions.CsvMalformedLineException +import com.yogeshpaliyal.common.data.AccountModel +import com.yogeshpaliyal.keypass.R +import com.yogeshpaliyal.keypass.ui.commonComponents.LoadingDialog +import com.yogeshpaliyal.keypass.ui.redux.actions.Action +import com.yogeshpaliyal.keypass.ui.redux.actions.RestoreAccountsAction +import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreKeePassBackupState +import org.reduxkotlin.compose.rememberTypedDispatcher + +@Composable +fun RestoreKeePassBackupDialog( + state: RestoreKeePassBackupState +) { + val (selectedFile) = state + + val dispatchAction = rememberTypedDispatcher() + + val context = LocalContext.current + + val onCompleteOrCancel: (ToastAction) -> Unit = { + dispatchAction(UpdateDialogAction(null)) + dispatchAction(it) + } + + LaunchedEffect(key1 = selectedFile, block = { + try { + val inputStream = context.contentResolver.openInputStream(selectedFile) + val reader = CSVReader(inputStream?.reader()) + val myEntries: List> = reader.readAll() + val headers = myEntries[0] + val result = myEntries.drop(1).map { data -> + headers.zip(data).toMap() + } + val listOfAccounts = ArrayList() + result.forEach { + listOfAccounts.add( + AccountModel( + title = it["Title"], + notes = it["Notes"], + password = it["Password"], + username = it["Username"], + site = it["URL"], + tags = it["Group"], + secret = if (it["TOTP"].isNullOrBlank()) null else it["TOTP"] + ) + ) + } + dispatchAction(RestoreAccountsAction(listOfAccounts)) + onCompleteOrCancel(ToastAction(R.string.backup_restored)) + } catch (e: CsvMalformedLineException) { + onCompleteOrCancel(ToastAction(R.string.invalid_csv)) + } + }) + + LoadingDialog(R.string.restore) +} diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeyPassBackupDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeyPassBackupDialog.kt new file mode 100644 index 00000000..abe9210a --- /dev/null +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/RestoreKeyPassBackupDialog.kt @@ -0,0 +1,102 @@ +package com.yogeshpaliyal.keypass.ui.dialogs + +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.dbhelper.restoreBackup +import com.yogeshpaliyal.common.utils.BACKUP_KEY_LENGTH +import com.yogeshpaliyal.keypass.R +import com.yogeshpaliyal.keypass.ui.redux.actions.Action +import com.yogeshpaliyal.keypass.ui.redux.actions.RestoreAccountsAction +import com.yogeshpaliyal.keypass.ui.redux.actions.ToastAction +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreKeyPassBackupState +import kotlinx.coroutines.launch +import org.reduxkotlin.compose.rememberTypedDispatcher + +@Composable +fun RestoreKeyPassBackupDialog( + state: RestoreKeyPassBackupState +) { + val (keyphrase, setKeyPhrase) = remember { + mutableStateOf("") + } + + val (selectedFile) = state + + val dispatchAction = rememberTypedDispatcher() + + val context = LocalContext.current + + val coroutineScope = rememberCoroutineScope() + + val hideDialog: () -> Unit = { + dispatchAction(UpdateDialogAction(null)) + } + + AlertDialog( + onDismissRequest = hideDialog, + title = { + Text(text = stringResource(id = R.string.restore)) + }, + confirmButton = { + TextButton(onClick = { + if (keyphrase.isEmpty()) { + dispatchAction(ToastAction(R.string.alert_blank_keyphrase)) + return@TextButton + } + + if (keyphrase.length != BACKUP_KEY_LENGTH) { + dispatchAction(ToastAction(R.string.alert_invalid_keyphrase)) + return@TextButton + } + coroutineScope.launch { + val result = + restoreBackup(keyphrase, context.contentResolver, selectedFile) + + if (result != null) { + dispatchAction(RestoreAccountsAction(result)) + dispatchAction(UpdateDialogAction(null)) + dispatchAction(ToastAction(R.string.backup_restored)) + } else { + dispatchAction(ToastAction(R.string.invalid_keyphrase)) + } + } + }) { + Text(text = stringResource(id = R.string.restore)) + } + }, + dismissButton = { + TextButton(onClick = hideDialog) { + Text(text = stringResource(id = R.string.cancel)) + } + }, + text = { + Column(modifier = Modifier.fillMaxWidth(1f)) { + Text(text = stringResource(id = R.string.keyphrase_restore_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/home/components/ValidateKeyPhraseDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/ValidateKeyPhraseDialog.kt similarity index 93% rename from app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ValidateKeyPhraseDialog.kt rename to app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/ValidateKeyPhraseDialog.kt index 0c5831f0..dd5a36b2 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ValidateKeyPhraseDialog.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/dialogs/ValidateKeyPhraseDialog.kt @@ -1,4 +1,4 @@ -package com.yogeshpaliyal.keypass.ui.home.components +package com.yogeshpaliyal.keypass.ui.dialogs import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -21,7 +21,7 @@ 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 com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction import com.yogeshpaliyal.keypass.ui.redux.states.ForgotKeyPhraseState import kotlinx.coroutines.launch import org.reduxkotlin.compose.rememberTypedDispatcher @@ -40,7 +40,7 @@ fun ValidateKeyPhraseDialog() { val coroutineScope = rememberCoroutineScope() val hideDialog: () -> Unit = { - dispatchAction(UpdateDialogState(null)) + dispatchAction(UpdateDialogAction(null)) } AlertDialog( @@ -92,7 +92,7 @@ fun ValidateKeyPhraseDialog() { Spacer(modifier = Modifier.size(8.dp)) TextButton(onClick = { - dispatchAction(UpdateDialogState(ForgotKeyPhraseState)) + dispatchAction(UpdateDialogAction(ForgotKeyPhraseState)) }) { Text(text = stringResource(id = R.string.forgot_keyphrase_question)) } diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt index 36a03f5e..c72700f4 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/DashboardViewModel.kt @@ -43,9 +43,11 @@ class DashboardViewModel @Inject constructor( } } - suspend fun restoreBackup( + fun restoreBackup( list: List ) { - return appDb.saveToDb(list) + viewModelScope.launch { + appDb.saveToDb(list) + } } } 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 feb883e9..881d3a92 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 @@ -23,12 +23,15 @@ 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.KeyPassRedux +import com.yogeshpaliyal.keypass.ui.redux.actions.Action 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.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateViewModalAction import com.yogeshpaliyal.keypass.ui.redux.states.HomeState import com.yogeshpaliyal.keypass.ui.redux.states.ValidateKeyPhrase -import org.reduxkotlin.compose.rememberDispatcher +import org.reduxkotlin.compose.rememberTypedDispatcher import java.util.concurrent.TimeUnit /* @@ -52,12 +55,16 @@ fun Homepage( val listOfAccountsLiveData by mViewModel.mediator.observeAsState() - val dispatchAction = rememberDispatcher() + val dispatchAction = rememberTypedDispatcher() LaunchedEffect(tag, keyword, sortField, sortAscendingOrder, block = { mViewModel.queryUpdated(keyword, tag, sortField, sortAscendingOrder) }) + LaunchedEffect(KeyPassRedux, mViewModel) { + dispatchAction(UpdateViewModalAction(mViewModel)) + } + LaunchedEffect(Unit, { if (userSettings.backupKey == null) { return@LaunchedEffect @@ -68,7 +75,7 @@ fun Homepage( val diffInDays = TimeUnit.MILLISECONDS.toDays(diff) if (diffInDays >= 7) { // Show the modal - dispatchAction(UpdateDialogState(dialogState = ValidateKeyPhrase)) + dispatchAction(UpdateDialogAction(dialogState = ValidateKeyPhrase)) } }) diff --git a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ForgotKeyPhraseDialog.kt b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ForgotKeyPhraseDialog.kt index 72075914..22ea3933 100644 --- a/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ForgotKeyPhraseDialog.kt +++ b/app/src/main/java/com/yogeshpaliyal/keypass/ui/home/components/ForgotKeyPhraseDialog.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.yogeshpaliyal.keypass.R import com.yogeshpaliyal.keypass.ui.redux.actions.Action -import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogState +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateDialogAction import com.yogeshpaliyal.keypass.ui.redux.states.ValidateKeyPhrase import org.reduxkotlin.compose.rememberTypedDispatcher @@ -22,7 +22,7 @@ fun ForgotKeyPhraseDialog() { val dispatchAction = rememberTypedDispatcher() val hideDialog: () -> Unit = { - dispatchAction(UpdateDialogState(ValidateKeyPhrase)) + dispatchAction(UpdateDialogAction(ValidateKeyPhrase)) } AlertDialog( 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 a2ea4283..abc2b743 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 @@ -33,10 +33,13 @@ import com.yogeshpaliyal.keypass.ui.backupsImport.BackupImporter import com.yogeshpaliyal.keypass.ui.changeDefaultPasswordLength.ChangeDefaultPasswordLengthScreen import com.yogeshpaliyal.keypass.ui.changePassword.ChangePassword import com.yogeshpaliyal.keypass.ui.detail.AccountDetailPage +import com.yogeshpaliyal.keypass.ui.dialogs.RestoreChromeBackupDialog +import com.yogeshpaliyal.keypass.ui.dialogs.RestoreKeePassBackupDialog +import com.yogeshpaliyal.keypass.ui.dialogs.RestoreKeyPassBackupDialog +import com.yogeshpaliyal.keypass.ui.dialogs.ValidateKeyPhraseDialog import com.yogeshpaliyal.keypass.ui.generate.ui.GeneratePasswordScreen import com.yogeshpaliyal.keypass.ui.home.Homepage import com.yogeshpaliyal.keypass.ui.home.components.ForgotKeyPhraseDialog -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 @@ -55,6 +58,9 @@ import com.yogeshpaliyal.keypass.ui.redux.states.ForgotKeyPhraseState 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.RestoreChromeBackupState +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreKeePassBackupState +import com.yogeshpaliyal.keypass.ui.redux.states.RestoreKeyPassBackupState import com.yogeshpaliyal.keypass.ui.redux.states.SettingsState import com.yogeshpaliyal.keypass.ui.redux.states.ValidateKeyPhrase import com.yogeshpaliyal.keypass.ui.settings.MySettingCompose @@ -132,7 +138,6 @@ fun Dashboard() { DisposableEffect(KeyPassRedux, context) { dispatch(UpdateContextAction(context)) - onDispose { dispatch(UpdateContextAction(null)) } @@ -195,7 +200,10 @@ fun CurrentPage() { currentScreen.dialog?.let { when (it) { is ValidateKeyPhrase -> ValidateKeyPhraseDialog() - ForgotKeyPhraseState -> ForgotKeyPhraseDialog() + is ForgotKeyPhraseState -> ForgotKeyPhraseDialog() + is RestoreKeyPassBackupState -> RestoreKeyPassBackupDialog(it) + is RestoreChromeBackupState -> RestoreChromeBackupDialog(it) + is RestoreKeePassBackupState -> RestoreKeePassBackupDialog(it) } } } 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 2fbbcb02..56c01d9c 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 @@ -9,11 +9,13 @@ import com.yogeshpaliyal.keypass.ui.redux.actions.BottomSheetAction import com.yogeshpaliyal.keypass.ui.redux.actions.CopyToClipboard import com.yogeshpaliyal.keypass.ui.redux.actions.GoBackAction import com.yogeshpaliyal.keypass.ui.redux.actions.NavigationAction +import com.yogeshpaliyal.keypass.ui.redux.actions.RestoreAccountsAction 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.actions.UpdateDialogAction +import com.yogeshpaliyal.keypass.ui.redux.actions.UpdateViewModalAction import com.yogeshpaliyal.keypass.ui.redux.middlewares.intentNavigationMiddleware import com.yogeshpaliyal.keypass.ui.redux.states.BottomSheetState import com.yogeshpaliyal.keypass.ui.redux.states.KeyPassState @@ -102,10 +104,19 @@ object KeyPassRedux { ) ) } - is UpdateDialogState -> { + is UpdateDialogAction -> { state.copy(dialog = action.dialogState) } + is UpdateViewModalAction -> { + state.copy(viewModel = action.viewModal) + } + + is RestoreAccountsAction -> { + state.viewModel?.restoreBackup(action.accounts) + state + } + 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 8315e6ea..e6b162e9 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,13 +1,19 @@ package com.yogeshpaliyal.keypass.ui.redux.actions import android.content.Context +import com.yogeshpaliyal.common.data.AccountModel +import com.yogeshpaliyal.keypass.ui.home.DashboardViewModel import com.yogeshpaliyal.keypass.ui.redux.states.DialogState import com.yogeshpaliyal.keypass.ui.redux.states.ScreenState sealed interface Action class UpdateContextAction(val context: Context?) : Action +class UpdateViewModalAction(val viewModal: DashboardViewModel?) : Action data class NavigationAction(val state: ScreenState, val clearBackStack: Boolean = false) : Action + +data class RestoreAccountsAction(val accounts: List) : Action + data class StateUpdateAction(val state: ScreenState) : Action -data class UpdateDialogState(val dialogState: DialogState? = null) : Action +data class UpdateDialogAction(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 index a9252535..dec63cd0 100644 --- 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 @@ -1,6 +1,14 @@ package com.yogeshpaliyal.keypass.ui.redux.states +import android.net.Uri + sealed class DialogState() object ValidateKeyPhrase : DialogState() object ForgotKeyPhraseState : DialogState() + +sealed class RestoreBackupState(val fileUri: Uri) : DialogState() + +data class RestoreKeyPassBackupState(val fileUrii: Uri) : RestoreBackupState(fileUrii) +data class RestoreChromeBackupState(val fileUrii: Uri) : RestoreBackupState(fileUrii) +data class RestoreKeePassBackupState(val fileUrii: Uri) : RestoreBackupState(fileUrii) 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 085b9b8d..f5c0cce0 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 @@ -1,10 +1,12 @@ package com.yogeshpaliyal.keypass.ui.redux.states import android.content.Context +import com.yogeshpaliyal.keypass.ui.home.DashboardViewModel import com.yogeshpaliyal.keypass.ui.redux.BottomSheetRoutes data class KeyPassState( val context: Context? = null, + val viewModel: DashboardViewModel? = null, val currentScreen: ScreenState, val bottomSheet: BottomSheetState? = null, val dialog: 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 063895ff..b932bc9f 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,7 +19,6 @@ 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 @@ -48,7 +47,7 @@ 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.actions.UpdateDialogAction import com.yogeshpaliyal.keypass.ui.redux.states.AboutState import com.yogeshpaliyal.keypass.ui.redux.states.BackupImporterState import com.yogeshpaliyal.keypass.ui.redux.states.BackupScreenState @@ -115,7 +114,7 @@ fun MySettingCompose() { title = R.string.validate_keyphrase, summary = R.string.validate_keyphrase ) { - dispatchAction(UpdateDialogState(ValidateKeyPhrase)) + dispatchAction(UpdateDialogAction(ValidateKeyPhrase)) } BiometricsOption() diff --git a/build.gradle.kts b/build.gradle.kts index 45754d6d..55e59686 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,4 +63,4 @@ subprojects { // commandLine = listOf("sh", "./githooks/git-init.sh") //} -tasks.getByPath("app:assemble").dependsOn(installGitHook) +//tasks.getByPath("app:assemble").dependsOn(installGitHook)