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

227 lines
5.7 KiB
Kotlin

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
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
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
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
import xyz.pixelatedw.recipe.ui.theme.RecipeTheme
import java.io.BufferedReader
import java.io.InputStreamReader
class MainActivity : ComponentActivity() {
private val recipeView: RecipesView by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
findSourceDir()
enableEdgeToEdge()
setContent {
RecipeTheme {
Surface {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
RecipeList(innerPadding, recipeView)
}
}
}
}
}
private fun findSourceDir() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addCategory(Intent.CATEGORY_DEFAULT)
}
val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.data?.let { uri ->
getRecipes(uri)
}
}
}
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
}
}
}
data class Recipe(val title: String, val tags: List<String>)
@Composable
fun RecipeList(padding: PaddingValues, view: RecipesView) {
val recipes = view.recipes.collectAsState()
LazyColumn(modifier = Modifier.padding(padding)) {
items(recipes.value) { recipe ->
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)
//// }
//}
@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")))
}
}
}