Compare commits
No commits in common. "357cab1afa517c7a948dc3c780c2bbc123648332" and "e99625445155dd6d5f5de0db0b215e7077fca0f9" have entirely different histories.
357cab1afa
...
e996254451
|
|
@ -12,39 +12,18 @@ class RecipesView : ViewModel() {
|
||||||
private val _recipes = MutableStateFlow<List<RecipeWithTags>>( arrayListOf() )
|
private val _recipes = MutableStateFlow<List<RecipeWithTags>>( arrayListOf() )
|
||||||
val recipes = _recipes.asStateFlow()
|
val recipes = _recipes.asStateFlow()
|
||||||
|
|
||||||
private val _tagFilters = MutableStateFlow<List<TagFilter>>( arrayListOf() )
|
|
||||||
val tagFilters = _tagFilters.asStateFlow()
|
|
||||||
|
|
||||||
private val _search = MutableStateFlow<String?>(null)
|
private val _search = MutableStateFlow<String?>(null)
|
||||||
val search = _search.asStateFlow()
|
val search = _search.asStateFlow()
|
||||||
|
|
||||||
private val _keepScreenOn = MutableStateFlow<Boolean>(false)
|
private val _keepScreenOn = MutableStateFlow<Boolean>(false)
|
||||||
val keepScreenOn = _keepScreenOn.asStateFlow()
|
val keepScreenOn = _keepScreenOn.asStateFlow()
|
||||||
|
|
||||||
fun reloadTagFilterState() {
|
|
||||||
_tagFilters.value = _tagFilters.value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTagFilterState(tag: String, state: Boolean) {
|
|
||||||
_tagFilters.value = _tagFilters.value.toMutableList().apply { replaceAll { it -> if (tag == it.tag) it.checked = state; it } }.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setKeepScreenOn(flag: Boolean) {
|
fun setKeepScreenOn(flag: Boolean) {
|
||||||
_keepScreenOn.update { flag }
|
_keepScreenOn.update { flag }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRecipes(recipes: List<RecipeWithTags>) {
|
fun setRecipes(recipes: List<RecipeWithTags>) {
|
||||||
_recipes.update { recipes }
|
_recipes.update { recipes }
|
||||||
|
|
||||||
val filters = arrayListOf<TagFilter>()
|
|
||||||
|
|
||||||
_recipes.value.stream()
|
|
||||||
.filter { it.tags.isNotEmpty() }
|
|
||||||
.forEach {
|
|
||||||
it.tags.forEach {tag -> filters.add(TagFilter(tag.name))}
|
|
||||||
}
|
|
||||||
|
|
||||||
_tagFilters.update { filters.distinct() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeRecipe(recipe: RecipeWithTags) {
|
fun removeRecipe(recipe: RecipeWithTags) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
package xyz.pixelatedw.recipe.data
|
|
||||||
|
|
||||||
data class TagFilter(val tag: String, var checked: Boolean = false) {
|
|
||||||
}
|
|
||||||
|
|
@ -9,19 +9,15 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
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.material3.Button
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.material.icons.filled.Edit
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
|
|
@ -35,49 +31,18 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
|
||||||
val recipes = view.recipes.collectAsState()
|
val recipes = view.recipes.collectAsState()
|
||||||
val active = view.activeRecipe.collectAsState()
|
val active = view.activeRecipe.collectAsState()
|
||||||
val search = view.search.collectAsState()
|
val search = view.search.collectAsState()
|
||||||
val filters = view.tagFilters.collectAsState()
|
|
||||||
|
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
||||||
val isInSearch = isInSearch@{ entry: RecipeWithTags ->
|
val isInSearch = isInSearch@{ entry: RecipeWithTags ->
|
||||||
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)
|
||||||
val hasTags = entry.tags.stream()
|
val hasTags = entry.tags.isNotEmpty() && entry.tags.stream()
|
||||||
.filter { tag -> tag.name.contains(search.value.orEmpty(), ignoreCase = true) }
|
.filter { tag -> tag.name.contains(search.value.orEmpty(), ignoreCase = true) }
|
||||||
.count() > 0
|
.count() > 0
|
||||||
|
|
||||||
if (!isSearchEmpty && !hasTitle && !hasTags) {
|
hasTitle || hasTags
|
||||||
return@isInSearch false
|
|
||||||
}
|
|
||||||
|
|
||||||
val totalFilters = filters.value.stream().filter{ f -> f.checked }.count()
|
|
||||||
var checkedFilters = 0
|
|
||||||
|
|
||||||
for (filter in filters.value) {
|
|
||||||
if (filter.checked) {
|
|
||||||
val hasTagFilters = entry.tags.isNotEmpty() && entry.tags.stream()
|
|
||||||
.filter { tag -> tag.name.contains(filter.tag, ignoreCase = true) }
|
|
||||||
.count() > 0
|
|
||||||
|
|
||||||
if(hasTagFilters) {
|
|
||||||
checkedFilters += 1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
checkedFilters -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val hasTagFilters = checkedFilters >= totalFilters
|
|
||||||
if (totalFilters > 0 && !hasTagFilters) {
|
|
||||||
return@isInSearch false
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val openTagFilterDialog = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = "list"
|
startDestination = "list"
|
||||||
|
|
@ -89,31 +54,19 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.weight(0.7f),
|
|
||||||
value = search.value.orEmpty(),
|
value = search.value.orEmpty(),
|
||||||
onValueChange = { search -> view.setSearch(search) },
|
onValueChange = { search -> view.setSearch(search) },
|
||||||
)
|
)
|
||||||
// Tags
|
Button(
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.weight(0.15f),
|
|
||||||
onClick = { openTagFilterDialog.value = true },
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Edit,
|
|
||||||
contentDescription = "Toggle tags filtering menu"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// Load
|
|
||||||
IconButton(
|
|
||||||
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)) },
|
||||||
) {
|
) {
|
||||||
Icon(
|
Text(
|
||||||
imageVector = Icons.Default.Add,
|
text = "Load",
|
||||||
contentDescription = "Load recipes from filesystem"
|
maxLines = 1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,18 +82,6 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when {
|
|
||||||
openTagFilterDialog.value -> {
|
|
||||||
TagFilterDialog(
|
|
||||||
onAccept = {
|
|
||||||
openTagFilterDialog.value = false
|
|
||||||
view.reloadTagFilterState()
|
|
||||||
},
|
|
||||||
view,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
composable("info") {
|
composable("info") {
|
||||||
RecipeInfo(ctx, view, navController, padding, active.value!!)
|
RecipeInfo(ctx, view, navController, padding, active.value!!)
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,13 @@ package xyz.pixelatedw.recipe.ui.components
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
|
@ -19,7 +21,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DeleteRecipeDialog(onAccept: ( ) -> Unit, onDismiss: () -> Unit) {
|
fun RecipeDeletionDialog(onAccept: ( ) -> Unit, onDismiss: () -> Unit) {
|
||||||
Dialog(onDismissRequest = { onDismiss() }) {
|
Dialog(onDismissRequest = { onDismiss() }) {
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -30,6 +31,7 @@ import xyz.pixelatedw.recipe.data.RecipeWithTags
|
||||||
import xyz.pixelatedw.recipe.data.RecipesView
|
import xyz.pixelatedw.recipe.data.RecipesView
|
||||||
import xyz.pixelatedw.recipe.utils.parseMarkdown
|
import xyz.pixelatedw.recipe.utils.parseMarkdown
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun RecipeInfo(
|
fun RecipeInfo(
|
||||||
ctx: MainActivity,
|
ctx: MainActivity,
|
||||||
|
|
@ -122,7 +124,7 @@ fun RecipeInfo(
|
||||||
|
|
||||||
when {
|
when {
|
||||||
openDeletionDialog.value -> {
|
openDeletionDialog.value -> {
|
||||||
DeleteRecipeDialog(
|
RecipeDeletionDialog(
|
||||||
onAccept = {
|
onAccept = {
|
||||||
view.removeRecipe(active)
|
view.removeRecipe(active)
|
||||||
ctx.db.recipeDao().delete(active.recipe)
|
ctx.db.recipeDao().delete(active.recipe)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ fun RecipePreview(entry: RecipeWithTags, previewUri: Bitmap?, onClick: () -> Uni
|
||||||
bitmap = previewUri.asImageBitmap(),
|
bitmap = previewUri.asImageBitmap(),
|
||||||
contentDescription = "Recipe image",
|
contentDescription = "Recipe image",
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier.size(256.dp).padding(top = 16.dp, bottom = 16.dp),
|
modifier = Modifier.size(256.dp).padding(top = 16.dp, bottom = 16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
package xyz.pixelatedw.recipe.ui.components
|
|
||||||
|
|
||||||
import androidx.collection.arraySetOf
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
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.wrapContentSize
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
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.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.TextUnit
|
|
||||||
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
|
|
||||||
import xyz.pixelatedw.recipe.data.TagFilter
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TagFilterDialog(onAccept: ( ) -> Unit, view: RecipesView) {
|
|
||||||
val filters = view.tagFilters.collectAsState()
|
|
||||||
|
|
||||||
Dialog(onDismissRequest = { }) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight(0.9f)
|
|
||||||
.padding(16.dp),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
) {
|
|
||||||
Text(text = "Filter by Tags", fontSize = 6.em, modifier = Modifier.padding(16.dp))
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight(0.9f)
|
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
|
||||||
.wrapContentSize(Alignment.TopStart)) {
|
|
||||||
items(filters.value) { tag ->
|
|
||||||
// TODO This doesn't really feel right lmao, but for now it works
|
|
||||||
val filterChecked = remember { mutableStateOf(tag.checked) }
|
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Checkbox(checked = filterChecked.value, onCheckedChange = {
|
|
||||||
filterChecked.value = !tag.checked
|
|
||||||
view.setTagFilterState(tag.tag, !tag.checked)
|
|
||||||
})
|
|
||||||
Text(tag.tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(end = 16.dp), horizontalArrangement = Arrangement.End) {
|
|
||||||
TextButton(onClick = onAccept) {
|
|
||||||
Text("Done")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue