Deleting images from the file system when the recipe gets deleted and a nice fade animation for previews with multiple images

master
Wynd 2025-12-21 14:53:15 +02:00
parent 519bbd4a89
commit 06949f0f8d
3 changed files with 93 additions and 32 deletions

View File

@ -25,8 +25,8 @@ data class Recipe(
val content: String, val content: String,
val hash: Int val hash: Int
) { ) {
fun previewImage(ctx: Context): Bitmap? { fun previewImage(ctx: Context, idx: Int): Bitmap? {
return showImage(ctx, 0) return showImage(ctx, idx)
} }
fun showImage(ctx: Context, idx: Int): Bitmap? { fun showImage(ctx: Context, idx: Int): Bitmap? {

View File

@ -33,6 +33,7 @@ import xyz.pixelatedw.recipe.MainActivity
import xyz.pixelatedw.recipe.R import xyz.pixelatedw.recipe.R
import xyz.pixelatedw.recipe.data.RecipeWithTags import xyz.pixelatedw.recipe.data.RecipeWithTags
import xyz.pixelatedw.recipe.data.RecipesView import xyz.pixelatedw.recipe.data.RecipesView
import java.io.File
@Composable @Composable
fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) { fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
@ -140,9 +141,8 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
LazyColumn { LazyColumn {
items(recipes.value) { entry -> items(recipes.value) { entry ->
if (isInSearch(entry)) { if (isInSearch(entry)) {
val previewUri = entry.recipe.previewImage(LocalContext.current)
val isSelected = selectedEntries.value.contains(entry) val isSelected = selectedEntries.value.contains(entry)
RecipePreview(entry, previewUri, isSelected, onClick = { RecipePreview(entry, isSelected, onClick = {
view.setActive(entry) view.setActive(entry)
navController.navigate("info") navController.navigate("info")
}, onSelected = { flag -> }, onSelected = { flag ->
@ -167,6 +167,13 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
// TODO I feel like this could be done in batch or something...but I truly don't care atm // TODO I feel like this could be done in batch or something...but I truly don't care atm
for (entry in selectedEntries.value) { for (entry in selectedEntries.value) {
view.removeRecipe(entry) view.removeRecipe(entry)
// TODO This needs some refcounting or database stuff so it doesn't delete pics if they're used by multiple recipes, however this is not a problem atm
// Make sure to delete the pics from phone's storage so they don't waste space
for (picPath in entry.recipe.pics) {
File(ctx.filesDir, picPath).delete()
}
ctx.db.recipeDao().delete(entry.recipe) ctx.db.recipeDao().delete(entry.recipe)
ctx.db.recipeWithTagsDao().delete(entry.recipe.title) ctx.db.recipeWithTagsDao().delete(entry.recipe.title)
} }

View File

@ -1,9 +1,12 @@
package xyz.pixelatedw.recipe.ui.components package xyz.pixelatedw.recipe.ui.components
import android.graphics.Bitmap import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable 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
@ -14,52 +17,98 @@ 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.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color 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.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.imageResource import androidx.compose.ui.res.imageResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em import androidx.compose.ui.unit.em
import kotlinx.coroutines.delay
import xyz.pixelatedw.recipe.R import xyz.pixelatedw.recipe.R
import xyz.pixelatedw.recipe.data.Recipe
import xyz.pixelatedw.recipe.data.RecipeWithTags import xyz.pixelatedw.recipe.data.RecipeWithTags
import kotlin.time.Duration.Companion.seconds
@Composable @Composable
fun RecipePreview(entry: RecipeWithTags, previewUri: Bitmap?, isSelected: Boolean, onClick: () -> Unit, onSelected: (Boolean) -> Unit) { fun RecipePreview(
Column(modifier = Modifier entry: RecipeWithTags,
.background(color = if(isSelected) Color(0x11FF0000) else Color(0x00FFFFFF) ) isSelected: Boolean,
onClick: () -> Unit,
onSelected: (Boolean) -> Unit
) {
val availablePics = entry.recipe.pics.size
var displayImageId by remember { mutableIntStateOf(0) }
if (availablePics > 0) {
LaunchedEffect(Unit) {
while (true) {
delay(5.seconds)
displayImageId = (displayImageId + 1) % availablePics
}
}
}
val displayImage = entry.recipe.previewImage(LocalContext.current, displayImageId)
Column(
modifier = Modifier
.background(color = if (isSelected) Color(0x11FF0000) else Color(0x00FFFFFF))
.padding(8.dp) .padding(8.dp)
.combinedClickable( .combinedClickable(
onLongClick = { onSelected(!isSelected) }, onLongClick = { onSelected(!isSelected) },
onClick = onClick onClick = onClick
)) { )
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().height(256.dp)) { ) {
if (previewUri != null) { Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.height(256.dp)
) {
if (displayImage != null) {
AnimatedContent(
displayImage,
label = "Recipe Preview",
transitionSpec = {
fadeIn(animationSpec = tween(500))
.togetherWith(fadeOut(animationSpec = tween(500)))
}
) { targetImage ->
Image( Image(
bitmap = previewUri.asImageBitmap(), bitmap = targetImage.asImageBitmap(),
contentDescription = "Recipe image", contentDescription = "Recipe image",
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp).padding(top = 16.dp, bottom = 8.dp), modifier = Modifier
.size(256.dp)
.padding(top = 16.dp, bottom = 8.dp),
) )
} }
else { } else {
Image( Image(
bitmap = ImageBitmap.imageResource(R.drawable.missing_image), bitmap = ImageBitmap.imageResource(R.drawable.missing_image),
contentDescription = "Missing recipe image", contentDescription = "Missing recipe image",
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp).padding(top = 16.dp, bottom = 8.dp), modifier = Modifier
.size(256.dp)
.padding(top = 16.dp, bottom = 8.dp),
) )
} }
} }
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) { Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
) {
Text( Text(
text = entry.recipe.title, text = entry.recipe.title,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -69,7 +118,12 @@ fun RecipePreview(entry: RecipeWithTags, previewUri: Bitmap?, isSelected: Boolea
) )
) )
} }
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)) { Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
for (tag in entry.tags) { for (tag in entry.tags) {
Tag(tag) Tag(tag)
} }