Caching loaded recipes in database, copying files to app storage for easier handling and some fixes

master
Wynd 2025-08-09 18:09:09 +03:00
parent 0af796082c
commit 0b2e301f8a
14 changed files with 262 additions and 103 deletions

View File

@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
alias(libs.plugins.devtools.ksp)
} }
android { android {
@ -49,18 +50,22 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.documentfile) implementation(libs.androidx.documentfile)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.navigation.ui)
implementation(libs.androidx.navigation.navigation.compose)
implementation(libs.androidx.room)
implementation(libs.jtoml)
implementation(libs.commonmark)
ksp(libs.androidx.room.compiler)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.androidx.navigation.fragment)
implementation(libs.androidx.navigation.navigation.ui)
implementation(libs.androidx.navigation.navigation.compose)
implementation(libs.jtoml)
implementation(libs.commonmark)
} }

View File

@ -1,6 +1,5 @@
package xyz.pixelatedw.recipe package xyz.pixelatedw.recipe
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -11,6 +10,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.room.Room
import xyz.pixelatedw.recipe.data.AppDatabase
import xyz.pixelatedw.recipe.data.RecipesView import xyz.pixelatedw.recipe.data.RecipesView
import xyz.pixelatedw.recipe.ui.components.MainScreen import xyz.pixelatedw.recipe.ui.components.MainScreen
import xyz.pixelatedw.recipe.ui.theme.RecipeTheme import xyz.pixelatedw.recipe.ui.theme.RecipeTheme
@ -18,11 +19,18 @@ import xyz.pixelatedw.recipe.utils.getRecipes
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val recipeView: RecipesView by viewModels() private val recipeView: RecipesView by viewModels()
private lateinit var db: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
findSourceDir() db = Room.databaseBuilder(
this,
AppDatabase::class.java, "recipes"
).allowMainThreadQueries().build()
val recipes = db.recipeWithTagsDao().getAll()
recipeView.setRecipes(recipes)
enableEdgeToEdge() enableEdgeToEdge()
@ -30,25 +38,22 @@ class MainActivity : ComponentActivity() {
RecipeTheme { RecipeTheme {
Surface { Surface {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
MainScreen(innerPadding, recipeView) MainScreen(this, innerPadding, recipeView)
} }
} }
} }
} }
} }
private fun findSourceDir() { val sourceChooser =
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
result.data?.data?.let { uri ->
getRecipes(this, db, uri)
val getContent = val recipes = db.recipeWithTagsDao().getAll()
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> recipeView.setRecipes(recipes)
if (result.resultCode == RESULT_OK) {
result.data?.data?.let { uri ->
getRecipes(this, recipeView, uri)
}
} }
} }
}
getContent.launch(intent)
}
} }

View File

@ -0,0 +1,16 @@
package xyz.pixelatedw.recipe.data
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(
version = 1,
entities = [Recipe::class, Tag::class, RecipeTag::class],
)
abstract class AppDatabase : RoomDatabase() {
abstract fun recipeWithTagsDao(): RecipeWithTagsDao
abstract fun recipeDao(): RecipeDao
abstract fun tagDao(): TagDao
}

View File

@ -1,15 +1,41 @@
package xyz.pixelatedw.recipe.data package xyz.pixelatedw.recipe.data
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.core.net.toUri
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import java.io.File
@Entity
data class Recipe( data class Recipe(
@PrimaryKey
val title: String, val title: String,
val tags: List<String>, val preview: String?,
val previews: List<Bitmap>,
val lastModified: Long, val lastModified: Long,
val content: String val content: String
) { ) {
fun mainImage(): Bitmap? { fun mainImage(ctx: Context): Bitmap? {
return this.previews.getOrNull(0) if (this.preview != null) {
val file = File(ctx.filesDir, this.preview)
if (file.exists()) {
ctx.contentResolver.openInputStream(file.toUri()).use {
val bitmap: Bitmap? = BitmapFactory.decodeStream(it)
return bitmap
}
}
}
return null
} }
} }
@Dao
interface RecipeDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(recipe: Recipe)
}

View File

@ -0,0 +1,44 @@
package xyz.pixelatedw.recipe.data
import androidx.room.Dao
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.Junction
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Relation
import androidx.room.Transaction
@Entity(primaryKeys = ["title", "name"])
data class RecipeTag(
val title: String,
val name: String
)
data class RecipeWithTags(
@Embedded
val recipe: Recipe,
@Relation(
parentColumn = "title",
entity = Tag::class,
entityColumn = "name",
associateBy = Junction(
value = RecipeTag::class,
parentColumn = "title",
entityColumn = "name"
)
)
val tags: List<Tag>
)
@Dao
interface RecipeWithTagsDao {
@Transaction
@Query("SELECT * FROM recipe")
fun getAll(): List<RecipeWithTags>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(recipe: RecipeTag)
}

View File

@ -6,26 +6,24 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
class RecipesView : ViewModel() { class RecipesView : ViewModel() {
private val _activeRecipe = MutableStateFlow<Recipe?>( null ) private val _activeRecipe = MutableStateFlow<RecipeWithTags?>( null )
val activeRecipe = _activeRecipe.asStateFlow() val activeRecipe = _activeRecipe.asStateFlow()
private val _recipes = MutableStateFlow<List<Recipe>>( arrayListOf() ) private val _recipes = MutableStateFlow<List<RecipeWithTags>>( arrayListOf() )
val recipes = _recipes.asStateFlow() val recipes = _recipes.asStateFlow()
private val _search = MutableStateFlow<String?>(null) private val _search = MutableStateFlow<String?>(null)
val search = _search.asStateFlow() val search = _search.asStateFlow()
fun setRecipes(recipes: List<RecipeWithTags>) {
_recipes.update { recipes }
}
fun setSearch(search: String) { fun setSearch(search: String) {
_search.update { search } _search.update { search }
} }
fun addRecipe(recipe: Recipe) { fun setActive(recipe: RecipeWithTags) {
_recipes.update {
it + recipe
}
}
fun setActive(recipe: Recipe) {
_activeRecipe.update { recipe } _activeRecipe.update { recipe }
} }
} }

View File

@ -0,0 +1,18 @@
package xyz.pixelatedw.recipe.data
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
@Entity
data class Tag(
@PrimaryKey val name: String
)
@Dao
interface TagDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(tag: Tag)
}

View File

@ -1,34 +1,44 @@
package xyz.pixelatedw.recipe.ui.components package xyz.pixelatedw.recipe.ui.components
import android.content.Intent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth 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.material3.Button
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.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
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
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import xyz.pixelatedw.recipe.data.Recipe import xyz.pixelatedw.recipe.MainActivity
import xyz.pixelatedw.recipe.data.RecipeWithTags
import xyz.pixelatedw.recipe.data.RecipesView import xyz.pixelatedw.recipe.data.RecipesView
@Composable @Composable
fun MainScreen(padding: PaddingValues, view: RecipesView) { 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 navController = rememberNavController() val navController = rememberNavController()
val isInSearch = isInSearch@{ recipe: Recipe -> val isInSearch = isInSearch@{ entry: RecipeWithTags ->
val hasTitle = recipe.title.contains(search.value.orEmpty(), ignoreCase = true) val hasTitle = entry.recipe.title.contains(search.value.orEmpty(), ignoreCase = true)
val hasTags = recipe.tags.isNotEmpty() && recipe.tags.stream() val hasTags = entry.tags.isNotEmpty() && entry.tags.stream()
.filter { tag -> tag.contains(search.value.orEmpty(), ignoreCase = true) }.count() > 0 .filter { tag -> tag.name.contains(search.value.orEmpty(), ignoreCase = true) }
.count() > 0
hasTitle || hasTags hasTitle || hasTags
} }
@ -39,18 +49,33 @@ fun MainScreen(padding: PaddingValues, view: RecipesView) {
) { ) {
composable("list") { composable("list") {
Column(modifier = Modifier.padding(padding)) { Column(modifier = Modifier.padding(padding)) {
OutlinedTextField( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(8.dp), .padding(8.dp),
value = search.value.orEmpty(), verticalAlignment = Alignment.CenterVertically,
onValueChange = { search -> view.setSearch(search) }) horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedTextField(
value = search.value.orEmpty(),
onValueChange = { search -> view.setSearch(search) },
)
Button(
onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) },
) {
Text(
text = "Load",
maxLines = 1,
textAlign = TextAlign.Center,
)
}
}
LazyColumn { LazyColumn {
items(recipes.value) { recipe -> items(recipes.value) { entry ->
if (isInSearch(recipe)) { if (isInSearch(entry)) {
val previewUri = recipe.mainImage() val previewUri = entry.recipe.mainImage(LocalContext.current)
RecipePreview(recipe, previewUri, onClick = { RecipePreview(entry, previewUri, onClick = {
view.setActive(recipe) view.setActive(entry)
navController.navigate("info") navController.navigate("info")
}) })
} }

View File

@ -16,10 +16,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import xyz.pixelatedw.recipe.data.Recipe import xyz.pixelatedw.recipe.data.Recipe
import xyz.pixelatedw.recipe.data.RecipeWithTags
import xyz.pixelatedw.recipe.utils.parseMarkdown import xyz.pixelatedw.recipe.utils.parseMarkdown
@Composable @Composable
fun RecipeInfo(padding: PaddingValues, active: Recipe) { fun RecipeInfo(padding: PaddingValues, active: RecipeWithTags) {
Column( Column(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
@ -30,7 +31,7 @@ fun RecipeInfo(padding: PaddingValues, active: Recipe) {
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text( Text(
text = active.title, text = active.recipe.title,
style = MaterialTheme.typography.displayLarge, style = MaterialTheme.typography.displayLarge,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
@ -40,7 +41,7 @@ fun RecipeInfo(padding: PaddingValues, active: Recipe) {
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
val annotatedString = val annotatedString =
parseMarkdown(active.content, MaterialTheme.typography) parseMarkdown(active.recipe.content, MaterialTheme.typography)
Text( Text(
text = annotatedString, text = annotatedString,
modifier = Modifier.padding(16.dp), modifier = Modifier.padding(16.dp),

View File

@ -20,9 +20,10 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em import androidx.compose.ui.unit.em
import xyz.pixelatedw.recipe.data.Recipe import xyz.pixelatedw.recipe.data.Recipe
import xyz.pixelatedw.recipe.data.RecipeWithTags
@Composable @Composable
fun RecipePreview(recipe: Recipe, previewUri: Bitmap?, onClick: () -> Unit) { fun RecipePreview(entry: RecipeWithTags, previewUri: Bitmap?, onClick: () -> Unit) {
Column(modifier = Modifier Column(modifier = Modifier
.padding(8.dp) .padding(8.dp)
.clickable(onClick = onClick)) { .clickable(onClick = onClick)) {
@ -38,7 +39,7 @@ fun RecipePreview(recipe: Recipe, previewUri: Bitmap?, onClick: () -> Unit) {
} }
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) { Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) {
Text( Text(
text = recipe.title, text = entry.recipe.title,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
style = TextStyle( style = TextStyle(
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
@ -47,7 +48,7 @@ fun RecipePreview(recipe: Recipe, previewUri: Bitmap?, onClick: () -> Unit) {
) )
} }
Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)) { Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)) {
for (tag in recipe.tags) { for (tag in entry.tags) {
Tag(tag) Tag(tag)
} }
} }

View File

@ -10,9 +10,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import xyz.pixelatedw.recipe.data.Tag
@Composable @Composable
fun Tag(tag: String) { fun Tag(tag: Tag) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(start = 8.dp) .padding(start = 8.dp)
@ -21,7 +22,7 @@ fun Tag(tag: String) {
) { ) {
Text( Text(
modifier = Modifier.padding(start = 8.dp, end = 8.dp), modifier = Modifier.padding(start = 8.dp, end = 8.dp),
text = tag text = tag.name
) )
} }
} }

View File

@ -1,61 +1,56 @@
package xyz.pixelatedw.recipe.utils package xyz.pixelatedw.recipe.utils
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import androidx.core.text.trimmedLength import androidx.core.text.trimmedLength
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import io.github.wasabithumb.jtoml.JToml import io.github.wasabithumb.jtoml.JToml
import xyz.pixelatedw.recipe.data.AppDatabase
import xyz.pixelatedw.recipe.data.Recipe import xyz.pixelatedw.recipe.data.Recipe
import xyz.pixelatedw.recipe.data.RecipesView import xyz.pixelatedw.recipe.data.RecipeTag
import xyz.pixelatedw.recipe.data.Tag
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.InputStreamReader import java.io.InputStreamReader
private val previews = HashMap<String, Bitmap>()
private val recipeFiles = mutableListOf<DocumentFile>() private val recipeFiles = mutableListOf<DocumentFile>()
fun getRecipes(ctx: Context, view: RecipesView, uri: Uri?) { fun getRecipes(ctx: Context, db: AppDatabase, uri: Uri?) {
if (uri == null) { if (uri == null) {
return return
} }
val path = ""
val dir = DocumentFile.fromTreeUri(ctx, uri) val dir = DocumentFile.fromTreeUri(ctx, uri)
if (dir != null) { if (dir != null) {
parseDir(ctx, dir) parseDir(ctx, dir, path)
for (file in recipeFiles) { for (file in recipeFiles) {
val recipe = parseRecipe(ctx, file) parseRecipe(ctx, db, file)
if (recipe != null) {
view.addRecipe(recipe)
}
} }
} }
} }
fun parseDir(ctx: Context, dir: DocumentFile) { fun parseDir(ctx: Context, dir: DocumentFile, path: String) {
val fileList: Array<DocumentFile> = dir.listFiles() val fileList: Array<DocumentFile> = dir.listFiles()
for (file in fileList) { for (file in fileList) {
if(file.isDirectory) { if (file.isDirectory) {
parseDir(ctx, file) parseDir(ctx, file, path + File.separator + file.name)
continue; continue;
} }
if (file.isFile && file.name?.endsWith(".jpg") == true) { if (file.isFile && file.name?.endsWith(".jpg") == true) {
val inStream = ctx.contentResolver.openInputStream(file.uri) val picsDir = File(ctx.filesDir, path)
picsDir.mkdirs()
var bitmap: Bitmap? = null val newFile = File(picsDir, file.name!!)
if (file.exists()) {
try {
bitmap = BitmapFactory.decodeStream(inStream)
}
finally {
inStream?.close()
}
}
if (bitmap != null) { ctx.contentResolver.openInputStream(file.uri).use { inStream ->
previews[file.name!!] = bitmap FileOutputStream(newFile, false).use { outStream ->
inStream?.copyTo(outStream)
}
} }
} }
if (file.isFile && file.name?.endsWith(".md") == true) { if (file.isFile && file.name?.endsWith(".md") == true) {
@ -64,7 +59,7 @@ fun parseDir(ctx: Context, dir: DocumentFile) {
} }
} }
private fun parseRecipe(ctx: Context, file: DocumentFile): Recipe? { private fun parseRecipe(ctx: Context, db: AppDatabase, file: DocumentFile) {
val lastModified = file.lastModified() val lastModified = file.lastModified()
val inputStream = ctx.contentResolver.openInputStream(file.uri) val inputStream = ctx.contentResolver.openInputStream(file.uri)
@ -75,9 +70,8 @@ private fun parseRecipe(ctx: Context, file: DocumentFile): Recipe? {
val sb = StringBuilder() val sb = StringBuilder()
var hasToml = false var hasToml = false
var frontMatterSize = 0 var frontMatterSize = 0
// TODO This could use some improvements as it always assumes frontmatter is the very first thing in the file
for (i in 0..lines.size) { for (line in lines) {
val line = lines[i]
frontMatterSize += line.trimmedLength() + 1 frontMatterSize += line.trimmedLength() + 1
if (line == "+++") { if (line == "+++") {
@ -91,38 +85,57 @@ private fun parseRecipe(ctx: Context, file: DocumentFile): Recipe? {
sb.appendLine(line) sb.appendLine(line)
} }
if (!hasToml) {
reader.close()
return
}
val toml = JToml.jToml() val toml = JToml.jToml()
val doc = toml.readFromString(sb.toString()) val doc = toml.readFromString(sb.toString())
val tags = arrayListOf<String>() if (!doc.contains("title")) {
for (tomlElem in doc["tags"]!!.asArray()) { reader.close()
tags.add(tomlElem!!.asPrimitive().asString()) return
} }
val recipeTitle = doc["title"]!!.asPrimitive().asString()
val pics = arrayListOf<String>() val pics = arrayListOf<String>()
for (tomlElem in doc["pics"]!!.asArray()) { if (doc.contains("pics")) {
pics.add(tomlElem!!.asPrimitive().asString()) for (tomlElem in doc["pics"]!!.asArray()) {
pics.add(tomlElem!!.asPrimitive().asString())
}
} }
val recipePreviews = mutableListOf<Bitmap>() val tags = arrayListOf<Tag>()
for (pic in pics) { if (doc.contains("tags")) {
val bitmap = previews[pic] for (tomlElem in doc["tags"]!!.asArray()) {
if (bitmap != null) { val tag = Tag(tomlElem!!.asPrimitive().asString())
recipePreviews.add(bitmap) tags.add(tag)
val recipeWithTags = RecipeTag(recipeTitle, tag.name)
db.tagDao().insert(tag)
db.recipeWithTagsDao().insert(recipeWithTags)
} }
} }
var recipePreview: String? = null
for (pic in pics) {
recipePreview = pic
}
val content = text.substring(frontMatterSize..<text.length) val content = text.substring(frontMatterSize..<text.length)
val recipe = Recipe( val recipe = Recipe(
title = doc["title"]!!.asPrimitive().asString(), title = recipeTitle,
tags = tags, preview = recipePreview,
previews = recipePreviews,
lastModified = lastModified, lastModified = lastModified,
content = content content = content
) )
reader.close() db.recipeDao().insert(recipe)
return recipe println(recipe)
reader.close()
} }

View File

@ -3,4 +3,5 @@ plugins {
alias(libs.plugins.android.application) apply false alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.devtools.ksp) apply false
} }

View File

@ -12,9 +12,13 @@ composeBom = "2025.07.00"
documentfile = "1.1.0" documentfile = "1.1.0"
commonmark = "0.25.1" commonmark = "0.25.1"
navVersion = "2.9.3" navVersion = "2.9.3"
room = "2.7.2"
roomCompiler = "2.7.2"
ksp = "2.0.21-1.0.27"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
jtoml = { group = "io.github.wasabithumb", name = "jtoml", version.ref = "jtoml" } jtoml = { group = "io.github.wasabithumb", name = "jtoml", version.ref = "jtoml" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
@ -34,9 +38,10 @@ commonmark = { group = "org.commonmark", name = "commonmark", version.ref = "com
androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navVersion"} androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navVersion"}
androidx-navigation-navigation-ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navVersion"} androidx-navigation-navigation-ui = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navVersion"}
androidx-navigation-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navVersion"} androidx-navigation-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navVersion"}
androidx-room = { group = "androidx.room", name = "room-runtime", version.ref = "room"}
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp"}