Properly handle update / insert and delete in the adventure database.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-23 08:42:09 +02:00
parent 7400799efd
commit 2b03ffd1ac
7 changed files with 230 additions and 211 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 2, "version": 2,
"identityHash": "95cb578b3b61a022ab4dda676d2e4645", "identityHash": "f1496aa4aa95e44822a7b0650065c53e",
"entities": [ "entities": [
{ {
"tableName": "lexicon", "tableName": "lexicon",
@ -353,7 +353,7 @@
}, },
{ {
"tableName": "AdventureStory", "tableName": "AdventureStory",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `category` TEXT, `background` TEXT, `revision` INTEGER NOT NULL, `index` INTEGER NOT NULL, `documentId` TEXT NOT NULL, PRIMARY KEY(`title`, `documentId`), FOREIGN KEY(`documentId`) REFERENCES `AdventureBooks`(`documentId`) ON UPDATE NO ACTION ON DELETE NO ACTION )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `category` TEXT, `background` TEXT, `revision` INTEGER NOT NULL, `index` INTEGER NOT NULL, `documentId` TEXT NOT NULL, PRIMARY KEY(`title`, `documentId`), FOREIGN KEY(`documentId`) REFERENCES `AdventureBooks`(`documentId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "title", "fieldPath": "title",
@ -403,7 +403,7 @@
"foreignKeys": [ "foreignKeys": [
{ {
"table": "AdventureBooks", "table": "AdventureBooks",
"onDelete": "NO ACTION", "onDelete": "CASCADE",
"onUpdate": "NO ACTION", "onUpdate": "NO ACTION",
"columns": [ "columns": [
"documentId" "documentId"
@ -478,7 +478,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '95cb578b3b61a022ab4dda676d2e4645')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f1496aa4aa95e44822a7b0650065c53e')"
] ]
} }
} }

View file

@ -6,6 +6,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Transaction import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@Dao @Dao
@ -21,64 +22,70 @@ interface AdventureDao {
fun getStoryLinesFlow(): Flow<List<AdventureLineDbo>> fun getStoryLinesFlow(): Flow<List<AdventureLineDbo>>
@Query("SELECT * from ${AdventureBookDbo.TABLE}") @Query("SELECT * from ${AdventureBookDbo.TABLE}")
fun findBooks(): List<AdventureBookDbo> fun fetchAdventureBooks(): List<AdventureBookDbo>
@Query("SELECT * from ${AdventureStoryDbo.TABLE}") @Query("SELECT * from ${AdventureStoryDbo.TABLE}")
fun findStories(): List<AdventureStoryDbo> fun fetchAdventureStories(): List<AdventureStoryDbo>
@Query("SELECT * from ${AdventureStoryDbo.TABLE} where documentId = :documentId") @Query("SELECT * from ${AdventureStoryDbo.TABLE} where documentId = :documentId")
fun findStories(documentId: String): List<AdventureStoryDbo> fun fetchAdventureStories(documentId: String): List<AdventureStoryDbo>
@Query("SELECT * from ${AdventureStoryDbo.TABLE} where documentId = :documentId AND title = :title") @Query("SELECT * from ${AdventureStoryDbo.TABLE} where documentId = :documentId AND title = :title")
fun findStory(documentId: String, title: String): AdventureStoryDbo? fun fetchAdventureStory(documentId: String, title: String): AdventureStoryDbo?
@Insert(onConflict = OnConflictStrategy.IGNORE) @Query("SELECT * from ${AdventureLineDbo.TABLE} where documentId = :documentId AND title = :storyTitle")
fun fetchAdventureLine(documentId: String, storyTitle: String): List<AdventureLineDbo>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBook(book: AdventureBookDbo) fun insertBook(book: AdventureBookDbo)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Update
fun insertStory(story: AdventureStoryDbo) fun updateBook(book: AdventureBookDbo)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertLines(line: AdventureLineDbo)
@Delete(entity = AdventureBookDbo::class) @Delete(entity = AdventureBookDbo::class)
fun deleteBook(id: AdventureBookDbo.BookId) fun deleteBook(id: AdventureBookDbo.BookId)
@Delete(entity = AdventureStoryDbo::class) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun deleteStory(id: AdventureBookDbo.BookId) fun insertStory(story: AdventureStoryDbo)
@Update
fun updateStory(story: AdventureStoryDbo)
@Delete(entity = AdventureStoryDbo::class) @Delete(entity = AdventureStoryDbo::class)
fun deleteStory(id: AdventureStoryDbo.StoryId) fun deleteStory(id: AdventureStoryDbo.StoryId)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertLine(line: AdventureLineDbo)
@Update
fun updateLine(line: AdventureLineDbo)
@Delete(entity = AdventureLineDbo::class) @Delete(entity = AdventureLineDbo::class)
fun deleteLines(id: AdventureLineDbo.LineId) fun deleteLines(id: AdventureLineDbo.LineId)
@Transaction @Transaction
fun update( fun update(
books: List<AdventureBookDbo>, booksToInsert: List<AdventureBookDbo>,
booksToUpdate: List<AdventureBookDbo>,
booksToRemove: List<AdventureBookDbo.BookId>, booksToRemove: List<AdventureBookDbo.BookId>,
stories: List<AdventureStoryDbo>, storiesToInsert: List<AdventureStoryDbo>,
storiesToUpdate: List<AdventureStoryDbo>,
storiesToRemove: List<AdventureStoryDbo.StoryId>, storiesToRemove: List<AdventureStoryDbo.StoryId>,
lines: List<AdventureLineDbo>, linesToInsert: List<AdventureLineDbo>,
linesToUpdate: List<AdventureLineDbo>,
linesToRemove: List<AdventureLineDbo.LineId>, linesToRemove: List<AdventureLineDbo.LineId>,
) { ) {
// First clean the database from old unused data. // First remove the stuff
booksToRemove.forEach {
deleteStory(id = it)
deleteBook(id = it)
}
// StoryLineDbo are remove with cascading foreign key from StoryBdo.
storiesToRemove.forEach { deleteStory(id = it) }
linesToRemove.forEach { deleteLines(id = it) } linesToRemove.forEach { deleteLines(id = it) }
storiesToRemove.forEach { deleteStory(id = it) }
books.forEach { book -> booksToRemove.forEach { deleteBook(id = it) }
insertBook(book = book) // then insert the stuff
} booksToInsert.forEach { insertBook(book = it) }
stories.forEach { story -> storiesToInsert.forEach { insertStory(story = it) }
insertStory(story = story) linesToInsert.forEach { insertLine(line = it) }
} // and update the stuff
lines.forEach { line -> booksToUpdate.forEach { updateBook(book = it) }
insertLines(line = line) storiesToUpdate.forEach { updateStory(story = it) }
} linesToUpdate.forEach { updateLine(line = it) }
} }
} }

View file

@ -44,7 +44,7 @@ data class AdventureBookDbo(
entity = AdventureBookDbo::class, entity = AdventureBookDbo::class,
parentColumns = [AdventureBookDbo.DOCUMENT_ID], parentColumns = [AdventureBookDbo.DOCUMENT_ID],
childColumns = [AdventureStoryDbo.FK_DOCUMENT_ID], childColumns = [AdventureStoryDbo.FK_DOCUMENT_ID],
onDelete = ForeignKey.NO_ACTION, onDelete = ForeignKey.CASCADE,
) )
], ],
) )

View file

@ -5,9 +5,6 @@ import com.pixelized.rplexicon.data.database.adventure.AdventureBookDbo
import com.pixelized.rplexicon.data.database.adventure.AdventureLineDbo import com.pixelized.rplexicon.data.database.adventure.AdventureLineDbo
import com.pixelized.rplexicon.data.database.adventure.AdventureStoryDbo import com.pixelized.rplexicon.data.database.adventure.AdventureStoryDbo
import com.pixelized.rplexicon.data.model.adventure.Adventure import com.pixelized.rplexicon.data.model.adventure.Adventure
import com.pixelized.rplexicon.data.model.adventure.AdventureBook
import com.pixelized.rplexicon.data.model.adventure.AdventureLine
import com.pixelized.rplexicon.data.model.adventure.AdventureStory
import com.pixelized.rplexicon.data.parser.adventure.AdventureBookParser import com.pixelized.rplexicon.data.parser.adventure.AdventureBookParser
import com.pixelized.rplexicon.data.parser.adventure.AdventureStoryLineParser import com.pixelized.rplexicon.data.parser.adventure.AdventureStoryLineParser
import com.pixelized.rplexicon.data.parser.adventure.AdventureStoryParser import com.pixelized.rplexicon.data.parser.adventure.AdventureStoryParser
@ -17,24 +14,30 @@ import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.min
@Singleton @Singleton
class AdventureRepository @Inject constructor( class AdventureRepository @Inject constructor(
private val googleRepository: GoogleSheetServiceRepository, private val googleRepository: GoogleSheetServiceRepository,
private val database: CompanionDatabase,
private val adventureBookParser: AdventureBookParser, private val adventureBookParser: AdventureBookParser,
private val adventureStoryParser: AdventureStoryParser, private val adventureStoryParser: AdventureStoryParser,
private val adventureStoryLineParser: AdventureStoryLineParser, private val adventureStoryLineParser: AdventureStoryLineParser,
private val adventureDboFactory: AdventureDboFactory, private val adventureDboFactory: AdventureDboFactory,
companionDatabase: CompanionDatabase,
) { ) {
private val adventures = database.adventureDao().let { database -> private val database = companionDatabase.adventureDao()
private val service = Service()
private val adventures = database.let { database ->
combine( combine(
database.getBooksFlow(), database.getBooksFlow(),
database.getStoriesFlow(), database.getStoriesFlow(),
@ -56,9 +59,7 @@ class AdventureRepository @Inject constructor(
return adventures return adventures
} }
fun storyFlow( fun storyFlow(bookTitle: String): Flow<List<Adventure>> {
bookTitle: String,
): Flow<List<Adventure>> {
return adventures.map { adventures -> return adventures.map { adventures ->
adventures.filter { adventure -> adventures.filter { adventure ->
adventure.bookTitle == bookTitle adventure.bookTitle == bookTitle
@ -66,10 +67,7 @@ class AdventureRepository @Inject constructor(
} }
} }
fun adventureFlow( fun adventureFlow(documentId: String, adventureTitle: String): Flow<Adventure?> {
documentId: String,
adventureTitle: String,
): Flow<Adventure?> {
return adventures.map { adventures -> return adventures.map { adventures ->
adventures.firstOrNull { adventure -> adventures.firstOrNull { adventure ->
adventure.documentId == documentId && adventure.storyTitle == adventureTitle adventure.documentId == documentId && adventure.storyTitle == adventureTitle
@ -78,188 +76,201 @@ class AdventureRepository @Inject constructor(
} }
@Throws(IncompatibleSheetStructure::class, Exception::class) @Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchBooks() { suspend fun fetchBooks() = update {
val database = database.adventureDao() fetchAndCompareBooks()
val booksToRemove = database.findBooks() (booksToInsert + booksToUpdate).forEach { book ->
.map { it.id } fetchAndCompareStories(documentId = book.documentId)
.toMutableList()
val books: List<AdventureBookDbo> = fetchAdventureBooks().map { book ->
// convert to BookDbo.
adventureDboFactory.convertToDbo(book = book).also {
// Flag this book to not delete it
booksToRemove.remove(element = it.id)
}
} }
val storiesToRemove = books (storiesToInsert + storiesToUpdate).forEach { story ->
.flatMap { book -> database.findStories(documentId = book.documentId) } fetchAndCompareLines(documentId = story.documentId, storyTitle = story.title)
.map { it.id }
.toMutableList()
val stories: List<AdventureStoryDbo> = books.flatMap { book ->
fetchAdventureStory(documentId = book.documentId)
.mapIndexed { index, story ->
// convert to StoryBdo
val update = adventureDboFactory.convertToDbo(
story = story,
index = index,
documentId = book.documentId,
).also {
// Flag this story to not delete it
storiesToRemove.remove(element = it.id)
}
val cache = database.findStory(
documentId = book.documentId,
title = story.title,
)
when {
cache == null -> update
cache.revision < update.revision -> update
else -> null
}
}
.mapNotNull { it }
} }
val lines: List<AdventureLineDbo> = stories.flatMap { story ->
fetchAdventureLine(
documentId = story.documentId,
storyTitle = story.title,
).mapIndexed { index, line ->
adventureDboFactory.convertToDbo(
adventureLine = line,
index = index,
documentId = story.documentId,
storyTitle = story.title,
)
}
}
database.update(
booksToRemove = booksToRemove,
storiesToRemove = storiesToRemove,
linesToRemove = lines.groupBy { it.id }.keys.toList(),
books = books,
stories = stories,
lines = lines,
)
} }
@Throws(IncompatibleSheetStructure::class, Exception::class) @Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchStories( suspend fun fetchStories(
documentId: String, documentId: String,
) { ) = update {
val database = database.adventureDao() fetchAndCompareStories(documentId = documentId)
val storiesToRemove = database.findStories(documentId = documentId) (storiesToInsert + storiesToUpdate).forEach { story ->
.map { it.id } fetchAndCompareLines(documentId = story.documentId, storyTitle = story.title)
.toMutableList()
val stories: List<AdventureStoryDbo> = fetchAdventureStory(documentId = documentId)
.mapIndexed { index, story ->
// convert to StoryBdo
val update = adventureDboFactory.convertToDbo(
story = story,
index = index,
documentId = documentId,
).also {
// Flag this story to not delete it
storiesToRemove.remove(element = it.id)
}
val cache = database.findStory(
documentId = documentId,
title = story.title,
)
when {
cache == null -> update
cache.revision < update.revision -> update
else -> null
}
}
.mapNotNull { it }
val lines: List<AdventureLineDbo> = stories.flatMap { story ->
fetchAdventureLine(
documentId = story.documentId,
storyTitle = story.title,
).mapIndexed { index, line ->
adventureDboFactory.convertToDbo(
adventureLine = line,
index = index,
documentId = story.documentId,
storyTitle = story.title,
)
}
} }
database.update(
booksToRemove = emptyList(),
storiesToRemove = storiesToRemove,
linesToRemove = lines.groupBy { it.id }.keys.toList(),
books = emptyList(),
stories = stories,
lines = lines,
)
} }
@Throws(IncompatibleSheetStructure::class, Exception::class) @Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchAdventure( suspend fun fetchAdventure(
documentId: String, documentId: String,
storyTitle: String, storyTitle: String,
) { ) = update {
val database = database.adventureDao() // specific case for adventure fetching, need to update the story too as the background is stored there.
service.fetchAdventureStories(
val lines = fetchAdventureLine(
documentId = documentId, documentId = documentId,
storyTitle = storyTitle, ).firstOrNull { story ->
).mapIndexed { index, line -> story.documentId == documentId && story.title == storyTitle
adventureDboFactory.convertToDbo( }?.let { story ->
adventureLine = line, storiesToUpdate.add(story)
index = index,
documentId = documentId,
storyTitle = storyTitle,
)
} }
fetchAndCompareLines(documentId = documentId, storyTitle = storyTitle)
}
private suspend fun DatabaseUpdateScope.fetchAndCompareBooks() {
val cache = database.fetchAdventureBooks()
val update = service.fetchAdventureBooks()
val toRemove = cache.map { it.id }.toMutableList()
val toInsert = mutableListOf<AdventureBookDbo>()
val toUpdate = mutableListOf<AdventureBookDbo>()
update.forEach { item ->
when (toRemove.remove(item.id)) {
true -> toUpdate.add(item)
else -> toInsert.add(item)
}
}
booksToInsert.addAll(elements = toInsert)
booksToUpdate.addAll(elements = toUpdate)
booksToRemove.addAll(elements = toRemove)
}
private suspend fun DatabaseUpdateScope.fetchAndCompareStories(
documentId: String,
) {
val cache = database.fetchAdventureStories(
documentId = documentId,
).associateBy {
it.id
}
val update = service.fetchAdventureStories(
documentId = documentId,
)
val toRemove = cache.keys.toMutableList()
val toInsert = mutableListOf<AdventureStoryDbo>()
val toUpdate = mutableListOf<AdventureStoryDbo>()
update.forEach { item ->
when (toRemove.remove(item.id)) {
true -> if ((cache[item.id]?.revision ?: 0) < item.revision) toUpdate.add(item)
else -> toInsert.add(item)
}
}
storiesToInsert.addAll(elements = toInsert)
storiesToUpdate.addAll(elements = toUpdate)
storiesToRemove.addAll(elements = toRemove)
}
private suspend fun DatabaseUpdateScope.fetchAndCompareLines(
documentId: String,
storyTitle: String,
) {
val cache = database.fetchAdventureLine(
documentId = documentId,
storyTitle = storyTitle,
)
val update = service.fetchAdventureLine(
documentId = documentId,
storyTitle = storyTitle,
)
// if cache is smaller than the update we need to insert
if (cache.size < update.size) {
(cache.size until update.size).forEach {
linesToInsert.add(update[it])
}
}
// if cache is bigger than the update we need to delete
if (update.size < cache.size) {
(update.size until cache.size).forEach {
linesToRemove.add(cache[it].id)
}
}
// then we update the rest
(0 until min(cache.size, update.size)).forEach {
linesToUpdate.add(update[it])
}
}
private suspend inline fun update(
crossinline lambda: suspend DatabaseUpdateScope.() -> Unit,
) = withContext(Dispatchers.IO + NonCancellable) {
val update = DatabaseUpdateScope()
lambda.invoke(update)
database.update( database.update(
booksToRemove = emptyList(), booksToInsert = update.booksToInsert,
storiesToRemove = emptyList(), booksToUpdate = update.booksToUpdate,
linesToRemove = emptyList(), booksToRemove = update.booksToRemove,
books = emptyList(), storiesToInsert = update.storiesToInsert,
stories = emptyList(), storiesToUpdate = update.storiesToUpdate,
lines = lines, storiesToRemove = update.storiesToRemove,
linesToInsert = update.linesToInsert,
linesToUpdate = update.linesToUpdate,
linesToRemove = update.linesToRemove,
) )
} }
@Throws(IncompatibleSheetStructure::class, Exception::class) private data class DatabaseUpdateScope(
private suspend fun fetchAdventureBooks(): List<AdventureBook> { val booksToInsert: MutableList<AdventureBookDbo> = mutableListOf(),
return googleRepository.fetch { sheet -> val booksToUpdate: MutableList<AdventureBookDbo> = mutableListOf(),
val request = sheet.get(Adventures.ID, Adventures.ADVENTURES) val booksToRemove: MutableList<AdventureBookDbo.BookId> = mutableListOf(),
adventureBookParser.parse(sheet = request.execute()) val storiesToInsert: MutableList<AdventureStoryDbo> = mutableListOf(),
} val storiesToUpdate: MutableList<AdventureStoryDbo> = mutableListOf(),
} val storiesToRemove: MutableList<AdventureStoryDbo.StoryId> = mutableListOf(),
val linesToInsert: MutableList<AdventureLineDbo> = mutableListOf(),
val linesToUpdate: MutableList<AdventureLineDbo> = mutableListOf(),
val linesToRemove: MutableList<AdventureLineDbo.LineId> = mutableListOf(),
)
@Throws(IncompatibleSheetStructure::class, Exception::class) private inner class Service {
private suspend fun fetchAdventureStory(documentId: String): List<AdventureStory> { @Throws(IncompatibleSheetStructure::class, Exception::class)
return googleRepository.fetch { sheet -> suspend fun fetchAdventureBooks(): List<AdventureBookDbo> {
val request = sheet.get(documentId, Adventures.ADVENTURES) return googleRepository.fetch { sheet ->
adventureStoryParser.parse(sheet = request.execute()) val request = sheet.get(Adventures.ID, Adventures.ADVENTURES)
} adventureBookParser.parse(sheet = request.execute())
} }.map { book ->
adventureDboFactory.convertToDbo(book = book)
private suspend fun fetchAdventureLine( }
documentId: String, }
storyTitle: String,
): List<AdventureLine> { @Throws(IncompatibleSheetStructure::class, Exception::class)
return try { suspend fun fetchAdventureStories(documentId: String): List<AdventureStoryDbo> {
googleRepository.fetch { sheet -> return googleRepository.fetch { sheet ->
val request = sheet.get(documentId, storyTitle) val request = sheet.get(documentId, Adventures.ADVENTURES)
adventureStoryLineParser.parse(sheet = request.execute()) adventureStoryParser.parse(sheet = request.execute())
}.mapIndexed { index, story ->
adventureDboFactory.convertToDbo(
story = story,
index = index,
documentId = documentId,
)
}
}
@Throws(Exception::class)
suspend fun fetchAdventureLine(
documentId: String,
storyTitle: String,
): List<AdventureLineDbo> {
return try {
googleRepository.fetch { sheet ->
val request = sheet.get(documentId, storyTitle)
adventureStoryLineParser.parse(sheet = request.execute())
}
} catch (exception: Exception) {
emptyList()
}.mapIndexed { index, line ->
adventureDboFactory.convertToDbo(
adventureLine = line,
index = index,
documentId = documentId,
storyTitle = storyTitle,
)
} }
} catch (exception: Exception) {
emptyList()
} }
} }
} }

View file

@ -21,8 +21,8 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.pixelized.rplexicon.ui.composable.images.AsyncImage
import com.pixelized.rplexicon.ui.composable.images.BackgroundImage import com.pixelized.rplexicon.ui.composable.images.BackgroundImage
import com.pixelized.rplexicon.ui.composable.images.rememberBackgroundGradient
import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.annotateWithDropCap import com.pixelized.rplexicon.utilitary.annotateWithDropCap
import com.pixelized.rplexicon.utilitary.extentions.lexicon import com.pixelized.rplexicon.utilitary.extentions.lexicon
@ -53,6 +53,7 @@ fun AdventureBook(
.matchParentSize() .matchParentSize()
.align(alignment = Alignment.TopCenter), .align(alignment = Alignment.TopCenter),
colorFilter = null, colorFilter = null,
background = rememberBackgroundGradient(0.2f, 1.0f),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
alignment = Alignment.TopCenter, alignment = Alignment.TopCenter,
model = item.bookIcon, model = item.bookIcon,

View file

@ -122,7 +122,10 @@ private fun AdventureListContent(
horizontalArrangement = Arrangement.spacedBy(space = 8.dp), horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
verticalArrangement = Arrangement.spacedBy(space = 8.dp), verticalArrangement = Arrangement.spacedBy(space = 8.dp),
) { ) {
items(items = items.value) { items(
items = items.value,
key = { it.documentId },
) {
AdventureBook( AdventureBook(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View file

@ -194,10 +194,7 @@ private fun AdventureDetailContent(
style = MaterialTheme.lexicon.typography.base.titleLarge, style = MaterialTheme.lexicon.typography.base.titleLarge,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
text = annotateMajWithDropCap( text = title.value ?: "",
text = title.value ?: "",
style = MaterialTheme.lexicon.typography.dropCap.titleLarge
),
) )
}, },
actions = { actions = {