refactor: Clean up UI components and improve JSON import/export

- Removed unused parameters and functions related to biometric protection and save/reset operations in AppShell.kt and AppDialogs.kt.
- Simplified logging and toast messages in JsonImportExportDialog.kt.
- Improved null safety and file output stream handling in JsonImportExportDialog.kt.
- Updated AndroidManifest.xml with target API level and tools namespace.
This commit is contained in:
2025-11-06 22:18:49 +01:00
parent c2ea7c98ef
commit 44e4bd3218
4 changed files with 66 additions and 72 deletions

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
@@ -16,7 +17,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.Noteshop">
android:theme="@style/Theme.Noteshop"
tools:targetApi="33">
<activity
android:name=".MainActivity"
android:exported="true"

View File

@@ -1084,8 +1084,8 @@ fun AppShell(
onShowListDialogChange = { setShowListDialog(it) },
listDetails = listDetails,
onListDetailsChange = { shoppingListsViewModel.updateListDetails(it) },
onSaveList = { scope.launch { shoppingListsViewModel.saveList() } },
onResetListDetails = { shoppingListsViewModel.resetListDetails() },
onSetListProtection = { password ->
selectedListId?.let { listId ->
shoppingListsViewModel.setProtection(listId, password)
@@ -1095,32 +1095,20 @@ fun AppShell(
setShowSetListPasswordDialog(false)
},
onSetListProtectionBiometric = { id ->
shoppingListsViewModel.setProtectionBiometric(id)
setCurrentScreen(Screen.ShoppingLists)
},
txtImportLauncher = txtImportLauncher,
showNoteDialog = showNoteDialog,
onShowNoteDialogChange = { setShowNoteDialog(it) },
noteDetails = noteDetails,
onNoteDetailsChange = { notesViewModel.updateNoteDetails(it) },
onSaveNote = { scope.launch { notesViewModel.saveNote() } },
onResetNoteDetails = { notesViewModel.resetNoteDetails() },
onSetNoteProtectionBiometric = { id ->
notesViewModel.setProtectionBiometric(id)
setCurrentScreen(Screen.Notes)
},
noteImportLauncher = noteImportLauncher,
showRecipeDialog = showRecipeDialog,
onShowRecipeDialogChange = { setShowRecipeDialog(it) },
recipeDetails = recipeDetails,
onRecipeDetailsChange = { recipesViewModel.updateRecipeDetails(it) },
onSaveRecipe = { scope.launch { recipesViewModel.saveRecipe() } },
onResetRecipeDetails = { recipesViewModel.resetRecipeDetails() },
onSetRecipeProtectionBiometric = { id ->
recipesViewModel.setProtectionBiometric(id)
setCurrentScreen(Screen.Recipes)
},
recipeImportLauncher = recipeImportLauncher,
recipesTitle = recipesTitle,
showJsonDialog = showJsonDialog,
@@ -1276,7 +1264,6 @@ fun AppShell(
setItemToLockType(null)
},
canUseBiometrics = canUseBiometrics,
itemToLockType = itemToLockType,
showStartupPasswordDialog = showStartupPasswordDialog,
onShowStartupPasswordDialogChange = { setShowStartupPasswordDialog(it) },
onUnlockEncryption = { password ->

View File

@@ -1,5 +1,6 @@
package de.lxtools.noteshop.ui
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Column
@@ -89,14 +90,14 @@ fun JsonImportExportDialog(
scope.launch {
context.contentResolver.openInputStream(it)?.use { inputStream ->
val fileContent = inputStream.readBytes()
var jsonString: String? = null
var jsonString: String?
if (secretKey != null) {
try {
val decryptedBytes = fileEncryptor.decrypt(fileContent, secretKey!!)
jsonString = decryptedBytes.toString(Charsets.UTF_8)
} catch (e: javax.crypto.AEADBadTagException) {
android.util.Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
// Fallback to unencrypted if decryption fails
jsonString = fileContent.toString(Charsets.UTF_8)
}
@@ -106,14 +107,14 @@ fun JsonImportExportDialog(
}
try {
notesViewModel.importNotesFromJson(jsonString!!)
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
} catch (e: kotlinx.serialization.SerializationException) {
android.util.Log.e("JsonImportExportDialog", "Error parsing notes JSON: ${e.message}")
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
notesViewModel.importNotesFromJson(jsonString)
Toast.makeText(context, R.string.json_import_successful, Toast.LENGTH_SHORT).show()
} catch (e: SerializationException) {
Log.e("JsonImportExportDialog", "Error parsing notes JSON: ${e.message}")
Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), Toast.LENGTH_LONG).show()
} catch (e: Exception) {
android.util.Log.e("JsonImportExportDialog", "Error importing notes: ${e.message}")
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
Log.e("JsonImportExportDialog", "Error importing notes: ${e.message}")
Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), Toast.LENGTH_LONG).show()
}
}
}
@@ -129,12 +130,14 @@ fun JsonImportExportDialog(
scope.launch {
val allNotes = notesViewModel.uiState.value.noteList
val content = notesViewModel.exportNotesToJson(allNotes)
context.contentResolver.openOutputStream(it)?.use { outputStream ->
secretKey?.let { key ->
val encryptedContent = fileEncryptor.encrypt(content.toByteArray(), key)
context.contentResolver.openOutputStream(it, "wt")?.use { outputStream ->
if (secretKey != null) {
val encryptedContent = fileEncryptor.encrypt(content.toByteArray(), secretKey!!)
outputStream.write(encryptedContent)
} else {
outputStream.write(content.toByteArray(Charsets.UTF_8))
}
android.widget.Toast.makeText(context, R.string.json_export_successful, android.widget.Toast.LENGTH_SHORT).show()
Toast.makeText(context, R.string.json_export_successful, Toast.LENGTH_SHORT).show()
}
}
}
@@ -149,12 +152,14 @@ fun JsonImportExportDialog(
scope.launch {
val allShoppingLists = shoppingListsViewModel.uiState.value.shoppingLists
val content = shoppingListsViewModel.exportShoppingListsToJson(allShoppingLists)
context.contentResolver.openOutputStream(it)?.use { outputStream ->
secretKey?.let { key ->
val encryptedContent = fileEncryptor.encrypt(content.toByteArray(), key)
context.contentResolver.openOutputStream(it, "wt")?.use { outputStream ->
if (secretKey != null) {
val encryptedContent = fileEncryptor.encrypt(content.toByteArray(), secretKey!!)
outputStream.write(encryptedContent)
} else {
outputStream.write(content.toByteArray(Charsets.UTF_8))
}
android.widget.Toast.makeText(context, R.string.json_export_successful, android.widget.Toast.LENGTH_SHORT).show()
Toast.makeText(context, R.string.json_export_successful, Toast.LENGTH_SHORT).show()
}
}
}
@@ -169,14 +174,14 @@ fun JsonImportExportDialog(
scope.launch {
context.contentResolver.openInputStream(it)?.use { inputStream ->
val fileContent = inputStream.readBytes()
var jsonString: String? = null
var jsonString: String?
if (secretKey != null) {
try {
val decryptedBytes = fileEncryptor.decrypt(fileContent, secretKey!!)
jsonString = decryptedBytes.toString(Charsets.UTF_8)
} catch (e: javax.crypto.AEADBadTagException) {
android.util.Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
// Fallback to unencrypted if decryption fails
jsonString = fileContent.toString(Charsets.UTF_8)
}
@@ -186,14 +191,14 @@ fun JsonImportExportDialog(
}
try {
shoppingListsViewModel.importShoppingListsFromJson(jsonString!!)
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
} catch (e: kotlinx.serialization.SerializationException) {
android.util.Log.e("JsonImportExportDialog", "Error parsing shopping lists JSON: ${e.message}")
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
shoppingListsViewModel.importShoppingListsFromJson(jsonString)
Toast.makeText(context, R.string.json_import_successful, Toast.LENGTH_SHORT).show()
} catch (e: SerializationException) {
Log.e("JsonImportExportDialog", "Error parsing shopping lists JSON: ${e.message}")
Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), Toast.LENGTH_LONG).show()
} catch (e: Exception) {
android.util.Log.e("JsonImportExportDialog", "Error importing shopping lists: ${e.message}")
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
Log.e("JsonImportExportDialog", "Error importing shopping lists: ${e.message}")
Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), Toast.LENGTH_LONG).show()
}
}
}
@@ -209,12 +214,14 @@ fun JsonImportExportDialog(
scope.launch {
val allRecipes = recipesViewModel.uiState.value.recipeList
val content = recipesViewModel.exportRecipesToJson(allRecipes)
context.contentResolver.openOutputStream(it)?.use { outputStream ->
secretKey?.let { key ->
val encryptedContent = fileEncryptor.encrypt(content.toByteArray(), key)
context.contentResolver.openOutputStream(it, "wt")?.use { outputStream ->
if (secretKey != null) {
val encryptedContent = fileEncryptor.encrypt(content.toByteArray(), secretKey!!)
outputStream.write(encryptedContent)
} else {
outputStream.write(content.toByteArray(Charsets.UTF_8))
}
android.widget.Toast.makeText(context, R.string.json_export_successful, android.widget.Toast.LENGTH_SHORT).show()
Toast.makeText(context, R.string.json_export_successful, Toast.LENGTH_SHORT).show()
}
}
}
@@ -229,14 +236,14 @@ fun JsonImportExportDialog(
scope.launch {
context.contentResolver.openInputStream(it)?.use { inputStream ->
val fileContent = inputStream.readBytes()
var jsonString: String? = null
var jsonString: String?
if (secretKey != null) {
try {
val decryptedBytes = fileEncryptor.decrypt(fileContent, secretKey!!)
jsonString = decryptedBytes.toString(Charsets.UTF_8)
} catch (e: javax.crypto.AEADBadTagException) {
android.util.Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
Log.w("JsonImportExportDialog", "Decryption failed, trying as unencrypted: ${e.message}")
// Fallback to unencrypted if decryption fails
jsonString = fileContent.toString(Charsets.UTF_8)
}
@@ -246,14 +253,14 @@ fun JsonImportExportDialog(
}
try {
recipesViewModel.importRecipesFromJson(jsonString!!)
android.widget.Toast.makeText(context, R.string.json_import_successful, android.widget.Toast.LENGTH_SHORT).show()
} catch (e: kotlinx.serialization.SerializationException) {
android.util.Log.e("JsonImportExportDialog", "Error parsing recipes JSON: ${e.message}")
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), android.widget.Toast.LENGTH_LONG).show()
recipesViewModel.importRecipesFromJson(jsonString)
Toast.makeText(context, R.string.json_import_successful, Toast.LENGTH_SHORT).show()
} catch (e: SerializationException) {
Log.e("JsonImportExportDialog", "Error parsing recipes JSON: ${e.message}")
Toast.makeText(context, context.getString(R.string.json_import_failed_invalid_format, e.message), Toast.LENGTH_LONG).show()
} catch (e: Exception) {
android.util.Log.e("JsonImportExportDialog", "Error importing recipes: ${e.message}")
android.widget.Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), android.widget.Toast.LENGTH_LONG).show()
Log.e("JsonImportExportDialog", "Error importing recipes: ${e.message}")
Toast.makeText(context, context.getString(R.string.json_import_failed_generic, e.message), Toast.LENGTH_LONG).show()
}
}
}
@@ -339,4 +346,4 @@ fun JsonImportExportDialog(
}
}
)
}
}

View File

@@ -27,7 +27,6 @@ import de.lxtools.noteshop.ui.recipes.RecipesViewModel
import de.lxtools.noteshop.ui.shopping.ShoppingListDetails
import de.lxtools.noteshop.ui.shopping.ShoppingListInputDialog
import de.lxtools.noteshop.ui.ChooseLockMethodDialog
import de.lxtools.noteshop.ui.LockableItemType
import de.lxtools.noteshop.ui.LockMethod
import de.lxtools.noteshop.ui.shopping.ShoppingListsViewModel
import de.lxtools.noteshop.ui.StartupPasswordDialog
@@ -45,26 +44,26 @@ fun AppDialogs(
onShowListDialogChange: (Boolean) -> Unit,
listDetails: ShoppingListDetails,
onListDetailsChange: (ShoppingListDetails) -> Unit,
onSaveList: () -> Unit,
onResetListDetails: () -> Unit,
onSetListProtection: (String) -> Unit,
onSetListProtectionBiometric: (Int) -> Unit,
txtImportLauncher: ActivityResultLauncher<Array<String>>,
showNoteDialog: Boolean,
onShowNoteDialogChange: (Boolean) -> Unit,
noteDetails: NoteDetails,
onNoteDetailsChange: (NoteDetails) -> Unit,
onSaveNote: () -> Unit,
onResetNoteDetails: () -> Unit,
onSetNoteProtectionBiometric: (Int) -> Unit,
noteImportLauncher: ActivityResultLauncher<Array<String>>,
showRecipeDialog: Boolean,
onShowRecipeDialogChange: (Boolean) -> Unit,
recipeDetails: RecipeDetails,
onRecipeDetailsChange: (RecipeDetails) -> Unit,
onSaveRecipe: () -> Unit,
onResetRecipeDetails: () -> Unit,
onSetRecipeProtectionBiometric: (Int) -> Unit,
recipeImportLauncher: ActivityResultLauncher<Array<String>>,
recipesTitle: String,
showJsonDialog: Boolean,
@@ -102,7 +101,6 @@ fun AppDialogs(
onShowChooseLockMethodDialogChange: (Boolean) -> Unit,
onConfirmLockMethod: (LockMethod) -> Unit,
canUseBiometrics: Boolean,
itemToLockType: de.lxtools.noteshop.ui.LockableItemType?,
showStartupPasswordDialog: Boolean,
onShowStartupPasswordDialogChange: (Boolean) -> Unit,
onUnlockEncryption: (String) -> Unit,