From 8229d7a6f04eff6d6300f6cbf34d878e9c0f4e43 Mon Sep 17 00:00:00 2001 From: Wynd Date: Thu, 7 Aug 2025 16:47:41 +0300 Subject: [PATCH] Finally some state exists and can load recipes from the file system --- app/build.gradle.kts | 4 +- .../xyz/pixelatedw/recipe/MainActivity.kt | 145 +++++++++++++----- gradle/libs.versions.toml | 4 + 3 files changed, 116 insertions(+), 37 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b6914f1..8ef7608 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -40,7 +40,6 @@ android { } dependencies { - implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) @@ -49,6 +48,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) + implementation(libs.androidx.documentfile) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) @@ -56,4 +56,6 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) + + implementation(libs.jtoml) } diff --git a/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt b/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt index e54377e..e4e165c 100644 --- a/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt +++ b/app/src/main/java/xyz/pixelatedw/recipe/MainActivity.kt @@ -9,99 +9,172 @@ 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.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer 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.layout.width import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.MaterialTheme +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.TextUnit -import androidx.compose.ui.unit.TextUnitType 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 androidx.compose.foundation.lazy.items -import androidx.compose.ui.platform.LocalContext -import androidx.core.net.toUri +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) - val recipes = listOf( - Recipe(title = "Test", tags = listOf("tag1", "tag2")), - Recipe(title = "Actual Recipe", tags = listOf("test")) - ) - - requestDirLauncher() + findSourceDir() enableEdgeToEdge() setContent { - RecipeTheme { + RecipeTheme { Surface { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - RecipeList(innerPadding, recipes) + RecipeList(innerPadding, recipeView) } - } - } + } + } } } - private val requestDirLauncher = { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) + 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 -> - println(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 = 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() + 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>( arrayListOf() ) + val recipes = _recipes.asStateFlow() + + public fun addRecipe(recipe: Recipe) { + _recipes.update { + it + recipe + } + } } data class Recipe(val title: String, val tags: List) @Composable -fun RecipeList(padding: PaddingValues, recipes: List) { +fun RecipeList(padding: PaddingValues, view: RecipesView) { + val recipes = view.recipes.collectAsState() + LazyColumn(modifier = Modifier.padding(padding)) { - items(recipes) { recipe -> + 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) - } -} +//@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) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8123f36..e86f4ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.7.3" +jtoml = "1.1.0" kotlin = "2.0.0" coreKtx = "1.16.0" junit = "4.13.2" @@ -8,9 +9,11 @@ espressoCore = "3.7.0" lifecycleRuntimeKtx = "2.9.2" activityCompose = "1.10.1" composeBom = "2024.04.01" +documentfile = "1.1.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +jtoml = { group = "io.github.wasabithumb", name = "jtoml", version.ref = "jtoml" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -24,6 +27,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }