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:
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user