chore(android): Commit existing uncommitted changes in MainActivity and strings.xml

This commit is contained in:
2025-10-22 21:48:34 +02:00
parent fae079b77f
commit be44321c99
3 changed files with 159 additions and 64 deletions

View File

@@ -399,6 +399,7 @@ fun AppShell(
var isEncryptionEnabled by rememberSaveable { mutableStateOf(sharedPrefs.getBoolean("encryption_enabled", false)) }
var hasEncryptionPassword by rememberSaveable { mutableStateOf(keyManager.hasKey()) }
var showPasswordDialog by rememberSaveable { mutableStateOf(false) }
var showDeleteConfirmationDialog by remember { mutableStateOf(false) }
val onEncryptionToggle: (Boolean) -> Unit = { enabled ->
if (!enabled) { // Turning OFF
@@ -964,26 +965,27 @@ fun AppShell(
}
)
val syncFolderLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
onResult = { uri ->
uri?.let {
context.contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
val syncFolderLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(),
onResult = { uri ->
uri?.let {
try {
context.contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
sharedPrefs.edit {
putString("sync_folder_uri", it.toString())
}
val parentFolder = DocumentFile.fromTreeUri(context, it)
parentFolder?.findFile("Noteshop") ?: parentFolder?.createDirectory("Noteshop")
android.widget.Toast.makeText(context, R.string.sync_folder_selected, android.widget.Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Log.e("MainActivity", "Error setting up sync folder", e)
}
}
)
}
)
@@ -1106,23 +1108,32 @@ fun AppShell(
item {
NavigationDrawerItem(
label = {
Column {
Text(stringResource(id = R.string.select_sync_folder))
val syncFolderUriString = sharedPrefs.getString("sync_folder_uri", null)
if (!syncFolderUriString.isNullOrBlank()) {
val displayPath = remember(syncFolderUriString) {
try {
val uri = android.net.Uri.parse(syncFolderUriString)
val docFile = DocumentFile.fromTreeUri(context, uri)
val currentName = docFile?.name
val parentName = docFile?.parentFile?.name
if (parentName != null && currentName != null) {
"$parentName / $currentName"
} else {
currentName ?: ""
Row(verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.weight(1f)) {
Text(stringResource(id = R.string.select_sync_folder))
val syncFolderUriString = sharedPrefs.getString("sync_folder_uri", null)
val displayPath = if (syncFolderUriString.isNullOrBlank()) {
stringResource(id = R.string.no_folder_selected)
} else {
val path = remember(syncFolderUriString) {
try {
val uri = android.net.Uri.parse(syncFolderUriString)
val docFile = DocumentFile.fromTreeUri(context, uri)
val noteshopDir = docFile?.findFile("Noteshop")
val parentName = docFile?.name
if (parentName != null && noteshopDir != null) {
"$parentName / ${noteshopDir.name}"
} else {
parentName
}
} catch (e: Exception) {
null
}
} catch (e: Exception) {
"Ungültiger Pfad"
}
if (path != null) {
stringResource(id = R.string.selected_folder) + " " + path
} else {
stringResource(id = R.string.no_folder_selected)
}
}
Text(
@@ -1133,6 +1144,12 @@ fun AppShell(
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
)
}
val syncFolderUriString = sharedPrefs.getString("sync_folder_uri", null)
if (!syncFolderUriString.isNullOrBlank()) {
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.delete_folder))
}
}
}
},
selected = false,
@@ -1667,6 +1684,7 @@ fun AppShell(
}
)
}
is Screen.Notes -> {
NotesScreen(
viewModel = notesViewModel,
@@ -1689,39 +1707,52 @@ fun AppShell(
secretKey = secretKey,
fileEncryptor = fileEncryptor,
onUnlockClick = {
scope.launch {
if (secretKey != null) {
// Key already exists, decrypt directly
shoppingListsViewModel.toggleListLock(selectedListId!!, secretKey, fileEncryptor)
} else {
// No session key, prompt for authentication
val cipher = keyManager.getDecryptionCipher()
if (cipher != null) {
val crypto = BiometricPrompt.CryptoObject(cipher)
val activity = context.findActivity() as FragmentActivity
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.unlock_list),
subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
crypto = crypto,
onSuccess = { result ->
result.cryptoObject?.cipher?.let { authenticatedCipher ->
scope.launch {
val key = keyManager.getSecretKeyFromAuthenticatedCipher(authenticatedCipher)
shoppingListsViewModel.toggleListLock(selectedListId!!, key, fileEncryptor)
}
}
},
onFailed = {},
onError = { _, _ -> }
scope.launch {
if (secretKey != null) {
// Key already exists, decrypt directly
shoppingListsViewModel.toggleListLock(
selectedListId!!,
secretKey,
fileEncryptor
)
} else {
// No session key, prompt for authentication
val cipher = keyManager.getDecryptionCipher()
if (cipher != null) {
val crypto = BiometricPrompt.CryptoObject(cipher)
val activity =
context.findActivity() as FragmentActivity
biometricAuthenticator.promptBiometricAuth(
title = context.getString(R.string.unlock_list),
subtitle = "",
negativeButtonText = context.getString(R.string.cancel),
fragmentActivity = activity,
crypto = crypto,
onSuccess = { result ->
result.cryptoObject?.cipher?.let { authenticatedCipher ->
scope.launch {
val key =
keyManager.getSecretKeyFromAuthenticatedCipher(
authenticatedCipher
)
shoppingListsViewModel.toggleListLock(
selectedListId!!,
key,
fileEncryptor
)
}
}
},
onFailed = {},
onError = { _, _ -> }
)
}
}
}
}
}
)
}
is Screen.NoteDetail -> {
NoteDetailScreen(
noteId = selectedNoteId,
@@ -1741,8 +1772,17 @@ fun AppShell(
crypto = crypto,
onSuccess = { result ->
result.cryptoObject?.cipher?.let { authenticatedCipher ->
val key = keyManager.getSecretKeyFromAuthenticatedCipher(authenticatedCipher)
selectedNoteId?.let { notesViewModel.toggleNoteLock(it, key, fileEncryptor) }
val key =
keyManager.getSecretKeyFromAuthenticatedCipher(
authenticatedCipher
)
selectedNoteId?.let {
notesViewModel.toggleNoteLock(
it,
key,
fileEncryptor
)
}
}
},
onFailed = {},
@@ -1752,6 +1792,7 @@ fun AppShell(
}
)
}
is Screen.Recipes -> {
de.lxtools.noteshop.ui.recipes.RecipesScreen(
viewModel = recipesViewModel,
@@ -1765,6 +1806,7 @@ fun AppShell(
}
)
}
is Screen.RecipeDetail -> {
de.lxtools.noteshop.ui.recipes.RecipeDetailScreen(
recipeId = selectedRecipeId,
@@ -1784,8 +1826,17 @@ fun AppShell(
crypto = crypto,
onSuccess = { result ->
result.cryptoObject?.cipher?.let { authenticatedCipher ->
val key = keyManager.getSecretKeyFromAuthenticatedCipher(authenticatedCipher)
selectedRecipeId?.let { recipesViewModel.toggleRecipeLock(it, key, fileEncryptor) }
val key =
keyManager.getSecretKeyFromAuthenticatedCipher(
authenticatedCipher
)
selectedRecipeId?.let {
recipesViewModel.toggleRecipeLock(
it,
key,
fileEncryptor
)
}
}
},
onFailed = {},
@@ -1795,14 +1846,16 @@ fun AppShell(
}
)
}
is Screen.About -> {
AboutScreen()
}
is Screen.Settings -> {
SettingsScreen(
onThemeChange = onThemeChange,
currentTheme = colorTheme,
isAppLockEnabled = isAppLockEnabled,
onThemeChange = onThemeChange,
currentTheme = colorTheme,
isAppLockEnabled = isAppLockEnabled,
onAppLockChange = onAppLockChange,
isEncryptionEnabled = isEncryptionEnabled,
onEncryptionToggle = onEncryptionToggle,
@@ -1848,6 +1901,40 @@ fun AppShell(
sharedPrefs = sharedPrefs
)
}
if (showDeleteConfirmationDialog) {
AlertDialog(
onDismissRequest = { showDeleteConfirmationDialog = false },
title = { Text(stringResource(R.string.delete_folder)) },
text = { Text(stringResource(R.string.delete_folder_confirmation)) },
confirmButton = {
Button(
onClick = {
val syncFolderUriString = sharedPrefs.getString("sync_folder_uri", null)
if (syncFolderUriString != null) {
try {
val uri = android.net.Uri.parse(syncFolderUriString)
val docFile = DocumentFile.fromTreeUri(context, uri)
docFile?.delete()
context.contentResolver.releasePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
sharedPrefs.edit().remove("sync_folder_uri").apply()
} catch (e: Exception) {
Log.e("MainActivity", "Error deleting sync folder", e)
}
}
showDeleteConfirmationDialog = false
}
) {
Text(stringResource(R.string.delete))
}
},
dismissButton = {
TextButton(onClick = { showDeleteConfirmationDialog = false }) {
Text(stringResource(R.string.cancel))
}
}
)
}
}
@OptIn(ExperimentalMaterial3Api::class)

View File

@@ -275,4 +275,8 @@
<string name="error_corrupted_recipe">Rezeptdaten sind beschädigt und können nicht entschlüsselt werden.</string>
<string name="confirm_to_proceed">Zum Fortfahren bestätigen</string>
<string name="authenticate_to_perform_action">Zur Durchführung dieser Aktion authentifizieren</string>
<string name="selected_folder">Ausgewählt:</string>
<string name="no_folder_selected">kein Ordner ausgewählt</string>
<string name="delete_folder_confirmation">Möchten Sie den Noteshop-Ordner und alle darin enthaltenen Daten wirklich von Ihrem Gerät löschen?</string>
<string name="delete_folder">Ordner löschen</string>
</resources>

View File

@@ -275,4 +275,8 @@
<string name="json_export_successful">JSON export successful</string>
<string name="confirm_to_proceed">Confirm to proceed</string>
<string name="authenticate_to_perform_action">Authenticate to perform this action</string>
<string name="selected_folder">Selected:</string>
<string name="no_folder_selected">no folder selected</string>
<string name="delete_folder_confirmation">Do you really want to delete the Noteshop folder and all its contents from your device?</string>
<string name="delete_folder">Delete folder</string>
</resources>