Refactor: State management in AppShell.kt
This commit is contained in:
@@ -37,8 +37,7 @@ Noteshop is a versatile and privacy-focused application for managing your notes,
|
||||
|
||||
1. Clone the repository:
|
||||
```sh
|
||||
git clone YOUR_GITEA_REPOSITORY_URL_HERE
|
||||
cd noteshop
|
||||
git clone https://git.ilunix.de/punix/noteshop.git && cd noteshop
|
||||
```
|
||||
2. Build the app using Gradle:
|
||||
```sh
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.lxtools.noteshop.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
@@ -41,7 +42,6 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -74,12 +74,11 @@ import de.lxtools.noteshop.ui.recipes.RecipesViewModel
|
||||
import de.lxtools.noteshop.ui.shopping.ShoppingListsViewModel
|
||||
import de.lxtools.noteshop.ui.theme.ColorTheme
|
||||
import de.lxtools.noteshop.ui.webapp.WebAppIntegrationViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.FileOutputStream
|
||||
import javax.crypto.SecretKey
|
||||
import android.util.Base64
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -98,10 +97,10 @@ fun AppShell(
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var showStartupPasswordDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var startupUnlockErrorMessage by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val (showStartupPasswordDialog, setShowStartupPasswordDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (startupUnlockErrorMessage, setStartupUnlockErrorMessage) = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
|
||||
var syncFolderUriString by rememberSaveable {
|
||||
val (syncFolderUriString, setSyncFolderUriString) = rememberSaveable {
|
||||
mutableStateOf(
|
||||
sharedPrefs.getString(
|
||||
"sync_folder_uri",
|
||||
@@ -114,7 +113,7 @@ fun AppShell(
|
||||
val listener =
|
||||
android.content.SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||
if (key == "sync_folder_uri") {
|
||||
syncFolderUriString = sharedPreferences.getString(key, null)
|
||||
setSyncFolderUriString(sharedPreferences.getString(key, null))
|
||||
}
|
||||
}
|
||||
sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
|
||||
@@ -128,7 +127,7 @@ fun AppShell(
|
||||
val keyManager = remember { KeyManager(context, canUseBiometrics) }
|
||||
val fileEncryptor = remember { FileEncryptor() }
|
||||
|
||||
var isEncryptionEnabled by rememberSaveable {
|
||||
val (isEncryptionEnabled, setIsEncryptionEnabled) = rememberSaveable {
|
||||
mutableStateOf(
|
||||
sharedPrefs.getBoolean(
|
||||
"encryption_enabled",
|
||||
@@ -136,9 +135,9 @@ fun AppShell(
|
||||
)
|
||||
)
|
||||
}
|
||||
var hasEncryptionPassword by rememberSaveable { mutableStateOf(keyManager.hasKey()) }
|
||||
var showPasswordDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showDeleteConfirmationDialog by remember { mutableStateOf(false) }
|
||||
val (hasEncryptionPassword, setHasEncryptionPassword) = rememberSaveable { mutableStateOf(keyManager.hasKey()) }
|
||||
val (showPasswordDialog, setShowPasswordDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (showDeleteConfirmationDialog, setShowDeleteConfirmationDialog) = remember { mutableStateOf(false) }
|
||||
|
||||
val onEncryptionToggle: (Boolean) -> Unit = { enabled ->
|
||||
if (!enabled) { // Turning OFF
|
||||
@@ -149,8 +148,8 @@ fun AppShell(
|
||||
if (cipher == null) {
|
||||
// Fallback for safety, though this state should be unlikely if hasEncryptionPassword is true
|
||||
keyManager.deleteKey()
|
||||
hasEncryptionPassword = false
|
||||
isEncryptionEnabled = false
|
||||
setHasEncryptionPassword(false)
|
||||
setIsEncryptionEnabled(false)
|
||||
sharedPrefs.edit {
|
||||
putBoolean("encryption_enabled", false)
|
||||
remove("biometric_unlock_enabled")
|
||||
@@ -174,8 +173,8 @@ fun AppShell(
|
||||
|
||||
// Now, safely delete the key and disable the feature
|
||||
keyManager.deleteKey()
|
||||
hasEncryptionPassword = false
|
||||
isEncryptionEnabled = false
|
||||
setHasEncryptionPassword(false)
|
||||
setIsEncryptionEnabled(false)
|
||||
sharedPrefs.edit {
|
||||
putBoolean("encryption_enabled", false)
|
||||
remove("biometric_unlock_enabled")
|
||||
@@ -188,13 +187,13 @@ fun AppShell(
|
||||
)
|
||||
} else {
|
||||
// No password was ever set, just turn the switch off.
|
||||
isEncryptionEnabled = false
|
||||
setIsEncryptionEnabled(false)
|
||||
sharedPrefs.edit {
|
||||
putBoolean("encryption_enabled", false)
|
||||
}
|
||||
}
|
||||
} else { // Turning ON
|
||||
isEncryptionEnabled = true
|
||||
setIsEncryptionEnabled(true)
|
||||
sharedPrefs.edit {
|
||||
putBoolean("encryption_enabled", true)
|
||||
}
|
||||
@@ -202,7 +201,7 @@ fun AppShell(
|
||||
}
|
||||
|
||||
val onSetEncryptionPassword: () -> Unit = {
|
||||
showPasswordDialog = true
|
||||
setShowPasswordDialog(true)
|
||||
}
|
||||
|
||||
val onRemoveEncryption: () -> Unit = {
|
||||
@@ -210,8 +209,8 @@ fun AppShell(
|
||||
val cipher = keyManager.getDecryptionCipher()
|
||||
if (cipher == null) {
|
||||
// This can happen if no key is set. In this case, just disable the feature.
|
||||
hasEncryptionPassword = false
|
||||
isEncryptionEnabled = false
|
||||
setHasEncryptionPassword(false)
|
||||
setIsEncryptionEnabled(false)
|
||||
sharedPrefs.edit {
|
||||
putBoolean("encryption_enabled", false)
|
||||
remove("biometric_unlock_enabled")
|
||||
@@ -233,8 +232,8 @@ fun AppShell(
|
||||
|
||||
// Now it's safe to delete the key
|
||||
keyManager.deleteKey()
|
||||
hasEncryptionPassword = false
|
||||
isEncryptionEnabled = false
|
||||
setHasEncryptionPassword(false)
|
||||
setIsEncryptionEnabled(false)
|
||||
sharedPrefs.edit {
|
||||
putBoolean("encryption_enabled", false)
|
||||
remove("biometric_unlock_enabled")
|
||||
@@ -515,8 +514,8 @@ fun AppShell(
|
||||
}
|
||||
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
var secretKey by rememberSaveable(stateSaver = SecretKeySaver) { mutableStateOf(null) }
|
||||
var isDecryptionAttempted by rememberSaveable { mutableStateOf(false) }
|
||||
val (secretKey, setSecretKey) = rememberSaveable(stateSaver = SecretKeySaver) { mutableStateOf<SecretKey?>(null) }
|
||||
val (isDecryptionAttempted, setIsDecryptionAttempted) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
DisposableEffect(lifecycleOwner, context, scope) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
@@ -541,32 +540,33 @@ fun AppShell(
|
||||
onSuccess = { result ->
|
||||
result.cryptoObject?.cipher?.let { authenticatedCipher ->
|
||||
scope.launch {
|
||||
secretKey =
|
||||
setSecretKey(
|
||||
keyManager.getSecretKeyFromAuthenticatedCipher(
|
||||
authenticatedCipher
|
||||
)
|
||||
isDecryptionAttempted = true
|
||||
)
|
||||
setIsDecryptionAttempted(true)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailed = {},
|
||||
onError = { _, _ ->
|
||||
showStartupPasswordDialog = true
|
||||
setShowStartupPasswordDialog(true)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
isDecryptionAttempted = true // No key/cipher, proceed
|
||||
setIsDecryptionAttempted(true) // No key/cipher, proceed
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("AppShell", "Error during decryption prompt: ${e.message}")
|
||||
isDecryptionAttempted = true // Proceed without key
|
||||
setIsDecryptionAttempted(true) // Proceed without key
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showStartupPasswordDialog = true
|
||||
setShowStartupPasswordDialog(true)
|
||||
}
|
||||
} else {
|
||||
isDecryptionAttempted = true // No encryption, proceed
|
||||
setIsDecryptionAttempted(true) // No encryption, proceed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ fun AppShell(
|
||||
}
|
||||
}
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
var currentScreen: Screen by rememberSaveable {
|
||||
val (currentScreen, setCurrentScreen) = rememberSaveable {
|
||||
val defaultStartScreenRoute = sharedPrefs.getString("default_start_screen", "last_screen")
|
||||
val initialScreen = when (defaultStartScreenRoute) {
|
||||
Screen.ShoppingLists.route -> Screen.ShoppingLists
|
||||
@@ -624,58 +624,58 @@ fun AppShell(
|
||||
LaunchedEffect(Unit) {
|
||||
val firstLaunchCompleted = sharedPrefs.getBoolean("first_launch_completed", false)
|
||||
if (!firstLaunchCompleted) {
|
||||
currentScreen = Screen.GuidedTour
|
||||
setCurrentScreen(Screen.GuidedTour)
|
||||
sharedPrefs.edit { putBoolean("first_launch_completed", true) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var selectedListId: Int? by rememberSaveable { mutableStateOf(null) }
|
||||
var selectedNoteId: Int? by rememberSaveable { mutableStateOf(null) }
|
||||
var selectedRecipeId: Int? by rememberSaveable { mutableStateOf(null) }
|
||||
val (selectedListId, setSelectedListId) = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
val (selectedNoteId, setSelectedNoteId) = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
val (selectedRecipeId, setSelectedRecipeId) = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
|
||||
var itemWasInitiallyLocked by rememberSaveable { mutableStateOf(false) }
|
||||
val (itemWasInitiallyLocked, setItemWasInitiallyLocked) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(selectedNoteId, selectedListId, selectedRecipeId) {
|
||||
if (selectedNoteId == null && selectedListId == null && selectedRecipeId == null) {
|
||||
itemWasInitiallyLocked = false
|
||||
setItemWasInitiallyLocked(false)
|
||||
}
|
||||
}
|
||||
|
||||
var showListDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val (showListDialog, setShowListDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val listDetails by shoppingListsViewModel.listDetails.collectAsState()
|
||||
|
||||
var showNoteDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val (showNoteDialog, setShowNoteDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val noteDetails by notesViewModel.noteDetails.collectAsState()
|
||||
|
||||
var showRecipeDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val (showRecipeDialog, setShowRecipeDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val recipeDetails by recipesViewModel.recipeDetails.collectAsState()
|
||||
|
||||
var showJsonDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showSetPasswordDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showSetRecipePasswordDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showSetListPasswordDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val (showJsonDialog, setShowJsonDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (showSetPasswordDialog, setShowSetPasswordDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (showSetRecipePasswordDialog, setShowSetRecipePasswordDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (showSetListPasswordDialog, setShowSetListPasswordDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
|
||||
var showUnlockPasswordDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var unlockErrorMessage by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var itemToUnlockId by rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var itemToUnlockType by rememberSaveable { mutableStateOf<Screen?>(null) }
|
||||
val (showUnlockPasswordDialog, setShowUnlockPasswordDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (unlockErrorMessage, setUnlockErrorMessage) = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val (itemToUnlockId, setItemToUnlockId) = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
val (itemToUnlockType, setItemToUnlockType) = rememberSaveable { mutableStateOf<Screen?>(null) }
|
||||
|
||||
|
||||
|
||||
|
||||
var showChooseLockMethodDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var itemToLockId: Int? by rememberSaveable { mutableStateOf(null) }
|
||||
var itemToLockType: LockableItemType? by rememberSaveable { mutableStateOf(null) }
|
||||
val (showChooseLockMethodDialog, setShowChooseLockMethodDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
val (itemToLockId, setItemToLockId) = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
val (itemToLockType, setItemToLockType) = rememberSaveable { mutableStateOf<LockableItemType?>(null) }
|
||||
|
||||
|
||||
val onUnlockItem: (Int, Screen, Int) -> Unit = { id, type, protectionType ->
|
||||
when (protectionType) {
|
||||
1 -> { // Password protected
|
||||
itemToUnlockId = id
|
||||
itemToUnlockType = type
|
||||
showUnlockPasswordDialog = true
|
||||
setItemToUnlockId(id)
|
||||
setItemToUnlockType(type)
|
||||
setShowUnlockPasswordDialog(true)
|
||||
}
|
||||
2 -> { // Biometric protected
|
||||
biometricAuthenticator.promptBiometricAuth(
|
||||
@@ -684,25 +684,25 @@ fun AppShell(
|
||||
fragmentActivity = context.findActivity() as FragmentActivity,
|
||||
onSuccess = { _ ->
|
||||
when (type) {
|
||||
is Screen.ShoppingListDetail -> selectedListId = id
|
||||
is Screen.NoteDetail -> selectedNoteId = id
|
||||
is Screen.RecipeDetail -> selectedRecipeId = id
|
||||
is Screen.ShoppingListDetail -> setSelectedListId(id)
|
||||
is Screen.NoteDetail -> setSelectedNoteId(id)
|
||||
is Screen.RecipeDetail -> setSelectedRecipeId(id)
|
||||
else -> {}
|
||||
}
|
||||
itemWasInitiallyLocked = true
|
||||
currentScreen = type
|
||||
setItemWasInitiallyLocked(true)
|
||||
setCurrentScreen(type)
|
||||
},
|
||||
onFailed = {
|
||||
// Biometric failed, offer password as fallback if desired
|
||||
itemToUnlockId = id
|
||||
itemToUnlockType = type
|
||||
showUnlockPasswordDialog = true
|
||||
setItemToUnlockId(id)
|
||||
setItemToUnlockType(type)
|
||||
setShowUnlockPasswordDialog(true)
|
||||
},
|
||||
onError = { _, _ ->
|
||||
// Biometric error, offer password as fallback if desired
|
||||
itemToUnlockId = id
|
||||
itemToUnlockType = type
|
||||
showUnlockPasswordDialog = true
|
||||
setItemToUnlockId(id)
|
||||
setItemToUnlockType(type)
|
||||
setShowUnlockPasswordDialog(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -716,7 +716,7 @@ fun AppShell(
|
||||
)
|
||||
.collectAsState(initial = null)
|
||||
|
||||
var shoppingListsTitle by remember {
|
||||
val (shoppingListsTitle, setShoppingListsTitle) = remember {
|
||||
mutableStateOf(
|
||||
sharedPrefs.getString(
|
||||
"shopping_lists_title",
|
||||
@@ -724,9 +724,9 @@ fun AppShell(
|
||||
) ?: context.getString(R.string.menu_shopping_lists)
|
||||
)
|
||||
}
|
||||
var showRenameDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val (showRenameDialog, setShowRenameDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var recipesTitle by remember {
|
||||
val (recipesTitle, setRecipesTitle) = remember {
|
||||
mutableStateOf(
|
||||
sharedPrefs.getString(
|
||||
"recipes_title",
|
||||
@@ -734,9 +734,9 @@ fun AppShell(
|
||||
) ?: context.getString(R.string.menu_recipes)
|
||||
)
|
||||
}
|
||||
var showRecipeRenameDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val (showRecipeRenameDialog, setShowRecipeRenameDialog) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
var startScreen by remember {
|
||||
val (startScreen, setStartScreen) = remember {
|
||||
mutableStateOf(
|
||||
sharedPrefs.getString(
|
||||
"default_start_screen",
|
||||
@@ -764,7 +764,7 @@ fun AppShell(
|
||||
shoppingListsViewModel.importListFromTxt(listName, content)
|
||||
}
|
||||
}
|
||||
showListDialog = false // Close dialog on success
|
||||
setShowListDialog(false) // Close dialog on success
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -942,19 +942,19 @@ fun AppShell(
|
||||
AppDrawer(
|
||||
drawerState = drawerState,
|
||||
currentScreen = currentScreen,
|
||||
onScreenChange = { currentScreen = it },
|
||||
onScreenChange = { setCurrentScreen(it) },
|
||||
shoppingListsViewModel = shoppingListsViewModel,
|
||||
notesViewModel = notesViewModel,
|
||||
shoppingListsTitle = shoppingListsTitle,
|
||||
recipesTitle = recipesTitle,
|
||||
onShowRenameDialog = { showRenameDialog = true },
|
||||
onShowRecipeRenameDialog = { showRecipeRenameDialog = true },
|
||||
onShowJsonDialog = { showJsonDialog = true },
|
||||
onShowRenameDialog = { setShowRenameDialog(true) },
|
||||
onShowRecipeRenameDialog = { setShowRecipeRenameDialog(true) },
|
||||
onShowJsonDialog = { setShowJsonDialog(true) },
|
||||
syncFolderUriString = syncFolderUriString,
|
||||
onShowDeleteConfirmationDialog = { showDeleteConfirmationDialog = true },
|
||||
onShowDeleteConfirmationDialog = { setShowDeleteConfirmationDialog(true) },
|
||||
onLaunchSyncFolderChooser = { syncFolderLauncher.launch(null) },
|
||||
onSetSelectedListId = { selectedListId = it },
|
||||
onSetSelectedNoteId = { selectedNoteId = it }
|
||||
onSetSelectedListId = { setSelectedListId(it) },
|
||||
onSetSelectedNoteId = { setSelectedNoteId(it) }
|
||||
)
|
||||
}
|
||||
) {
|
||||
@@ -965,31 +965,31 @@ fun AppShell(
|
||||
topBar = {
|
||||
AppTopBar(
|
||||
currentScreen = currentScreen,
|
||||
onScreenChange = { currentScreen = it },
|
||||
onScreenChange = { setCurrentScreen(it) },
|
||||
drawerState = drawerState,
|
||||
shoppingListsViewModel = shoppingListsViewModel,
|
||||
notesViewModel = notesViewModel,
|
||||
recipesViewModel = recipesViewModel,
|
||||
shoppingListsTitle = shoppingListsTitle,
|
||||
recipesTitle = recipesTitle,
|
||||
onShowListDialog = { showListDialog = true },
|
||||
onShowNoteDialog = { showNoteDialog = true },
|
||||
onShowRecipeDialog = { showRecipeDialog = true },
|
||||
onShowListDialog = { setShowListDialog(true) },
|
||||
onShowNoteDialog = { setShowNoteDialog(true) },
|
||||
onShowRecipeDialog = { setShowRecipeDialog(true) },
|
||||
selectedListId = selectedListId,
|
||||
onSetSelectedListId = { selectedListId = it },
|
||||
onSetSelectedNoteId = { selectedNoteId = it },
|
||||
onSetSelectedRecipeId = { selectedRecipeId = it },
|
||||
onSetSelectedListId = { setSelectedListId(it) },
|
||||
onSetSelectedNoteId = { setSelectedNoteId(it) },
|
||||
onSetSelectedRecipeId = { setSelectedRecipeId(it) },
|
||||
exportLauncher = exportLauncher,
|
||||
noteExportLauncher = noteExportLauncher,
|
||||
recipeExportLauncher = recipeExportLauncher,
|
||||
hasEncryptionPassword = hasEncryptionPassword,
|
||||
onShowSetPasswordDialog = { showSetPasswordDialog = true },
|
||||
onShowSetRecipePasswordDialog = { showSetRecipePasswordDialog = true },
|
||||
onShowSetListPasswordDialog = { showSetListPasswordDialog = true },
|
||||
onShowSetPasswordDialog = { setShowSetPasswordDialog(true) },
|
||||
onShowSetRecipePasswordDialog = { setShowSetRecipePasswordDialog(true) },
|
||||
onShowSetListPasswordDialog = { setShowSetListPasswordDialog(true) },
|
||||
onShowChooseLockMethodDialog = { type, id ->
|
||||
itemToLockType = type
|
||||
itemToLockId = id
|
||||
showChooseLockMethodDialog = true
|
||||
setItemToLockType(type)
|
||||
setItemToLockId(id)
|
||||
setShowChooseLockMethodDialog(true)
|
||||
}
|
||||
)
|
||||
},
|
||||
@@ -1073,19 +1073,19 @@ fun AppShell(
|
||||
AppContent(
|
||||
innerPadding = innerPadding,
|
||||
currentScreen = currentScreen,
|
||||
onScreenChange = { currentScreen = it },
|
||||
onScreenChange = { setCurrentScreen(it) },
|
||||
notesViewModel = notesViewModel,
|
||||
shoppingListsViewModel = shoppingListsViewModel,
|
||||
recipesViewModel = recipesViewModel,
|
||||
selectedListId = selectedListId,
|
||||
onSetSelectedListId = { selectedListId = it },
|
||||
onShowListDialog = { showListDialog = true },
|
||||
onSetSelectedListId = { setSelectedListId(it) },
|
||||
onShowListDialog = { setShowListDialog(true) },
|
||||
selectedNoteId = selectedNoteId,
|
||||
onSetSelectedNoteId = { selectedNoteId = it },
|
||||
onShowNoteDialog = { showNoteDialog = true },
|
||||
onSetSelectedNoteId = { setSelectedNoteId(it) },
|
||||
onShowNoteDialog = { setShowNoteDialog(true) },
|
||||
selectedRecipeId = selectedRecipeId,
|
||||
onSetSelectedRecipeId = { selectedRecipeId = it },
|
||||
onShowRecipeDialog = { showRecipeDialog = true },
|
||||
onSetSelectedRecipeId = { setSelectedRecipeId(it) },
|
||||
onShowRecipeDialog = { setShowRecipeDialog(true) },
|
||||
itemWasInitiallyLocked = itemWasInitiallyLocked,
|
||||
onUnlockItem = onUnlockItem,
|
||||
shoppingListsTitle = shoppingListsTitle,
|
||||
@@ -1102,16 +1102,16 @@ fun AppShell(
|
||||
sharedPrefs = sharedPrefs,
|
||||
onResetShoppingListsTitle = {
|
||||
sharedPrefs.edit { remove("shopping_lists_title") }
|
||||
shoppingListsTitle = context.getString(R.string.menu_shopping_lists)
|
||||
setShoppingListsTitle(context.getString(R.string.menu_shopping_lists))
|
||||
},
|
||||
onResetRecipesTitle = {
|
||||
sharedPrefs.edit { remove("recipes_title") }
|
||||
recipesTitle = context.getString(R.string.menu_recipes)
|
||||
setRecipesTitle(context.getString(R.string.menu_recipes))
|
||||
},
|
||||
startScreen = startScreen,
|
||||
onStartScreenChange = {
|
||||
sharedPrefs.edit { putString("default_start_screen", it) }
|
||||
startScreen = it
|
||||
setStartScreen(it)
|
||||
},
|
||||
canUseBiometrics = canUseBiometrics,
|
||||
webAppIntegrationViewModel = webAppIntegrationViewModel,
|
||||
@@ -1125,7 +1125,7 @@ fun AppShell(
|
||||
shoppingListsViewModel = shoppingListsViewModel,
|
||||
recipesViewModel = recipesViewModel,
|
||||
showListDialog = showListDialog,
|
||||
onShowListDialogChange = { showListDialog = it },
|
||||
onShowListDialogChange = { setShowListDialog(it) },
|
||||
listDetails = listDetails,
|
||||
onListDetailsChange = { shoppingListsViewModel.updateListDetails(it) },
|
||||
onSaveList = { scope.launch { shoppingListsViewModel.saveList() } },
|
||||
@@ -1133,71 +1133,71 @@ fun AppShell(
|
||||
onSetListProtection = { password ->
|
||||
selectedListId?.let { listId ->
|
||||
shoppingListsViewModel.setProtection(listId, password)
|
||||
currentScreen = Screen.ShoppingLists
|
||||
setCurrentScreen(Screen.ShoppingLists)
|
||||
}
|
||||
selectedListId = null
|
||||
showSetListPasswordDialog = false
|
||||
setSelectedListId(null)
|
||||
setShowSetListPasswordDialog(false)
|
||||
},
|
||||
|
||||
onSetListProtectionBiometric = { id ->
|
||||
shoppingListsViewModel.setProtectionBiometric(id)
|
||||
currentScreen = Screen.ShoppingLists
|
||||
setCurrentScreen(Screen.ShoppingLists)
|
||||
},
|
||||
txtImportLauncher = txtImportLauncher,
|
||||
showNoteDialog = showNoteDialog,
|
||||
onShowNoteDialogChange = { showNoteDialog = it },
|
||||
onShowNoteDialogChange = { setShowNoteDialog(it) },
|
||||
noteDetails = noteDetails,
|
||||
onNoteDetailsChange = { notesViewModel.updateNoteDetails(it) },
|
||||
onSaveNote = { scope.launch { notesViewModel.saveNote() } },
|
||||
onResetNoteDetails = { notesViewModel.resetNoteDetails() },
|
||||
onSetNoteProtectionBiometric = { id ->
|
||||
notesViewModel.setProtectionBiometric(id)
|
||||
currentScreen = Screen.Notes
|
||||
setCurrentScreen(Screen.Notes)
|
||||
},
|
||||
noteImportLauncher = noteImportLauncher,
|
||||
showRecipeDialog = showRecipeDialog,
|
||||
onShowRecipeDialogChange = { showRecipeDialog = it },
|
||||
onShowRecipeDialogChange = { setShowRecipeDialog(it) },
|
||||
recipeDetails = recipeDetails,
|
||||
onRecipeDetailsChange = { recipesViewModel.updateRecipeDetails(it) },
|
||||
onSaveRecipe = { scope.launch { recipesViewModel.saveRecipe() } },
|
||||
onResetRecipeDetails = { recipesViewModel.resetRecipeDetails() },
|
||||
onSetRecipeProtectionBiometric = { id ->
|
||||
recipesViewModel.setProtectionBiometric(id)
|
||||
currentScreen = Screen.Recipes
|
||||
setCurrentScreen(Screen.Recipes)
|
||||
},
|
||||
recipeImportLauncher = recipeImportLauncher,
|
||||
recipesTitle = recipesTitle,
|
||||
showJsonDialog = showJsonDialog,
|
||||
onShowJsonDialogChange = { showJsonDialog = it },
|
||||
onShowJsonDialogChange = { setShowJsonDialog(it) },
|
||||
shoppingListsTitle = shoppingListsTitle,
|
||||
fileEncryptor = fileEncryptor,
|
||||
keyManager = keyManager,
|
||||
biometricAuthenticator = biometricAuthenticator,
|
||||
showSetPasswordDialog = showSetPasswordDialog,
|
||||
onShowSetPasswordDialogChange = { showSetPasswordDialog = it },
|
||||
onShowSetPasswordDialogChange = { setShowSetPasswordDialog(it) },
|
||||
onSetNotePassword = { password ->
|
||||
notesViewModel.setProtectionPassword(password)
|
||||
selectedNoteId = null
|
||||
showSetPasswordDialog = false
|
||||
currentScreen = Screen.Notes
|
||||
setSelectedNoteId(null)
|
||||
setShowSetPasswordDialog(false)
|
||||
setCurrentScreen(Screen.Notes)
|
||||
},
|
||||
showSetRecipePasswordDialog = showSetRecipePasswordDialog,
|
||||
onShowSetRecipePasswordDialogChange = { showSetRecipePasswordDialog = it },
|
||||
onShowSetRecipePasswordDialogChange = { setShowSetRecipePasswordDialog(it) },
|
||||
onSetRecipePassword = { password ->
|
||||
recipesViewModel.setProtectionPassword(password)
|
||||
selectedRecipeId = null
|
||||
showSetRecipePasswordDialog = false
|
||||
currentScreen = Screen.Recipes
|
||||
setSelectedRecipeId(null)
|
||||
setShowSetRecipePasswordDialog(false)
|
||||
setCurrentScreen(Screen.Recipes)
|
||||
},
|
||||
showSetListPasswordDialog = showSetListPasswordDialog,
|
||||
onShowSetListPasswordDialogChange = { showSetListPasswordDialog = it },
|
||||
onShowSetListPasswordDialogChange = { setShowSetListPasswordDialog(it) },
|
||||
showUnlockPasswordDialog = showUnlockPasswordDialog,
|
||||
onShowUnlockPasswordDialogChange = {
|
||||
showUnlockPasswordDialog = it
|
||||
setShowUnlockPasswordDialog(it)
|
||||
if (!it) {
|
||||
unlockErrorMessage = null
|
||||
itemToUnlockId = null
|
||||
itemToUnlockType = null
|
||||
setUnlockErrorMessage(null)
|
||||
setItemToUnlockId(null)
|
||||
setItemToUnlockType(null)
|
||||
}
|
||||
},
|
||||
onUnlock = { password ->
|
||||
@@ -1209,44 +1209,44 @@ fun AppShell(
|
||||
val note = notesViewModel.uiState.value.noteList.find { it.id == itemToUnlockId }
|
||||
if (note != null && note.protectionHash == hashedPassword) {
|
||||
isPasswordCorrect = true
|
||||
selectedNoteId = itemToUnlockId
|
||||
setSelectedNoteId(itemToUnlockId)
|
||||
}
|
||||
}
|
||||
Screen.RecipeDetail -> {
|
||||
val recipe = recipesViewModel.uiState.value.recipeList.find { it.id == itemToUnlockId }
|
||||
if (recipe != null && recipe.protectionHash == hashedPassword) {
|
||||
isPasswordCorrect = true
|
||||
selectedRecipeId = itemToUnlockId
|
||||
setSelectedRecipeId(itemToUnlockId)
|
||||
}
|
||||
}
|
||||
Screen.ShoppingListDetail -> {
|
||||
val listWithItems = shoppingListsViewModel.uiState.value.shoppingLists.find { it.shoppingList.id == itemToUnlockId }
|
||||
if (listWithItems != null && listWithItems.shoppingList.protectionHash == hashedPassword) {
|
||||
isPasswordCorrect = true
|
||||
selectedListId = itemToUnlockId
|
||||
setSelectedListId(itemToUnlockId)
|
||||
}
|
||||
}
|
||||
else -> {} // Should not happen
|
||||
}
|
||||
|
||||
if (isPasswordCorrect) {
|
||||
unlockErrorMessage = null
|
||||
itemWasInitiallyLocked = true // Mark as initially locked for content decryption
|
||||
currentScreen = itemToUnlockType!!
|
||||
itemToUnlockId = null
|
||||
itemToUnlockType = null
|
||||
showUnlockPasswordDialog = false
|
||||
setUnlockErrorMessage(null)
|
||||
setItemWasInitiallyLocked(true) // Mark as initially locked for content decryption
|
||||
setCurrentScreen(itemToUnlockType!!)
|
||||
setItemToUnlockId(null)
|
||||
setItemToUnlockType(null)
|
||||
setShowUnlockPasswordDialog(false)
|
||||
} else {
|
||||
unlockErrorMessage = context.getString(R.string.incorrect_password)
|
||||
setUnlockErrorMessage(context.getString(R.string.incorrect_password))
|
||||
}
|
||||
},
|
||||
unlockErrorMessage = unlockErrorMessage,
|
||||
showPasswordDialog = showPasswordDialog,
|
||||
onShowPasswordDialogChange = { showPasswordDialog = it },
|
||||
onHasEncryptionPasswordChange = { hasEncryptionPassword = it },
|
||||
onShowPasswordDialogChange = { setShowPasswordDialog(it) },
|
||||
onHasEncryptionPasswordChange = { setHasEncryptionPassword(it) },
|
||||
sharedPrefs = sharedPrefs,
|
||||
showDeleteConfirmationDialog = showDeleteConfirmationDialog,
|
||||
onShowDeleteConfirmationDialogChange = { showDeleteConfirmationDialog = it },
|
||||
onShowDeleteConfirmationDialogChange = { setShowDeleteConfirmationDialog(it) },
|
||||
onDeleteSyncFolder = {
|
||||
val syncFolderUriString = sharedPrefs.getString("sync_folder_uri", null)
|
||||
if (syncFolderUriString != null) {
|
||||
@@ -1259,19 +1259,19 @@ fun AppShell(
|
||||
}
|
||||
},
|
||||
showRenameDialog = showRenameDialog,
|
||||
onShowRenameDialogChange = { showRenameDialog = it },
|
||||
onShowRenameDialogChange = { setShowRenameDialog(it) },
|
||||
onRenameShoppingListsTitle = { newTitle ->
|
||||
sharedPrefs.edit { putString("shopping_lists_title", newTitle) }
|
||||
shoppingListsTitle = newTitle
|
||||
setShoppingListsTitle(newTitle)
|
||||
},
|
||||
showRecipeRenameDialog = showRecipeRenameDialog,
|
||||
onShowRecipeRenameDialogChange = { showRecipeRenameDialog = it },
|
||||
onShowRecipeRenameDialogChange = { setShowRecipeRenameDialog(it) },
|
||||
onRenameRecipesTitle = { newTitle ->
|
||||
sharedPrefs.edit { putString("recipes_title", newTitle) }
|
||||
recipesTitle = newTitle
|
||||
setRecipesTitle(newTitle)
|
||||
},
|
||||
showChooseLockMethodDialog = showChooseLockMethodDialog,
|
||||
onShowChooseLockMethodDialogChange = { showChooseLockMethodDialog = it },
|
||||
onShowChooseLockMethodDialogChange = { setShowChooseLockMethodDialog(it) },
|
||||
onConfirmLockMethod = { lockMethod ->
|
||||
itemToLockType?.let { type ->
|
||||
itemToLockId?.let { id ->
|
||||
@@ -1279,63 +1279,63 @@ fun AppShell(
|
||||
LockableItemType.SHOPPING_LIST -> {
|
||||
when (lockMethod) {
|
||||
LockMethod.PASSWORD -> {
|
||||
selectedListId = id
|
||||
showSetListPasswordDialog = true
|
||||
setSelectedListId(id)
|
||||
setShowSetListPasswordDialog(true)
|
||||
}
|
||||
LockMethod.BIOMETRIC -> {
|
||||
shoppingListsViewModel.setProtectionBiometric(id)
|
||||
currentScreen = Screen.ShoppingLists
|
||||
setCurrentScreen(Screen.ShoppingLists)
|
||||
}
|
||||
}
|
||||
}
|
||||
LockableItemType.NOTE -> {
|
||||
when (lockMethod) {
|
||||
LockMethod.PASSWORD -> {
|
||||
selectedNoteId = id
|
||||
showSetPasswordDialog = true
|
||||
setSelectedNoteId(id)
|
||||
setShowSetPasswordDialog(true)
|
||||
}
|
||||
LockMethod.BIOMETRIC -> {
|
||||
notesViewModel.setProtectionBiometric(id)
|
||||
currentScreen = Screen.Notes
|
||||
setCurrentScreen(Screen.Notes)
|
||||
}
|
||||
}
|
||||
}
|
||||
LockableItemType.RECIPE -> {
|
||||
when (lockMethod) {
|
||||
LockMethod.PASSWORD -> {
|
||||
selectedRecipeId = id
|
||||
showSetRecipePasswordDialog = true
|
||||
setSelectedRecipeId(id)
|
||||
setShowSetRecipePasswordDialog(true)
|
||||
}
|
||||
LockMethod.BIOMETRIC -> {
|
||||
recipesViewModel.setProtectionBiometric(id)
|
||||
currentScreen = Screen.Recipes
|
||||
setCurrentScreen(Screen.Recipes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
showChooseLockMethodDialog = false
|
||||
itemToLockId = null
|
||||
itemToLockType = null
|
||||
setShowChooseLockMethodDialog(false)
|
||||
setItemToLockId(null)
|
||||
setItemToLockType(null)
|
||||
},
|
||||
canUseBiometrics = canUseBiometrics,
|
||||
itemToLockType = itemToLockType,
|
||||
showStartupPasswordDialog = showStartupPasswordDialog,
|
||||
onShowStartupPasswordDialogChange = { showStartupPasswordDialog = it },
|
||||
onShowStartupPasswordDialogChange = { setShowStartupPasswordDialog(it) },
|
||||
onUnlockEncryption = { password ->
|
||||
scope.launch {
|
||||
try {
|
||||
val decryptionCipher = keyManager.getDecryptionCipher()
|
||||
if (decryptionCipher == null) {
|
||||
startupUnlockErrorMessage = context.getString(R.string.unlock_failed)
|
||||
setStartupUnlockErrorMessage(context.getString(R.string.unlock_failed))
|
||||
return@launch
|
||||
}
|
||||
|
||||
val sharedPrefs = context.getSharedPreferences("encryption_prefs", Context.MODE_PRIVATE)
|
||||
val encryptedDerivedKeyString = sharedPrefs.getString("encrypted_derived_key", null)
|
||||
if (encryptedDerivedKeyString == null) {
|
||||
startupUnlockErrorMessage = context.getString(R.string.unlock_failed)
|
||||
setStartupUnlockErrorMessage(context.getString(R.string.unlock_failed))
|
||||
return@launch
|
||||
}
|
||||
val encryptedDerivedKey = Base64.decode(encryptedDerivedKeyString, Base64.DEFAULT)
|
||||
@@ -1348,19 +1348,19 @@ fun AppShell(
|
||||
Log.d("AppShell_Unlock", "Current PBE Key: ${currentPbeKey.encoded.joinToString()}")
|
||||
|
||||
if (decryptedStoredKeyBytes.contentEquals(currentPbeKey.encoded)) {
|
||||
secretKey = SecretKeySpec(decryptedStoredKeyBytes, "AES")
|
||||
isDecryptionAttempted = true
|
||||
showStartupPasswordDialog = false
|
||||
startupUnlockErrorMessage = null
|
||||
setSecretKey(SecretKeySpec(decryptedStoredKeyBytes, "AES"))
|
||||
setIsDecryptionAttempted(true)
|
||||
setShowStartupPasswordDialog(false)
|
||||
setStartupUnlockErrorMessage(null)
|
||||
} else {
|
||||
startupUnlockErrorMessage = context.getString(R.string.incorrect_password)
|
||||
setStartupUnlockErrorMessage(context.getString(R.string.incorrect_password))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("AppShell", "Failed to unlock encryption with password", e)
|
||||
startupUnlockErrorMessage = context.getString(R.string.unlock_failed)
|
||||
setStartupUnlockErrorMessage(context.getString(R.string.unlock_failed))
|
||||
}
|
||||
}
|
||||
},
|
||||
startupUnlockErrorMessage = startupUnlockErrorMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user