Feat: Make delete password optional for web app integration and translate messages.

This commit is contained in:
2025-10-31 15:37:57 +01:00
parent 5a64db4149
commit 9d2f0d4dd6
5 changed files with 152 additions and 78 deletions

View File

@@ -438,38 +438,53 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository,
viewModelScope.launch {
val webAppPrefs = getApplication<Application>().getSharedPreferences("webapp_prefs", Context.MODE_PRIVATE)
val url = webAppPrefs.getString("webapp_url", null)
val keyPass = webAppPrefs.getString("key_pass", null)
if (url.isNullOrBlank() || keyPass.isNullOrBlank()) {
Toast.makeText(getApplication(), "Web App credentials not set up", Toast.LENGTH_SHORT).show()
if (url.isNullOrBlank()) {
Toast.makeText(getApplication(), de.lxtools.noteshop.R.string.webapp_url_not_set_up, Toast.LENGTH_SHORT).show()
return@launch
}
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
val keyPass = webAppPrefs.getString("key_pass", null)
val username: String?
val password: String?
try {
val usernameEncrypted = webAppPrefs.getString("username_encrypted", null)
val passwordEncrypted = webAppPrefs.getString("password_encrypted", null)
if (keyPass.isNullOrBlank()) {
// Load unencrypted credentials
username = webAppPrefs.getString("username", null)
password = webAppPrefs.getString("password", null)
} else {
// Load encrypted credentials
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
if (usernameEncrypted == null || passwordEncrypted == null) {
Toast.makeText(getApplication(), "Web App credentials corrupted", Toast.LENGTH_SHORT).show()
try {
val usernameEncrypted = webAppPrefs.getString("username_encrypted", null)
val passwordEncrypted = webAppPrefs.getString("password_encrypted", null)
if (usernameEncrypted == null || passwordEncrypted == null) {
Toast.makeText(getApplication(), de.lxtools.noteshop.R.string.webapp_credentials_corrupted, Toast.LENGTH_SHORT).show()
return@launch
}
val decryptedUsernameBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(usernameEncrypted), secretKey)
username = String(decryptedUsernameBytes, Charsets.UTF_8)
val decryptedPasswordBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(passwordEncrypted), secretKey)
password = String(decryptedPasswordBytes, Charsets.UTF_8)
} catch (e: Exception) {
Toast.makeText(getApplication(), getApplication<Application>().getString(de.lxtools.noteshop.R.string.failed_to_decrypt_credentials_generic, e.message), Toast.LENGTH_LONG).show()
return@launch
}
val decryptedUsernameBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(usernameEncrypted), secretKey)
val username = String(decryptedUsernameBytes, Charsets.UTF_8)
val decryptedPasswordBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(passwordEncrypted), secretKey)
val password = String(decryptedPasswordBytes, Charsets.UTF_8)
noteshopRepository.importItemsFromWebApp(listId, url, username, password)
Toast.makeText(getApplication(), de.lxtools.noteshop.R.string.import_successful, Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(getApplication(), "Failed to import items: ${e.message}", Toast.LENGTH_LONG).show()
}
if (username.isNullOrBlank() || password.isNullOrBlank()) {
Toast.makeText(getApplication(), de.lxtools.noteshop.R.string.webapp_credentials_not_set_up, Toast.LENGTH_SHORT).show()
return@launch
}
noteshopRepository.importItemsFromWebApp(listId, url, username, password)
Toast.makeText(getApplication(), de.lxtools.noteshop.R.string.import_successful, Toast.LENGTH_SHORT).show()
}
}
@@ -477,35 +492,49 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository,
viewModelScope.launch {
val webAppPrefs = getApplication<Application>().getSharedPreferences("webapp_prefs", Context.MODE_PRIVATE)
val url = webAppPrefs.getString("webapp_url", null)
val keyPass = webAppPrefs.getString("key_pass", null)
if (url.isNullOrBlank() || keyPass.isNullOrBlank()) {
if (url.isNullOrBlank()) {
return@launch // Silently fail if not configured
}
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
val keyPass = webAppPrefs.getString("key_pass", null)
val username: String?
val password: String?
try {
val usernameEncrypted = webAppPrefs.getString("username_encrypted", null)
val passwordEncrypted = webAppPrefs.getString("password_encrypted", null)
if (keyPass.isNullOrBlank()) {
// Load unencrypted credentials
username = webAppPrefs.getString("username", null)
password = webAppPrefs.getString("password", null)
} else {
// Load encrypted credentials
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
if (usernameEncrypted == null || passwordEncrypted == null) {
return@launch // Silently fail
try {
val usernameEncrypted = webAppPrefs.getString("username_encrypted", null)
val passwordEncrypted = webAppPrefs.getString("password_encrypted", null)
if (usernameEncrypted == null || passwordEncrypted == null) {
return@launch // Silently fail
}
val decryptedUsernameBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(usernameEncrypted), secretKey)
username = String(decryptedUsernameBytes, Charsets.UTF_8)
val decryptedPasswordBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(passwordEncrypted), secretKey)
password = String(decryptedPasswordBytes, Charsets.UTF_8)
} catch (e: Exception) {
Log.e("ShoppingListsViewModel", "Failed to decrypt credentials for marking item", e)
return@launch
}
val decryptedUsernameBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(usernameEncrypted), secretKey)
val username = String(decryptedUsernameBytes, Charsets.UTF_8)
val decryptedPasswordBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(passwordEncrypted), secretKey)
val password = String(decryptedPasswordBytes, Charsets.UTF_8)
noteshopRepository.markItemInWebApp(url, username, password, itemName)
} catch (e: Exception) {
Log.e("ShoppingListsViewModel", "Failed to mark item in web app", e)
}
if (username.isNullOrBlank() || password.isNullOrBlank()) {
return@launch // Silently fail if not configured
}
noteshopRepository.markItemInWebApp(url, username, password, itemName)
}
}
@@ -513,33 +542,51 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository,
viewModelScope.launch {
val webAppPrefs = getApplication<Application>().getSharedPreferences("webapp_prefs", Context.MODE_PRIVATE)
val url = webAppPrefs.getString("webapp_url", null)
val keyPass = webAppPrefs.getString("key_pass", null) // This is the delete password
val deletePass = webAppPrefs.getString("key_pass", "") ?: ""
if (url.isNullOrBlank() || keyPass.isNullOrBlank()) {
if (url.isNullOrBlank()) {
// Silently fail if not configured
return@launch
}
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
val keyPass = webAppPrefs.getString("key_pass", null)
val username: String?
val password: String?
try {
val usernameEncrypted = webAppPrefs.getString("username_encrypted", null)
val passwordEncrypted = webAppPrefs.getString("password_encrypted", null)
if (keyPass.isNullOrBlank()) {
// Load unencrypted credentials
username = webAppPrefs.getString("username", null)
password = webAppPrefs.getString("password", null)
} else {
// Load encrypted credentials
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
if (usernameEncrypted == null || passwordEncrypted == null) {
return@launch // Silently fail
try {
val usernameEncrypted = webAppPrefs.getString("username_encrypted", null)
val passwordEncrypted = webAppPrefs.getString("password_encrypted", null)
if (usernameEncrypted == null || passwordEncrypted == null) {
return@launch // Silently fail
}
val decryptedUsernameBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(usernameEncrypted), secretKey)
username = String(decryptedUsernameBytes, Charsets.UTF_8)
val decryptedPasswordBytes = fileEncryptor.decrypt(java.util.Base64.getDecoder().decode(passwordEncrypted), secretKey)
password = String(decryptedPasswordBytes, Charsets.UTF_8)
} catch (e: Exception) {
Log.e("ShoppingListsViewModel", "Failed to decrypt credentials for deleting items", e)
return@launch
}
val username = String(java.util.Base64.getDecoder().decode(usernameEncrypted), Charsets.UTF_8)
val password = String(java.util.Base64.getDecoder().decode(passwordEncrypted), Charsets.UTF_8)
noteshopRepository.deleteMarkedItemsInWebApp(url, username, password, keyPass)
} catch (e: Exception) {
Log.e("ShoppingListsViewModel", "Failed to delete marked items in web app", e)
}
if (username.isNullOrBlank() || password.isNullOrBlank()) {
return@launch // Silently fail if not configured
}
noteshopRepository.deleteMarkedItemsInWebApp(url, username, password, deletePass)
}
}

View File

@@ -72,7 +72,7 @@ fun WebAppIntegrationScreen(
OutlinedTextField(
value = viewModel.deletePassword,
onValueChange = viewModel::onDeletePasswordChange,
label = { Text(stringResource(R.string.delete_password_label)) },
label = { Text(stringResource(R.string.delete_password_label) + " (optional)") },
visualTransformation = if (viewModel.showDeletePassword) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image = if (viewModel.showDeletePassword)

View File

@@ -81,7 +81,12 @@ class WebAppIntegrationViewModel(private val repository: NoteshopRepository, app
viewModelScope.launch {
webAppUrl = sharedPrefs.getString("webapp_url", "") ?: ""
val keyPass = sharedPrefs.getString("key_pass", null)
if (keyPass != null) {
if (keyPass.isNullOrBlank()) {
// Load unencrypted credentials
username = sharedPrefs.getString("username", "") ?: ""
password = sharedPrefs.getString("password", "") ?: ""
} else {
// Load encrypted credentials
deletePassword = keyPass
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
@@ -103,24 +108,34 @@ class WebAppIntegrationViewModel(private val repository: NoteshopRepository, app
fun saveAndTestConnection() {
viewModelScope.launch {
if (deletePassword.isBlank()) {
Toast.makeText(getApplication(), getApplication<Application>().getString(de.lxtools.noteshop.R.string.deletion_password_cannot_be_empty), Toast.LENGTH_SHORT).show()
return@launch
}
val (success, message) = repository.testWebAppConnection(webAppUrl, username, password)
if (success) {
val secretKey = keyManager.derivePbeKey(deletePassword.toCharArray())
if (deletePassword.isNotBlank()) {
val secretKey = keyManager.derivePbeKey(deletePassword.toCharArray())
val encryptedUsername = java.util.Base64.getEncoder().encodeToString(fileEncryptor.encrypt(username.toByteArray(), secretKey))
val encryptedPassword = java.util.Base64.getEncoder().encodeToString(fileEncryptor.encrypt(password.toByteArray(), secretKey))
val encryptedUsername = java.util.Base64.getEncoder().encodeToString(fileEncryptor.encrypt(username.toByteArray(), secretKey))
val encryptedPassword = java.util.Base64.getEncoder().encodeToString(fileEncryptor.encrypt(password.toByteArray(), secretKey))
sharedPrefs.edit().apply {
putString("webapp_url", webAppUrl)
putString("username_encrypted", encryptedUsername)
putString("password_encrypted", encryptedPassword)
putString("key_pass", deletePassword) // Storing the key password directly, assuming it's the delete password
apply()
sharedPrefs.edit().apply {
putString("webapp_url", webAppUrl)
putString("username_encrypted", encryptedUsername)
putString("password_encrypted", encryptedPassword)
putString("key_pass", deletePassword)
remove("username") // Remove plain text credentials if they exist
remove("password")
apply()
}
} else {
// Store credentials unencrypted
sharedPrefs.edit().apply {
putString("webapp_url", webAppUrl)
putString("username", username)
putString("password", password)
remove("username_encrypted")
remove("password_encrypted")
remove("key_pass")
apply()
}
}
Toast.makeText(getApplication(), de.lxtools.noteshop.R.string.connection_successful, Toast.LENGTH_SHORT).show()
} else {
@@ -141,6 +156,8 @@ class WebAppIntegrationViewModel(private val repository: NoteshopRepository, app
remove("username_encrypted")
remove("password_encrypted")
remove("key_pass")
remove("username")
remove("password")
apply()
}
Toast.makeText(getApplication(), getApplication<Application>().getString(de.lxtools.noteshop.R.string.web_app_integration_reset_successful), Toast.LENGTH_SHORT).show()

View File

@@ -302,6 +302,11 @@
<string name="web_app_integration_reset_successful">Web-App-Integration erfolgreich zurückgesetzt</string>
<string name="failed_to_decrypt_credentials">Fehler beim Entschlüsseln der Zugangsdaten</string>
<string name="deletion_password_cannot_be_empty">Löschpasswort darf nicht leer sein</string>
<string name="webapp_url_not_set_up">Web-App-URL nicht eingerichtet</string>
<string name="webapp_credentials_not_set_up">Web-App-Anmeldeinformationen nicht eingerichtet</string>
<string name="webapp_credentials_corrupted">Web-App-Anmeldeinformationen beschädigt</string>
<string name="failed_to_decrypt_credentials_generic">Anmeldeinformationen konnten nicht entschlüsselt werden: %1$s</string>
<string name="failed_to_import_items">Elemente konnten nicht importiert werden: %1$s</string>
<string name="tour_page_1_title">Willkommen bei Noteshop!</string>
<string name="tour_page_1_text">Verwalte deine Notizen, Einkaufslisten und Rezepte an einem Ort.</string>

View File

@@ -302,6 +302,11 @@
<string name="web_app_integration_reset_successful">Web App Integration reset successfully</string>
<string name="failed_to_decrypt_credentials">Failed to decrypt credentials</string>
<string name="deletion_password_cannot_be_empty">Deletion password cannot be empty</string>
<string name="webapp_url_not_set_up">Web App URL not set up</string>
<string name="webapp_credentials_not_set_up">Web App credentials not set up</string>
<string name="webapp_credentials_corrupted">Web App credentials corrupted</string>
<string name="failed_to_decrypt_credentials_generic">Failed to decrypt credentials: %1$s</string>
<string name="failed_to_import_items">Failed to import items: %1$s</string>
<string name="tour_page_1_title">Welcome to Noteshop!</string>
<string name="tour_page_1_text">Manage your notes, shopping lists, and recipes all in one place.</string>