Compare commits

..

2 Commits

5 changed files with 111 additions and 55 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,7 +33,7 @@ fun DeleteRecipeDialog(onAccept: ( ) -> Unit, onDismiss: () -> Unit) {
modifier = Modifier.height(100.dp).wrapContentSize(Alignment.Center) modifier = Modifier.height(100.dp).wrapContentSize(Alignment.Center)
) { ) {
Text( Text(
text = "Do you really wish to permanently delete this recipe ?", text = "Do you really wish to permanently delete these recipes ?",
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
) )
} }

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) {
@ -43,6 +44,7 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
val navController = rememberNavController() val navController = rememberNavController()
val openDeletionDialog = remember { mutableStateOf(false) }
val openTagFilterDialog = remember { mutableStateOf(false) } val openTagFilterDialog = remember { mutableStateOf(false) }
val selectedEntries = remember { mutableStateOf(listOf<RecipeWithTags>()) } val selectedEntries = remember { mutableStateOf(listOf<RecipeWithTags>()) }
@ -115,12 +117,7 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
IconButton( IconButton(
modifier = Modifier.weight(0.15f), modifier = Modifier.weight(0.15f),
onClick = { onClick = {
// TODO I feel like this could be done in batch or something...but I truly don't care atm openDeletionDialog.value = true
for (entry in selectedEntries.value) {
view.removeRecipe(entry)
ctx.db.recipeDao().delete(entry.recipe)
ctx.db.recipeWithTagsDao().delete(entry.recipe.title)
}
}, },
) { ) {
Icon( Icon(
@ -129,8 +126,7 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
contentDescription = "Load recipes from filesystem" contentDescription = "Load recipes from filesystem"
) )
} }
} } else {
else {
IconButton( IconButton(
modifier = Modifier.weight(0.15f), modifier = Modifier.weight(0.15f),
onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) }, onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) },
@ -145,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 ->
@ -166,6 +161,29 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
} }
when { when {
openDeletionDialog.value -> {
DeleteRecipeDialog(
onAccept = {
// TODO I feel like this could be done in batch or something...but I truly don't care atm
for (entry in selectedEntries.value) {
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.recipeWithTagsDao().delete(entry.recipe.title)
}
selectedEntries.value = listOf()
openDeletionDialog.value = false
},
onDismiss = { openDeletionDialog.value = false }
)
}
openTagFilterDialog.value -> { openTagFilterDialog.value -> {
TagFilterDialog( TagFilterDialog(
onAccept = { onAccept = {

View File

@ -49,7 +49,6 @@ fun RecipeInfo(
active: RecipeWithTags active: RecipeWithTags
) { ) {
val keepScreen = view.keepScreenOn.collectAsState() val keepScreen = view.keepScreenOn.collectAsState()
val openDeletionDialog = remember { mutableStateOf(false) }
val picsCounts = remember { active.recipe.pics.size }; val picsCounts = remember { active.recipe.pics.size };
val timestamp = view.activeRecipe.collectAsState().value?.recipe?.lastModified ?: 0 val timestamp = view.activeRecipe.collectAsState().value?.recipe?.lastModified ?: 0
@ -149,19 +148,4 @@ fun RecipeInfo(
) )
} }
} }
when {
openDeletionDialog.value -> {
DeleteRecipeDialog(
onAccept = {
view.removeRecipe(active)
ctx.db.recipeDao().delete(active.recipe)
ctx.db.recipeWithTagsDao().delete(active.recipe.title)
openDeletionDialog.value = false
nav.popBackStack()
},
onDismiss = { openDeletionDialog.value = false }
)
}
}
} }

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,
.padding(8.dp) onClick: () -> Unit,
.combinedClickable( onSelected: (Boolean) -> Unit
onLongClick = { onSelected(!isSelected) }, ) {
onClick = onClick val availablePics = entry.recipe.pics.size
)) { var displayImageId by remember { mutableIntStateOf(0) }
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().height(256.dp)) { if (availablePics > 0) {
if (previewUri != null) { LaunchedEffect(Unit) {
Image( while (true) {
bitmap = previewUri.asImageBitmap(), delay(5.seconds)
contentDescription = "Recipe image", displayImageId = (displayImageId + 1) % availablePics
contentScale = ContentScale.Crop,
modifier = Modifier.size(256.dp).padding(top = 16.dp, bottom = 8.dp),
)
} }
else { }
}
val displayImage = entry.recipe.previewImage(LocalContext.current, displayImageId)
Column(
modifier = Modifier
.background(color = if (isSelected) Color(0x11FF0000) else Color(0x00FFFFFF))
.padding(8.dp)
.combinedClickable(
onLongClick = { onSelected(!isSelected) },
onClick = onClick
)
) {
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(
bitmap = targetImage.asImageBitmap(),
contentDescription = "Recipe image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(256.dp)
.padding(top = 16.dp, bottom = 8.dp),
)
}
} 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)
} }