chore(android): Commit existing uncommitted changes in MainActivity and strings.xml
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user