Compare commits

..

3 Commits

6 changed files with 95 additions and 100 deletions

View File

@ -3,8 +3,8 @@ package xyz.pixelatedw.recipe.data
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.widget.ImageView
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Entity
@ -17,6 +17,9 @@ import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.io.File
private val imagesCache: HashMap<String, Bitmap> = hashMapOf()
@Entity
data class Recipe(
@PrimaryKey
@ -33,10 +36,17 @@ data class Recipe(
fun showImage(ctx: Context, idx: Int): Bitmap? {
val img = this.pics.getOrNull(idx)
if (img != null) {
val cachedImage = imagesCache[img]
if (cachedImage != null) {
return cachedImage
}
val file = File(ctx.filesDir, img)
if (file.exists()) {
ctx.contentResolver.openInputStream(file.toUri()).use {
return BitmapFactory.decodeStream(it)
val bitmapData = BitmapFactory.decodeStream(it)
imagesCache[img] = bitmapData
return bitmapData
}
}
}

View File

@ -44,20 +44,14 @@ class RecipesView : ViewModel() {
}
}
if (activeFilterNames.isNotEmpty()) {
_tagFilters.value = _tagFilters.value.map {
it.count = filtersMap[it.tag] ?: 0
it
}.toList()
} else {
val filters = filtersMap.map { TagFilter(it.key, count = it.value) }.toList()
_tagFilters.update { filters.distinct().sortedBy { it.tag } }
}
val filters = filtersMap.map { TagFilter(it.key, checked = activeFilterNames.contains(it.key), count = it.value) }
_tagFilters.update { filters.distinct().sortedBy { it.tag }.toList() }
}
fun setTagFilterState(tag: String, state: Boolean) {
_tagFilters.value = _tagFilters.value.toMutableList()
.apply { replaceAll { it -> if (tag == it.tag) it.checked = state; it } }.toList()
_tagFilters.value = _tagFilters.value.map { if (tag == it.tag) it.checked = state; it }
this.reloadTagFilterState()
}
fun setKeepScreenOn(flag: Boolean) {
@ -67,19 +61,7 @@ class RecipesView : ViewModel() {
fun setRecipes(recipes: List<RecipeWithTags>) {
_recipes.update { recipes.sortedBy { it.recipe.title } }
val filtersMap = mutableMapOf<String, Int>()
_recipes.value.stream()
.filter { it.tags.isNotEmpty() }
.forEach {
it.tags.forEach { tag ->
val count = filtersMap[tag.name] ?: 0
filtersMap[tag.name] = count + 1;
}
}
val filters = filtersMap.map { it -> TagFilter(it.key, count = it.value) }.toList()
_tagFilters.update { filters.distinct().sortedBy { it.tag } }
this.reloadTagFilterState()
}
fun removeRecipe(recipe: RecipeWithTags) {

View File

@ -4,10 +4,12 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Settings
@ -42,12 +44,12 @@ import java.io.File
@Composable
fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
val recipes = view.recipes.collectAsState()
val active = view.activeRecipe.collectAsState()
val search = view.search.collectAsState()
val filters = view.tagFilters.collectAsState()
val recipes by view.recipes.collectAsState()
val active by view.activeRecipe.collectAsState()
val search by view.search.collectAsState()
val filters by view.tagFilters.collectAsState()
var activeRecipes = remember { recipes.value }
var activeRecipes by remember { mutableStateOf(recipes) }
var activeTags = 0
val navController = rememberNavController()
@ -56,6 +58,8 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
var openTagFilterDialog by remember { mutableStateOf(false) }
var selectedEntries by remember { mutableStateOf(listOf<RecipeWithTags>()) }
val gridState = rememberLazyGridState()
NavHost(
navController = navController,
startDestination = "list"
@ -73,11 +77,11 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
modifier = Modifier
.weight(0.7f)
.padding(end = 4.dp),
value = search.value.orEmpty(),
value = search.orEmpty(),
onValueChange = { search ->
view.setSearch(search)
activeRecipes =
recipes.value.filter { filterRecipe(it, search, filters.value) }
recipes.filter { filterRecipe(it, search, filters) }
},
)
// Tags / Delete
@ -129,8 +133,18 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
)
}
}
LazyColumn {
items(activeRecipes) { entry ->
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
state = gridState,
) {
items(
count = activeRecipes.size,
key = { activeRecipes[it].recipe.title },
itemContent = { entryId ->
val entry = activeRecipes[entryId]
val isSelected = selectedEntries.contains(entry)
RecipePreview(entry, isSelected, onClick = {
view.setActive(entry)
@ -146,6 +160,7 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
}.toList()
})
}
)
}
}
@ -178,12 +193,12 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
onAccept = {
openTagFilterDialog = false
view.reloadTagFilterState()
activeTags = filters.value.count { f -> f.checked }
activeRecipes = recipes.value.filter {
activeTags = filters.count { f -> f.checked }
activeRecipes = recipes.filter {
filterRecipe(
it,
search.value,
filters.value
search,
filters
)
}
},
@ -193,7 +208,7 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
}
}
composable("info") {
RecipeInfo(ctx, view, navController, padding, active.value!!)
RecipeInfo(ctx, view, navController, padding, active!!)
}
composable("settings") {
SettingsScreen(ctx, navController)

View File

@ -11,10 +11,11 @@ import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@ -22,6 +23,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
@ -32,7 +34,6 @@ import androidx.compose.ui.res.imageResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import kotlinx.coroutines.delay
import xyz.pixelatedw.recipe.R
import xyz.pixelatedw.recipe.data.RecipeWithTags
@ -58,21 +59,20 @@ fun RecipePreview(
Column(
modifier = Modifier
.background(color = if (isSelected) Color(0x11FF0000) else Color(0x00FFFFFF))
.padding(8.dp)
.combinedClickable(
onLongClick = { onSelected(!isSelected) },
onClick = onClick
)
.height(192.dp)
) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.height(256.dp)
modifier = Modifier.fillMaxWidth().weight(0.7f)
) {
AnimatedContent(
displayImageId,
label = "Recipe Preview",
modifier = Modifier.fillMaxSize(),
transitionSpec = {
fadeIn(animationSpec = tween(500))
.togetherWith(fadeOut(animationSpec = tween(500)))
@ -83,47 +83,39 @@ fun RecipePreview(
Image(
bitmap = displayImage.asImageBitmap(),
contentDescription = "Recipe image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(256.dp)
.padding(top = 16.dp, bottom = 8.dp),
contentScale = ContentScale.FillHeight,
)
} else {
Image(
bitmap = ImageBitmap.imageResource(R.drawable.missing_image),
contentDescription = "Missing recipe image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(256.dp)
.padding(top = 16.dp, bottom = 8.dp),
contentScale = ContentScale.FillHeight,
)
}
}
}
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().weight(0.2f)
) {
Text(
text = entry.recipe.title,
modifier = Modifier.fillMaxWidth(),
style = TextStyle(
textAlign = TextAlign.Center,
fontSize = 7.em,
)
)
}
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
for (tag in entry.tags) {
Tag(tag)
}
}
// Row(
// horizontalArrangement = Arrangement.End,
// modifier = Modifier
// .weight(0.15f)
// .fillMaxWidth()
// ) {
// for (tag in entry.tags) {
// Tag(tag)
// }
// }
}
}

View File

@ -10,18 +10,20 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import xyz.pixelatedw.recipe.data.Tag
@Composable
fun Tag(tag: Tag) {
Box(
modifier = Modifier
.padding(start = 8.dp)
.clip(RoundedCornerShape(percent = 50))
.padding(start = 4.dp)
.clip(RoundedCornerShape(percent = 25))
.background(Color(0, 153, 170))
) {
Text(
modifier = Modifier.padding(start = 8.dp, end = 8.dp),
modifier = Modifier.padding(start = 4.dp, end = 4.dp),
fontSize = 12.sp,
text = tag.name
)
}

View File

@ -16,20 +16,23 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import xyz.pixelatedw.recipe.data.RecipesView
@Composable
fun TagFilterDialog(onAccept: () -> Unit, view: RecipesView) {
val filters = view.tagFilters.collectAsState()
val filters by view.tagFilters.collectAsState()
Dialog(onDismissRequest = { }) {
Card(
@ -46,19 +49,11 @@ fun TagFilterDialog(onAccept: () -> Unit, view: RecipesView) {
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.wrapContentSize(Alignment.TopStart)
) {
items(filters.value) { tag ->
if (tag.count <= 0) {
return@items
}
// TODO This doesn't really feel right lmao, but for now it works
val filterChecked = remember { mutableStateOf(tag.checked) }
items(filters) { tag ->
Row(
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = filterChecked.value, onCheckedChange = {
filterChecked.value = !tag.checked
Checkbox(checked = tag.checked, onCheckedChange = {
view.setTagFilterState(tag.tag, !tag.checked)
})
TextButton(
@ -68,7 +63,6 @@ fun TagFilterDialog(onAccept: () -> Unit, view: RecipesView) {
containerColor = Color.Transparent
),
onClick = {
filterChecked.value = !tag.checked
view.setTagFilterState(tag.tag, !tag.checked)
}
) {