recipe-kt/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt

227 lines
5.7 KiB
Kotlin
Raw Normal View History

2025-08-07 10:28:56 +03:00
package xyz.pixelatedw.recipe
import android.app.Activity
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
2025-08-07 10:28:56 +03:00
import androidx.compose.foundation.Image
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.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
2025-08-07 10:28:56 +03:00
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
2025-08-07 10:28:56 +03:00
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel
import io.github.wasabithumb.jtoml.JToml
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
2025-08-07 10:28:56 +03:00
import xyz.pixelatedw.recipe.ui.theme.RecipeTheme
import java.io.BufferedReader
import java.io.InputStreamReader
2025-08-07 10:28:56 +03:00
class MainActivity : ComponentActivity() {
private val recipeView: RecipesView by viewModels()
2025-08-07 10:28:56 +03:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findSourceDir()
2025-08-07 10:28:56 +03:00
enableEdgeToEdge()
setContent {
RecipeTheme {
2025-08-07 10:28:56 +03:00
Surface {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
RecipeList(innerPadding, recipeView)
2025-08-07 10:28:56 +03:00
}
}
}
2025-08-07 10:28:56 +03:00
}
}
private fun findSourceDir() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addCategory(Intent.CATEGORY_DEFAULT)
}
2025-08-07 10:28:56 +03:00
val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { uri ->
getRecipes(uri)
2025-08-07 10:28:56 +03:00
}
}
}
getContent.launch(intent)
}
private fun getRecipes(uri: Uri?) {
if (uri == null) {
return
}
val dir = DocumentFile.fromTreeUri(this, uri)
if (dir != null) {
val fileList: Array<DocumentFile> = dir.listFiles()
for (file in fileList) {
if (file.isFile && file.name?.endsWith(".md") == true) {
val recipe = parseRecipe(file)
if (recipe != null) {
this.recipeView.addRecipe(recipe)
}
}
}
}
}
private fun parseRecipe(file: DocumentFile): Recipe? {
val lastModified = file.lastModified()
val inputStream = this.contentResolver.openInputStream(file.uri)
val reader = BufferedReader(InputStreamReader(inputStream))
val lines = reader.readLines()
val sb = StringBuilder()
var hasToml = false
for (i in 0..lines.size) {
val line = lines[i]
if (line == "+++") {
if (hasToml) {
break
}
hasToml = true
continue
}
sb.appendLine(line)
}
val toml = JToml.jToml()
val doc = toml.readFromString(sb.toString())
val tags = arrayListOf<String>()
for (tomlElem in doc["tags"]!!.asArray()) {
tags.add(tomlElem!!.asPrimitive().asString())
}
val recipe = Recipe(
title = doc["title"]!!.asPrimitive().asString(),
tags = tags
)
return recipe
}
}
class RecipesView : ViewModel() {
private val _recipes = MutableStateFlow<List<Recipe>>( arrayListOf() )
val recipes = _recipes.asStateFlow()
public fun addRecipe(recipe: Recipe) {
_recipes.update {
it + recipe
}
}
2025-08-07 10:28:56 +03:00
}
data class Recipe(val title: String, val tags: List<String>)
@Composable
fun RecipeList(padding: PaddingValues, view: RecipesView) {
val recipes = view.recipes.collectAsState()
2025-08-07 10:28:56 +03:00
LazyColumn(modifier = Modifier.padding(padding)) {
items(recipes.value) { recipe ->
2025-08-07 10:28:56 +03:00
RecipePreview(recipe)
}
}
}
//@Preview
//@Composable
//fun RecipeListPreview() {
// val recipes = listOf(
// Recipe(title = "Test", tags = listOf("tag1", "tag2")),
// Recipe(title = "Actual Recipe", tags = listOf("test"))
// )
//// RecipeTheme {
//// RecipeList(PaddingValues(), recipes)
//// }
//}
2025-08-07 10:28:56 +03:00
@Composable
fun RecipePreview(recipe: Recipe) {
Column(modifier = Modifier.padding(8.dp)) {
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
Image(
painter = painterResource(R.drawable.ic_launcher_background),
contentDescription = "Recipe image",
modifier = Modifier.size(256.dp)
)
}
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
Text(
text = recipe.title,
modifier = Modifier.fillMaxWidth(),
style = TextStyle(
textAlign = TextAlign.Center,
fontSize = 7.em,
)
)
}
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
for (tag in recipe.tags) {
Text(
text = tag,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
}
@Preview(
showBackground = true,
name = "Light Mode"
)
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
@Composable
fun RecipePreviewPreview() {
RecipeTheme {
Surface {
RecipePreview(Recipe("Test", listOf("test", "test2", "test3")))
}
}
}