Added long press toggles for easier mass deletion

master
Wynd 2025-12-21 00:58:56 +02:00
parent a11e8ac0da
commit a7d961393f
3 changed files with 60 additions and 42 deletions

View File

@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@ -20,6 +21,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
@ -41,6 +43,9 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
val navController = rememberNavController() val navController = rememberNavController()
val openTagFilterDialog = remember { mutableStateOf(false) }
val selectedEntries = remember { mutableStateOf(listOf<RecipeWithTags>()) }
val isInSearch = isInSearch@{ entry: RecipeWithTags -> val isInSearch = isInSearch@{ entry: RecipeWithTags ->
val isSearchEmpty = search.value == null || search.value!!.isEmpty() val isSearchEmpty = search.value == null || search.value!!.isEmpty()
val hasTitle = entry.recipe.title.contains(search.value.orEmpty(), ignoreCase = true) val hasTitle = entry.recipe.title.contains(search.value.orEmpty(), ignoreCase = true)
@ -52,7 +57,7 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
return@isInSearch false return@isInSearch false
} }
val totalFilters = filters.value.stream().filter{ f -> f.checked }.count() val totalFilters = filters.value.stream().filter { f -> f.checked }.count()
var checkedFilters = 0 var checkedFilters = 0
for (filter in filters.value) { for (filter in filters.value) {
@ -61,10 +66,9 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
.filter { tag -> tag.name.contains(filter.tag, ignoreCase = true) } .filter { tag -> tag.name.contains(filter.tag, ignoreCase = true) }
.count() > 0 .count() > 0
if(hasTagFilters) { if (hasTagFilters) {
checkedFilters += 1 checkedFilters += 1
} } else {
else {
checkedFilters -= 1 checkedFilters -= 1
} }
} }
@ -78,8 +82,6 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
true true
} }
val openTagFilterDialog = remember { mutableStateOf(false) }
NavHost( NavHost(
navController = navController, navController = navController,
startDestination = "list" startDestination = "list"
@ -108,24 +110,55 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
contentDescription = "Toggle tags filtering menu" contentDescription = "Toggle tags filtering menu"
) )
} }
// Load // Load / Delete
IconButton( if (selectedEntries.value.isNotEmpty()) {
modifier = Modifier.weight(0.15f), IconButton(
onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }, modifier = Modifier.weight(0.15f),
) { onClick = {
Icon( // TODO I feel like this could be done in batch or something...but I truly don't care atm
imageVector = Icons.Default.Add, for (entry in selectedEntries.value) {
contentDescription = "Load recipes from filesystem" view.removeRecipe(entry)
) ctx.db.recipeDao().delete(entry.recipe)
ctx.db.recipeWithTagsDao().delete(entry.recipe.title)
}
},
) {
Icon(
imageVector = Icons.Default.Delete,
tint = Color(0xFFFF0000),
contentDescription = "Load recipes from filesystem"
)
}
}
else {
IconButton(
modifier = Modifier.weight(0.15f),
onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) },
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Load recipes from filesystem"
)
}
} }
} }
LazyColumn { LazyColumn {
items(recipes.value) { entry -> items(recipes.value) { entry ->
if (isInSearch(entry)) { if (isInSearch(entry)) {
val previewUri = entry.recipe.previewImage(LocalContext.current) val previewUri = entry.recipe.previewImage(LocalContext.current)
RecipePreview(entry, previewUri, onClick = { val isSelected = selectedEntries.value.contains(entry)
RecipePreview(entry, previewUri, isSelected, onClick = {
view.setActive(entry) view.setActive(entry)
navController.navigate("info") navController.navigate("info")
}, onSelected = { flag ->
selectedEntries.value =
selectedEntries.value.toMutableList().apply {
if (flag) {
add(entry)
} else {
remove(entry)
}
}.toList()
}) })
} }
} }

View File

@ -136,30 +136,6 @@ fun RecipeInfo(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
) )
} }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(end = 16.dp),
horizontalArrangement = Arrangement.End
) {
Box(
modifier = Modifier
.fillMaxHeight()
.padding(start = 16.dp)
) {
Button(
onClick = {
openDeletionDialog.value = true
},
colors = ButtonDefaults.buttonColors(containerColor = Color.Red)
) {
Text(
text = "Delete",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
} }
Box( Box(

View File

@ -2,7 +2,9 @@ package xyz.pixelatedw.recipe.ui.components
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -12,7 +14,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -28,10 +33,14 @@ import xyz.pixelatedw.recipe.data.Recipe
import xyz.pixelatedw.recipe.data.RecipeWithTags import xyz.pixelatedw.recipe.data.RecipeWithTags
@Composable @Composable
fun RecipePreview(entry: RecipeWithTags, previewUri: Bitmap?, onClick: () -> Unit) { fun RecipePreview(entry: RecipeWithTags, previewUri: Bitmap?, isSelected: Boolean, onClick: () -> Unit, onSelected: (Boolean) -> Unit) {
Column(modifier = Modifier Column(modifier = Modifier
.background(color = if(isSelected) Color(0x11FF0000) else Color(0x00FFFFFF) )
.padding(8.dp) .padding(8.dp)
.clickable(onClick = onClick)) { .combinedClickable(
onLongClick = { onSelected(!isSelected) },
onClick = onClick
)) {
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().height(256.dp)) { Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().height(256.dp)) {
if (previewUri != null) { if (previewUri != null) {
Image( Image(