feat: Mark single item in web app

When an item was marked as done in the mobile app, all items were marked as done in the web app. This was because the `importItemsFromWebApp` function always marked all fetched items as processed on the server.

This commit fixes this by:
- Removing the call to `markItems` from `importItemsFromWebApp`.
- Adding a new function `markItemInWebApp` that is called when a single item is tapped, which correctly synchronizes the state of the single item with the web app.
This commit is contained in:
2025-10-30 13:04:47 +01:00
parent 8037489986
commit 557af7fd9a
3 changed files with 50 additions and 5 deletions

View File

@@ -178,6 +178,11 @@ interface NoteshopRepository {
*/
suspend fun deleteMarkedItemsInWebApp(url: String, user: String, pass: String, deletePass: String)
/**
* Mark an item in the web app.
*/
suspend fun markItemInWebApp(url: String, user: String, pass: String, itemName: String)
}
class OfflineNoteshopRepository(
@@ -268,12 +273,14 @@ class OfflineNoteshopRepository(
if (newItems.isNotEmpty()) {
insertShoppingListItems(newItems)
}
// Always mark all fetched items as processed on the server
webAppClient.markItems(url, user, pass, itemsFromWeb)
}
override suspend fun deleteMarkedItemsInWebApp(url: String, user: String, pass: String, deletePass: String) {
webAppClient.deleteMarkedItems(url, user, pass, deletePass)
}
override suspend fun markItemInWebApp(url: String, user: String, pass: String, itemName: String) {
webAppClient.markItems(url, user, pass, listOf(itemName))
}
}

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material.icons.rounded.DragHandle
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -27,7 +28,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -46,7 +46,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import de.lxtools.noteshop.R
import de.lxtools.noteshop.data.ShoppingListItem
@@ -57,6 +56,7 @@ import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.text.withStyle
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -271,7 +271,9 @@ fun ShoppingListDetailScreen(
onLongPress = { viewModel.enableReorderMode() },
onTap = {
coroutineScope.launch {
viewModel.saveShoppingListItem(item.copy(isChecked = !item.isChecked), secretKey, fileEncryptor)
val updatedItem = item.copy(isChecked = !item.isChecked)
viewModel.saveShoppingListItem(updatedItem, secretKey, fileEncryptor)
viewModel.markItemInWebApp(item.name)
}
}
)

View File

@@ -473,6 +473,42 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository,
}
}
fun markItemInWebApp(itemName: String) {
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()) {
return@launch // Silently fail if not configured
}
val fileEncryptor = FileEncryptor()
val keyManager = KeyManager(getApplication(), false)
val secretKey = keyManager.derivePbeKey(keyPass.toCharArray())
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)
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)
}
}
}
fun deleteMarkedItemsInWebApp() {
viewModelScope.launch {
val webAppPrefs = getApplication<Application>().getSharedPreferences("webapp_prefs", Context.MODE_PRIVATE)