Refactor the Adventure Book/Story/Line model. Avoid clearing of already downloaded stories.
This commit is contained in:
		
							parent
							
								
									8cfa0ac272
								
							
						
					
					
						commit
						250d9cbb7c
					
				
					 13 changed files with 342 additions and 229 deletions
				
			
		| 
						 | 
					@ -36,56 +36,67 @@ interface AdventureDao {
 | 
				
			||||||
    @Query("SELECT * from ${AdventureLineDbo.TABLE} where documentId = :documentId AND title = :storyTitle")
 | 
					    @Query("SELECT * from ${AdventureLineDbo.TABLE} where documentId = :documentId AND title = :storyTitle")
 | 
				
			||||||
    fun fetchAdventureLine(documentId: String, storyTitle: String): List<AdventureLineDbo>
 | 
					    fun fetchAdventureLine(documentId: String, storyTitle: String): List<AdventureLineDbo>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
					    @Insert(onConflict = OnConflictStrategy.IGNORE)
 | 
				
			||||||
    fun insertBook(book: AdventureBookDbo)
 | 
					    fun insertBook(book: AdventureBookDbo): Long
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Update
 | 
					    @Update
 | 
				
			||||||
    fun updateBook(book: AdventureBookDbo)
 | 
					    fun updateBook(book: AdventureBookDbo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun insertOrUpdateBook(book: AdventureBookDbo) {
 | 
				
			||||||
 | 
					        if (insertBook(book = book) == 1L) {
 | 
				
			||||||
 | 
					            updateBook(book = book)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Delete(entity = AdventureBookDbo::class)
 | 
					    @Delete(entity = AdventureBookDbo::class)
 | 
				
			||||||
    fun deleteBook(id: AdventureBookDbo.BookId)
 | 
					    fun deleteBook(id: AdventureBookDbo.BookId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
					    @Insert(onConflict = OnConflictStrategy.IGNORE)
 | 
				
			||||||
    fun insertStory(story: AdventureStoryDbo)
 | 
					    fun insertStory(story: AdventureStoryDbo): Long
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Update
 | 
					    @Update
 | 
				
			||||||
    fun updateStory(story: AdventureStoryDbo)
 | 
					    fun updateStory(story: AdventureStoryDbo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun insertOrUpdateStory(story: AdventureStoryDbo) {
 | 
				
			||||||
 | 
					        if (insertStory(story = story) == 1L) {
 | 
				
			||||||
 | 
					            updateStory(story = story)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Delete(entity = AdventureStoryDbo::class)
 | 
					    @Delete(entity = AdventureStoryDbo::class)
 | 
				
			||||||
    fun deleteStory(id: AdventureStoryDbo.StoryId)
 | 
					    fun deleteStory(id: AdventureStoryDbo.StoryId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Insert(onConflict = OnConflictStrategy.REPLACE)
 | 
					    @Insert(onConflict = OnConflictStrategy.IGNORE)
 | 
				
			||||||
    fun insertLine(line: AdventureLineDbo)
 | 
					    fun insertLine(line: AdventureLineDbo): Long
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Update
 | 
					    @Update
 | 
				
			||||||
    fun updateLine(line: AdventureLineDbo)
 | 
					    fun updateLine(line: AdventureLineDbo)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun insertOrUpdateLine(line: AdventureLineDbo) {
 | 
				
			||||||
 | 
					        if (insertLine(line = line) == 1L) {
 | 
				
			||||||
 | 
					            updateLine(line = line)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Delete(entity = AdventureLineDbo::class)
 | 
					    @Delete(entity = AdventureLineDbo::class)
 | 
				
			||||||
    fun deleteLines(id: AdventureLineDbo.LineId)
 | 
					    fun deleteLines(id: AdventureLineDbo.LineId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Transaction
 | 
					    @Transaction
 | 
				
			||||||
    fun update(
 | 
					    fun update(
 | 
				
			||||||
        booksToInsert: List<AdventureBookDbo>,
 | 
					        booksToInsertOrUpdate: List<AdventureBookDbo> = emptyList(),
 | 
				
			||||||
        booksToUpdate: List<AdventureBookDbo>,
 | 
					        booksToRemove: List<AdventureBookDbo.BookId> = emptyList(),
 | 
				
			||||||
        booksToRemove: List<AdventureBookDbo.BookId>,
 | 
					        storiesToInsertOrUpdate: List<AdventureStoryDbo> = emptyList(),
 | 
				
			||||||
        storiesToInsert: List<AdventureStoryDbo>,
 | 
					        storiesToRemove: List<AdventureStoryDbo.StoryId> = emptyList(),
 | 
				
			||||||
        storiesToUpdate: List<AdventureStoryDbo>,
 | 
					        linesToInsertOrUpdate: List<AdventureLineDbo> = emptyList(),
 | 
				
			||||||
        storiesToRemove: List<AdventureStoryDbo.StoryId>,
 | 
					        linesToRemove: List<AdventureLineDbo.LineId> = emptyList(),
 | 
				
			||||||
        linesToInsert: List<AdventureLineDbo>,
 | 
					 | 
				
			||||||
        linesToUpdate: List<AdventureLineDbo>,
 | 
					 | 
				
			||||||
        linesToRemove: List<AdventureLineDbo.LineId>,
 | 
					 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        // First remove the stuff
 | 
					        // First remove the stuff
 | 
				
			||||||
        linesToRemove.forEach { deleteLines(id = it) }
 | 
					        linesToRemove.forEach { deleteLines(id = it) }
 | 
				
			||||||
        storiesToRemove.forEach { deleteStory(id = it) }
 | 
					        storiesToRemove.forEach { deleteStory(id = it) }
 | 
				
			||||||
        booksToRemove.forEach { deleteBook(id = it) }
 | 
					        booksToRemove.forEach { deleteBook(id = it) }
 | 
				
			||||||
        // then insert the stuff
 | 
					        // then insert or update the stuff
 | 
				
			||||||
        booksToInsert.forEach { insertBook(book = it) }
 | 
					        booksToInsertOrUpdate.forEach { insertOrUpdateBook(book = it) }
 | 
				
			||||||
        storiesToInsert.forEach { insertStory(story = it) }
 | 
					        storiesToInsertOrUpdate.forEach { insertOrUpdateStory(story = it) }
 | 
				
			||||||
        linesToInsert.forEach { insertLine(line = it) }
 | 
					        linesToInsertOrUpdate.forEach { insertOrUpdateLine(line = it) }
 | 
				
			||||||
        // and update the stuff
 | 
					 | 
				
			||||||
        booksToUpdate.forEach { updateBook(book = it) }
 | 
					 | 
				
			||||||
        storiesToUpdate.forEach { updateStory(story = it) }
 | 
					 | 
				
			||||||
        linesToUpdate.forEach { updateLine(line = it) }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,13 +0,0 @@
 | 
				
			||||||
package com.pixelized.rplexicon.data.model.adventure
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.net.Uri
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
data class Adventure(
 | 
					 | 
				
			||||||
    val bookTitle: String,
 | 
					 | 
				
			||||||
    val bookIcon: Uri?,
 | 
					 | 
				
			||||||
    val storyCategory: String?,
 | 
					 | 
				
			||||||
    val storyTitle: String,
 | 
					 | 
				
			||||||
    val storyBackground: Uri?,
 | 
					 | 
				
			||||||
    val documentId: String,
 | 
					 | 
				
			||||||
    val story: List<AdventureLine>,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,6 @@
 | 
				
			||||||
package com.pixelized.rplexicon.data.model.adventure
 | 
					package com.pixelized.rplexicon.data.model.adventure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
data class AdventureLine(
 | 
					data class AdventureLine(
 | 
				
			||||||
    val format: Format,
 | 
					    val format: AdventureLineFormat,
 | 
				
			||||||
    val text: String,
 | 
					    val text: String,
 | 
				
			||||||
) {
 | 
					)
 | 
				
			||||||
    enum class Format {
 | 
					 | 
				
			||||||
        TITLE,
 | 
					 | 
				
			||||||
        SUB_TITLE,
 | 
					 | 
				
			||||||
        CHAPTER,
 | 
					 | 
				
			||||||
        PARAGRAPH,
 | 
					 | 
				
			||||||
        DIALOGUE,
 | 
					 | 
				
			||||||
        ANNEX,
 | 
					 | 
				
			||||||
        LEGEND,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.data.model.adventure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum class AdventureLineFormat {
 | 
				
			||||||
 | 
					    TITLE,
 | 
				
			||||||
 | 
					    SUB_TITLE,
 | 
				
			||||||
 | 
					    CHAPTER,
 | 
				
			||||||
 | 
					    PARAGRAPH,
 | 
				
			||||||
 | 
					    DIALOGUE,
 | 
				
			||||||
 | 
					    ANNEX,
 | 
				
			||||||
 | 
					    LEGEND,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.data.model.adventure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					data class Book(
 | 
				
			||||||
 | 
					    val documentId: String,
 | 
				
			||||||
 | 
					    val title: String,
 | 
				
			||||||
 | 
					    val icon: Uri?,
 | 
				
			||||||
 | 
					    val updating: Boolean,
 | 
				
			||||||
 | 
					    val stories: List<Story>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    data class Story(
 | 
				
			||||||
 | 
					        val title: String,
 | 
				
			||||||
 | 
					        val category: String?,
 | 
				
			||||||
 | 
					        val background: Uri?,
 | 
				
			||||||
 | 
					        val lines: List<Line>,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        data class Line(
 | 
				
			||||||
 | 
					            val format: AdventureLineFormat,
 | 
				
			||||||
 | 
					            val text: String,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package com.pixelized.rplexicon.data.parser.adventure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
					import com.google.api.services.sheets.v4.model.ValueRange
 | 
				
			||||||
import com.pixelized.rplexicon.data.model.adventure.AdventureLine
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureLine
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureLineFormat
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
					import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
				
			||||||
import javax.inject.Inject
 | 
					import javax.inject.Inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,15 +23,15 @@ class AdventureStoryLineParser @Inject constructor() {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun ArrayList<*>.parseFormat(): AdventureLine.Format? =
 | 
					    private fun ArrayList<*>.parseFormat(): AdventureLineFormat? =
 | 
				
			||||||
        when (this.getOrNull(0) as? String) {
 | 
					        when (this.getOrNull(0) as? String) {
 | 
				
			||||||
            TITLE -> AdventureLine.Format.TITLE
 | 
					            TITLE -> AdventureLineFormat.TITLE
 | 
				
			||||||
            SUB_TITLE -> AdventureLine.Format.SUB_TITLE
 | 
					            SUB_TITLE -> AdventureLineFormat.SUB_TITLE
 | 
				
			||||||
            CHAPTER -> AdventureLine.Format.CHAPTER
 | 
					            CHAPTER -> AdventureLineFormat.CHAPTER
 | 
				
			||||||
            PARAGRAPH -> AdventureLine.Format.PARAGRAPH
 | 
					            PARAGRAPH -> AdventureLineFormat.PARAGRAPH
 | 
				
			||||||
            DIALOGUE -> AdventureLine.Format.DIALOGUE
 | 
					            DIALOGUE -> AdventureLineFormat.DIALOGUE
 | 
				
			||||||
            ANNEX -> AdventureLine.Format.ANNEX
 | 
					            ANNEX -> AdventureLineFormat.ANNEX
 | 
				
			||||||
            LEGEND -> AdventureLine.Format.LEGEND
 | 
					            LEGEND -> AdventureLineFormat.LEGEND
 | 
				
			||||||
            else -> null
 | 
					            else -> null
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,47 +3,54 @@ package com.pixelized.rplexicon.data.repository.adventure
 | 
				
			||||||
import com.pixelized.rplexicon.data.database.adventure.AdventureBookDbo
 | 
					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.AdventureBook
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureBook
 | 
				
			||||||
import com.pixelized.rplexicon.data.model.adventure.AdventureLine
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureLine
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureLineFormat
 | 
				
			||||||
import com.pixelized.rplexicon.data.model.adventure.AdventureStory
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureStory
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.data.model.adventure.Book
 | 
				
			||||||
import com.pixelized.rplexicon.utilitary.extentions.string.toUriOrNull
 | 
					import com.pixelized.rplexicon.utilitary.extentions.string.toUriOrNull
 | 
				
			||||||
import javax.inject.Inject
 | 
					import javax.inject.Inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdventureDboFactory @Inject constructor() {
 | 
					class AdventureDboFactory @Inject constructor() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun convertFromDbo(
 | 
					    fun convertFromDbo(
 | 
				
			||||||
 | 
					        updatingBookId: String?,
 | 
				
			||||||
        books: List<AdventureBookDbo>,
 | 
					        books: List<AdventureBookDbo>,
 | 
				
			||||||
        stories: List<AdventureStoryDbo>,
 | 
					        stories: List<AdventureStoryDbo>,
 | 
				
			||||||
        lines: List<AdventureLineDbo>,
 | 
					        lines: List<AdventureLineDbo>,
 | 
				
			||||||
    ): List<Adventure> {
 | 
					    ): List<Book> {
 | 
				
			||||||
        return books.flatMap { book: AdventureBookDbo ->
 | 
					        val indexedStories = stories.groupBy { it.documentId }
 | 
				
			||||||
            stories
 | 
					        val indexedLines = lines.groupBy { it.documentId to it.story }
 | 
				
			||||||
                .filter { story -> story.documentId == book.documentId }
 | 
					        return books.map { book: AdventureBookDbo ->
 | 
				
			||||||
                .sortedBy { story -> story.index }
 | 
					            Book(
 | 
				
			||||||
                .map { story: AdventureStoryDbo ->
 | 
					                documentId = book.documentId,
 | 
				
			||||||
                    Adventure(
 | 
					                title = book.title,
 | 
				
			||||||
                        bookTitle = book.title,
 | 
					                icon = book.icon.toUriOrNull(),
 | 
				
			||||||
                        bookIcon = book.icon.toUriOrNull(),
 | 
					                updating = book.documentId == updatingBookId,
 | 
				
			||||||
                        documentId = book.documentId,
 | 
					                stories = indexedStories[book.documentId]
 | 
				
			||||||
                        storyTitle = story.title,
 | 
					                    ?.sortedBy { story -> story.index }
 | 
				
			||||||
                        storyCategory = story.category,
 | 
					                    ?.map { story ->
 | 
				
			||||||
                        storyBackground = story.background.toUriOrNull(),
 | 
					                        Book.Story(
 | 
				
			||||||
                        story = lines
 | 
					                            title = story.title,
 | 
				
			||||||
                            .filter { it.documentId == story.documentId && it.story == story.title }
 | 
					                            category = story.category,
 | 
				
			||||||
                            .sortedBy { it.index }
 | 
					                            background = story.background.toUriOrNull(),
 | 
				
			||||||
                            .map { line ->
 | 
					                            lines = indexedLines[book.documentId to story.title]
 | 
				
			||||||
                                AdventureLine(
 | 
					                                ?.sortedBy { it.index }
 | 
				
			||||||
                                    text = line.text,
 | 
					                                ?.map { line ->
 | 
				
			||||||
                                    format = try {
 | 
					                                    Book.Story.Line(
 | 
				
			||||||
                                        AdventureLine.Format.valueOf(line.format)
 | 
					                                        text = line.text,
 | 
				
			||||||
                                    } catch (_: Exception) {
 | 
					                                        format = try {
 | 
				
			||||||
                                        AdventureLine.Format.PARAGRAPH
 | 
					                                            AdventureLineFormat.valueOf(line.format)
 | 
				
			||||||
                                    },
 | 
					                                        } catch (_: Exception) {
 | 
				
			||||||
                                )
 | 
					                                            AdventureLineFormat.PARAGRAPH
 | 
				
			||||||
                            },
 | 
					                                        },
 | 
				
			||||||
                    )
 | 
					                                    )
 | 
				
			||||||
                }
 | 
					                                }
 | 
				
			||||||
 | 
					                                ?: emptyList(),
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    ?: emptyList(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import com.pixelized.rplexicon.data.database.CompanionDatabase
 | 
				
			||||||
import com.pixelized.rplexicon.data.database.adventure.AdventureBookDbo
 | 
					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.Book
 | 
				
			||||||
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,6 +17,7 @@ import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
import kotlinx.coroutines.Job
 | 
					import kotlinx.coroutines.Job
 | 
				
			||||||
import kotlinx.coroutines.NonCancellable
 | 
					import kotlinx.coroutines.NonCancellable
 | 
				
			||||||
import kotlinx.coroutines.flow.Flow
 | 
					import kotlinx.coroutines.flow.Flow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.MutableStateFlow
 | 
				
			||||||
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
 | 
				
			||||||
| 
						 | 
					@ -24,7 +25,6 @@ import kotlinx.coroutines.flow.stateIn
 | 
				
			||||||
import kotlinx.coroutines.withContext
 | 
					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(
 | 
				
			||||||
| 
						 | 
					@ -41,13 +41,16 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
    var lastSuccessFullUpdate: Update = Update.INITIAL
 | 
					    var lastSuccessFullUpdate: Update = Update.INITIAL
 | 
				
			||||||
        private set
 | 
					        private set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val updatingBookId = MutableStateFlow<String?>(null)
 | 
				
			||||||
    private val adventures = database.let { database ->
 | 
					    private val adventures = database.let { database ->
 | 
				
			||||||
        combine(
 | 
					        combine(
 | 
				
			||||||
 | 
					            updatingBookId,
 | 
				
			||||||
            database.getBooksFlow(),
 | 
					            database.getBooksFlow(),
 | 
				
			||||||
            database.getStoriesFlow(),
 | 
					            database.getStoriesFlow(),
 | 
				
			||||||
            database.getStoryLinesFlow(),
 | 
					            database.getStoryLinesFlow(),
 | 
				
			||||||
        ) { books, stories, lines ->
 | 
					        ) { updatingBookId, books, stories, lines ->
 | 
				
			||||||
            adventureDboFactory.convertFromDbo(
 | 
					            adventureDboFactory.convertFromDbo(
 | 
				
			||||||
 | 
					                updatingBookId = updatingBookId,
 | 
				
			||||||
                books = books,
 | 
					                books = books,
 | 
				
			||||||
                stories = stories,
 | 
					                stories = stories,
 | 
				
			||||||
                lines = lines,
 | 
					                lines = lines,
 | 
				
			||||||
| 
						 | 
					@ -59,49 +62,112 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun bookFlow(): Flow<List<Adventure>> {
 | 
					    fun booksFlow(): Flow<List<Book>> {
 | 
				
			||||||
        return adventures
 | 
					        return adventures
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun storyFlow(bookTitle: String): Flow<List<Adventure>> {
 | 
					    fun bookFlow(documentId: String): Flow<Book?> {
 | 
				
			||||||
        return adventures.map { adventures ->
 | 
					        return adventures.map { adventures ->
 | 
				
			||||||
            adventures.filter { adventure ->
 | 
					            adventures.firstOrNull { adventure ->
 | 
				
			||||||
                adventure.bookTitle == bookTitle
 | 
					                adventure.documentId == documentId
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun adventureFlow(documentId: String, adventureTitle: String): Flow<Adventure?> {
 | 
					    fun storyFlow(documentId: String, storyTitle: String): Flow<Book.Story?> {
 | 
				
			||||||
        return adventures.map { adventures ->
 | 
					        return adventures.map { adventures ->
 | 
				
			||||||
            adventures.firstOrNull { adventure ->
 | 
					            adventures.firstNotNullOfOrNull { adventure ->
 | 
				
			||||||
                adventure.documentId == documentId && adventure.storyTitle == adventureTitle
 | 
					                if (adventure.documentId == documentId) {
 | 
				
			||||||
 | 
					                    adventure.stories.firstOrNull { it.title == storyTitle }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    null
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Throws(IncompatibleSheetStructure::class, Exception::class)
 | 
					    @Throws(IncompatibleSheetStructure::class, Exception::class)
 | 
				
			||||||
    suspend fun fetchBooks() = update {
 | 
					    suspend fun fetchBooks() {
 | 
				
			||||||
        fetchAndCompareBooks()
 | 
					        val exceptions = mutableListOf<Exception>()
 | 
				
			||||||
 | 
					        val cache = database.fetchAdventureBooks()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (booksToInsert + booksToUpdate).forEach { book ->
 | 
					        try {
 | 
				
			||||||
            fetchAndCompareStories(documentId = book.documentId)
 | 
					            val update = service.fetchAdventureBooks().sortedBy { it.title }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            val booksToRemove = cache.map { it.id }.toMutableList()
 | 
				
			||||||
 | 
					            val booksToInsertOrUpdate = mutableListOf<AdventureBookDbo>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            update.forEach { item ->
 | 
				
			||||||
 | 
					                booksToRemove.remove(item.id)
 | 
				
			||||||
 | 
					                booksToInsertOrUpdate.add(item)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            database.update(
 | 
				
			||||||
 | 
					                booksToRemove = booksToRemove,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            booksToInsertOrUpdate.forEach { book ->
 | 
				
			||||||
 | 
					                updatingBookId.value = book.documentId
 | 
				
			||||||
 | 
					                database.update(
 | 
				
			||||||
 | 
					                    booksToInsertOrUpdate = listOf(book),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                update {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        fetchAndCompareStories(
 | 
				
			||||||
 | 
					                            documentId = book.documentId
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        (storiesToInsertOrUpdate).forEach { story ->
 | 
				
			||||||
 | 
					                            try {
 | 
				
			||||||
 | 
					                                fetchAndCompareLines(
 | 
				
			||||||
 | 
					                                    documentId = story.documentId,
 | 
				
			||||||
 | 
					                                    storyTitle = story.title
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                            } catch (exception: Exception) {
 | 
				
			||||||
 | 
					                                exceptions.add(exception)
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } catch (exception: Exception) {
 | 
				
			||||||
 | 
					                        exceptions.add(exception)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            lastSuccessFullUpdate = Update.currentTime()
 | 
				
			||||||
 | 
					        } catch (exception: Exception) {
 | 
				
			||||||
 | 
					            exceptions.add(exception)
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            updatingBookId.value = null
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (exceptions.isNotEmpty()) {
 | 
				
			||||||
        (storiesToInsert + storiesToUpdate).forEach { story ->
 | 
					            throw exceptions.first()
 | 
				
			||||||
            fetchAndCompareLines(documentId = story.documentId, storyTitle = story.title)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        lastSuccessFullUpdate = Update.currentTime()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Throws(IncompatibleSheetStructure::class, Exception::class)
 | 
					    @Throws(IncompatibleSheetStructure::class, Exception::class)
 | 
				
			||||||
    suspend fun fetchStories(
 | 
					    suspend fun fetchStories(
 | 
				
			||||||
        documentId: String,
 | 
					        documentId: String,
 | 
				
			||||||
    ) = update {
 | 
					    ) = update {
 | 
				
			||||||
        fetchAndCompareStories(documentId = documentId)
 | 
					        val exceptions = mutableListOf<Exception>()
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            updatingBookId.value = documentId
 | 
				
			||||||
 | 
					            fetchAndCompareStories(documentId = documentId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        (storiesToInsert + storiesToUpdate).forEach { story ->
 | 
					            (storiesToInsertOrUpdate).forEach { story ->
 | 
				
			||||||
            fetchAndCompareLines(documentId = story.documentId, storyTitle = story.title)
 | 
					                try {
 | 
				
			||||||
 | 
					                    fetchAndCompareLines(
 | 
				
			||||||
 | 
					                        documentId = story.documentId,
 | 
				
			||||||
 | 
					                        storyTitle = story.title,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                } catch (exception: Exception) {
 | 
				
			||||||
 | 
					                    exceptions.add(exception)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (exception: Exception) {
 | 
				
			||||||
 | 
					            exceptions.add(exception)
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            updatingBookId.value = null
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (exceptions.isNotEmpty()) {
 | 
				
			||||||
 | 
					            throw exceptions.first()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,36 +176,31 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
        documentId: String,
 | 
					        documentId: String,
 | 
				
			||||||
        storyTitle: String,
 | 
					        storyTitle: String,
 | 
				
			||||||
    ) = update {
 | 
					    ) = update {
 | 
				
			||||||
        // specific case for adventure fetching, need to update the story too as the background is stored there.
 | 
					        val exceptions = mutableListOf<Exception>()
 | 
				
			||||||
        service.fetchAdventureStories(
 | 
					        try {
 | 
				
			||||||
            documentId = documentId,
 | 
					            updatingBookId.value = documentId
 | 
				
			||||||
        ).firstOrNull { story ->
 | 
					            // specific case for adventure fetching, need to update the story too as the background is stored there.
 | 
				
			||||||
            story.documentId == documentId && story.title == storyTitle
 | 
					            service.fetchAdventureStories(
 | 
				
			||||||
        }?.let { story ->
 | 
					                documentId = documentId,
 | 
				
			||||||
            storiesToUpdate.add(story)
 | 
					            ).firstOrNull { story ->
 | 
				
			||||||
        }
 | 
					                story.documentId == documentId && story.title == storyTitle
 | 
				
			||||||
 | 
					            }?.let { story ->
 | 
				
			||||||
        fetchAndCompareLines(documentId = documentId, storyTitle = storyTitle)
 | 
					                storiesToInsertOrUpdate.add(story)
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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)
 | 
					            try {
 | 
				
			||||||
        booksToUpdate.addAll(elements = toUpdate)
 | 
					                fetchAndCompareLines(documentId = documentId, storyTitle = storyTitle)
 | 
				
			||||||
        booksToRemove.addAll(elements = toRemove)
 | 
					            } catch (exception: Exception) {
 | 
				
			||||||
 | 
					                exceptions.add(exception)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (exception: Exception) {
 | 
				
			||||||
 | 
					            exceptions.add(exception)
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
 | 
					            updatingBookId.value = null
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (exceptions.isNotEmpty()) {
 | 
				
			||||||
 | 
					            throw exceptions.first()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private suspend fun DatabaseUpdateScope.fetchAndCompareStories(
 | 
					    private suspend fun DatabaseUpdateScope.fetchAndCompareStories(
 | 
				
			||||||
| 
						 | 
					@ -151,7 +212,7 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
        ): Boolean {
 | 
					        ): Boolean {
 | 
				
			||||||
            return cache?.let {
 | 
					            return cache?.let {
 | 
				
			||||||
                it.revision < item.revision || cache.index != item.index
 | 
					                it.revision < item.revision || cache.index != item.index
 | 
				
			||||||
            } ?: false
 | 
					            } ?: true
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val cache = database.fetchAdventureStories(
 | 
					        val cache = database.fetchAdventureStories(
 | 
				
			||||||
| 
						 | 
					@ -164,49 +225,39 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val toRemove = cache.keys.toMutableList()
 | 
					        val toRemove = cache.keys.toMutableList()
 | 
				
			||||||
        val toInsert = mutableListOf<AdventureStoryDbo>()
 | 
					        val toInsertOrUpdate = mutableListOf<AdventureStoryDbo>()
 | 
				
			||||||
        val toUpdate = mutableListOf<AdventureStoryDbo>()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        update.forEach { item ->
 | 
					        update.forEach { item ->
 | 
				
			||||||
            when (toRemove.remove(item.id)) {
 | 
					            toRemove.remove(item.id)
 | 
				
			||||||
                true -> if (shouldUpdate(cache = cache[item.id], item = item)) toUpdate.add(item)
 | 
					            if (shouldUpdate(cache = cache[item.id], item = item)) {
 | 
				
			||||||
                else -> toInsert.add(item)
 | 
					                toInsertOrUpdate.add(item)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        storiesToInsert.addAll(elements = toInsert)
 | 
					 | 
				
			||||||
        storiesToUpdate.addAll(elements = toUpdate)
 | 
					 | 
				
			||||||
        storiesToRemove.addAll(elements = toRemove)
 | 
					        storiesToRemove.addAll(elements = toRemove)
 | 
				
			||||||
 | 
					        storiesToInsertOrUpdate.addAll(elements = toInsertOrUpdate)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private suspend fun DatabaseUpdateScope.fetchAndCompareLines(
 | 
					    private suspend fun DatabaseUpdateScope.fetchAndCompareLines(
 | 
				
			||||||
        documentId: String,
 | 
					        documentId: String,
 | 
				
			||||||
        storyTitle: String,
 | 
					        storyTitle: String,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        val cache = database.fetchAdventureLine(
 | 
					        val cache: List<AdventureLineDbo> = database.fetchAdventureLine(
 | 
				
			||||||
            documentId = documentId,
 | 
					            documentId = documentId,
 | 
				
			||||||
            storyTitle = storyTitle,
 | 
					            storyTitle = storyTitle,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        val update = service.fetchAdventureLine(
 | 
					        val update: List<AdventureLineDbo> = service.fetchAdventureLine(
 | 
				
			||||||
            documentId = documentId,
 | 
					            documentId = documentId,
 | 
				
			||||||
            storyTitle = storyTitle,
 | 
					            storyTitle = storyTitle,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        // if cache is smaller than the update we need to insert
 | 
					        // if cache is bigger than the update we need to delete the excess lines.
 | 
				
			||||||
        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) {
 | 
					        if (update.size < cache.size) {
 | 
				
			||||||
            (update.size until cache.size).forEach {
 | 
					            (update.size until cache.size).forEach {
 | 
				
			||||||
                linesToRemove.add(cache[it].id)
 | 
					                linesToRemove.add(cache[it].id)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // then we update the rest
 | 
					        // then we insert / update
 | 
				
			||||||
        (0 until min(cache.size, update.size)).forEach {
 | 
					        linesToInsertOrUpdate.addAll(update)
 | 
				
			||||||
            linesToUpdate.add(update[it])
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private suspend inline fun update(
 | 
					    private suspend inline fun update(
 | 
				
			||||||
| 
						 | 
					@ -217,27 +268,21 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
        lambda.invoke(update)
 | 
					        lambda.invoke(update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        database.update(
 | 
					        database.update(
 | 
				
			||||||
            booksToInsert = update.booksToInsert,
 | 
					            booksToInsertOrUpdate = update.booksToInsertOrUpdate,
 | 
				
			||||||
            booksToUpdate = update.booksToUpdate,
 | 
					 | 
				
			||||||
            booksToRemove = update.booksToRemove,
 | 
					            booksToRemove = update.booksToRemove,
 | 
				
			||||||
            storiesToInsert = update.storiesToInsert,
 | 
					            storiesToInsertOrUpdate = update.storiesToInsertOrUpdate,
 | 
				
			||||||
            storiesToUpdate = update.storiesToUpdate,
 | 
					 | 
				
			||||||
            storiesToRemove = update.storiesToRemove,
 | 
					            storiesToRemove = update.storiesToRemove,
 | 
				
			||||||
            linesToInsert = update.linesToInsert,
 | 
					            linesToInsertOrUpdate = update.linesToInsertOrUpdate,
 | 
				
			||||||
            linesToUpdate = update.linesToUpdate,
 | 
					 | 
				
			||||||
            linesToRemove = update.linesToRemove,
 | 
					            linesToRemove = update.linesToRemove,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private data class DatabaseUpdateScope(
 | 
					    private data class DatabaseUpdateScope(
 | 
				
			||||||
        val booksToInsert: MutableList<AdventureBookDbo> = mutableListOf(),
 | 
					        val booksToInsertOrUpdate: MutableList<AdventureBookDbo> = mutableListOf(),
 | 
				
			||||||
        val booksToUpdate: MutableList<AdventureBookDbo> = mutableListOf(),
 | 
					 | 
				
			||||||
        val booksToRemove: MutableList<AdventureBookDbo.BookId> = mutableListOf(),
 | 
					        val booksToRemove: MutableList<AdventureBookDbo.BookId> = mutableListOf(),
 | 
				
			||||||
        val storiesToInsert: MutableList<AdventureStoryDbo> = mutableListOf(),
 | 
					        val storiesToInsertOrUpdate: MutableList<AdventureStoryDbo> = mutableListOf(),
 | 
				
			||||||
        val storiesToUpdate: MutableList<AdventureStoryDbo> = mutableListOf(),
 | 
					 | 
				
			||||||
        val storiesToRemove: MutableList<AdventureStoryDbo.StoryId> = mutableListOf(),
 | 
					        val storiesToRemove: MutableList<AdventureStoryDbo.StoryId> = mutableListOf(),
 | 
				
			||||||
        val linesToInsert: MutableList<AdventureLineDbo> = mutableListOf(),
 | 
					        val linesToInsertOrUpdate: MutableList<AdventureLineDbo> = mutableListOf(),
 | 
				
			||||||
        val linesToUpdate: MutableList<AdventureLineDbo> = mutableListOf(),
 | 
					 | 
				
			||||||
        val linesToRemove: MutableList<AdventureLineDbo.LineId> = mutableListOf(),
 | 
					        val linesToRemove: MutableList<AdventureLineDbo.LineId> = mutableListOf(),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -270,13 +315,9 @@ class AdventureRepository @Inject constructor(
 | 
				
			||||||
            documentId: String,
 | 
					            documentId: String,
 | 
				
			||||||
            storyTitle: String,
 | 
					            storyTitle: String,
 | 
				
			||||||
        ): List<AdventureLineDbo> {
 | 
					        ): List<AdventureLineDbo> {
 | 
				
			||||||
            return try {
 | 
					            return googleRepository.fetch { sheet ->
 | 
				
			||||||
                googleRepository.fetch { sheet ->
 | 
					                val request = sheet.get(documentId, storyTitle)
 | 
				
			||||||
                    val request = sheet.get(documentId, storyTitle)
 | 
					                adventureStoryLineParser.parse(sheet = request.execute())
 | 
				
			||||||
                    adventureStoryLineParser.parse(sheet = request.execute())
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } catch (exception: Exception) {
 | 
					 | 
				
			||||||
                emptyList()
 | 
					 | 
				
			||||||
            }.mapIndexed { index, line ->
 | 
					            }.mapIndexed { index, line ->
 | 
				
			||||||
                adventureDboFactory.convertToDbo(
 | 
					                adventureDboFactory.convertToDbo(
 | 
				
			||||||
                    adventureLine = line,
 | 
					                    adventureLine = line,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,12 @@
 | 
				
			||||||
package com.pixelized.rplexicon.ui.screens.adventure.book
 | 
					package com.pixelized.rplexicon.ui.screens.adventure.book
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.res.Configuration
 | 
					import android.content.res.Configuration
 | 
				
			||||||
import android.net.Uri
 | 
					import androidx.compose.animation.core.animateFloatAsState
 | 
				
			||||||
import androidx.compose.foundation.clickable
 | 
					import androidx.compose.foundation.clickable
 | 
				
			||||||
import androidx.compose.foundation.layout.Box
 | 
					import androidx.compose.foundation.layout.Box
 | 
				
			||||||
import androidx.compose.foundation.layout.PaddingValues
 | 
					import androidx.compose.foundation.layout.PaddingValues
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.aspectRatio
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.fillMaxWidth
 | 
				
			||||||
import androidx.compose.foundation.layout.padding
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
import androidx.compose.foundation.shape.RoundedCornerShape
 | 
					import androidx.compose.foundation.shape.RoundedCornerShape
 | 
				
			||||||
import androidx.compose.material3.MaterialTheme
 | 
					import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
| 
						 | 
					@ -20,10 +22,13 @@ import androidx.compose.ui.graphics.Shape
 | 
				
			||||||
import androidx.compose.ui.layout.ContentScale
 | 
					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.tooling.preview.PreviewParameter
 | 
				
			||||||
 | 
					import androidx.compose.ui.tooling.preview.PreviewParameterProvider
 | 
				
			||||||
import androidx.compose.ui.unit.dp
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
import com.pixelized.rplexicon.R
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
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.composable.images.rememberBackgroundGradient
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.composable.images.rememberSaturationFilter
 | 
				
			||||||
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
 | 
				
			||||||
| 
						 | 
					@ -34,6 +39,7 @@ data class AdventureBookUio(
 | 
				
			||||||
    val bookIcon: Any?,
 | 
					    val bookIcon: Any?,
 | 
				
			||||||
    val documentId: String,
 | 
					    val documentId: String,
 | 
				
			||||||
    val adventureTitle: String?,
 | 
					    val adventureTitle: String?,
 | 
				
			||||||
 | 
					    val updating: Boolean,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
| 
						 | 
					@ -47,14 +53,19 @@ fun AdventureBook(
 | 
				
			||||||
    Box(
 | 
					    Box(
 | 
				
			||||||
        modifier = Modifier
 | 
					        modifier = Modifier
 | 
				
			||||||
            .clip(shape = shape)
 | 
					            .clip(shape = shape)
 | 
				
			||||||
            .clickable(onClick = onClick)
 | 
					            .clickable(enabled = item.updating.not(), onClick = onClick)
 | 
				
			||||||
            .then(other = modifier),
 | 
					            .then(other = modifier),
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        BackgroundImage(
 | 
					        BackgroundImage(
 | 
				
			||||||
            modifier = Modifier
 | 
					            modifier = Modifier
 | 
				
			||||||
                .matchParentSize()
 | 
					                .matchParentSize()
 | 
				
			||||||
                .align(alignment = Alignment.TopCenter),
 | 
					                .align(alignment = Alignment.TopCenter),
 | 
				
			||||||
            colorFilter = null,
 | 
					            colorFilter = rememberSaturationFilter(
 | 
				
			||||||
 | 
					                saturation = animateFloatAsState(
 | 
				
			||||||
 | 
					                    targetValue = if (item.updating) 0f else 1f,
 | 
				
			||||||
 | 
					                    label = "book item saturation animation"
 | 
				
			||||||
 | 
					                ).value,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            background = rememberBackgroundGradient(0.2f, 1.0f),
 | 
					            background = rememberBackgroundGradient(0.2f, 1.0f),
 | 
				
			||||||
            contentScale = ContentScale.FillWidth,
 | 
					            contentScale = ContentScale.FillWidth,
 | 
				
			||||||
            alignment = Alignment.TopCenter,
 | 
					            alignment = Alignment.TopCenter,
 | 
				
			||||||
| 
						 | 
					@ -77,18 +88,38 @@ fun AdventureBook(
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
 | 
					@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
 | 
				
			||||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
 | 
					@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
 | 
				
			||||||
private fun AdventureItemPreview() {
 | 
					private fun AdventureItemPreview(
 | 
				
			||||||
 | 
					    @PreviewParameter(AdventureItemProvider::class) preview: AdventureBookUio,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
    LexiconTheme {
 | 
					    LexiconTheme {
 | 
				
			||||||
        Surface {
 | 
					        Surface {
 | 
				
			||||||
            AdventureBook(
 | 
					            AdventureBook(
 | 
				
			||||||
                item = AdventureBookUio(
 | 
					                modifier = Modifier
 | 
				
			||||||
                    bookIcon = R.drawable.icbg_book_generic_c_unfaded,
 | 
					                    .fillMaxWidth(0.4f)
 | 
				
			||||||
                    bookTitle = "Les chroniques d'une orc",
 | 
					                    .aspectRatio(ratio = 0.8f),
 | 
				
			||||||
                    documentId = "",
 | 
					                item = preview,
 | 
				
			||||||
                    adventureTitle = null,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                onClick = {},
 | 
					                onClick = {},
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private class AdventureItemProvider : PreviewParameterProvider<AdventureBookUio> {
 | 
				
			||||||
 | 
					    override val values: Sequence<AdventureBookUio> = sequenceOf(
 | 
				
			||||||
 | 
					        AdventureBookUio(
 | 
				
			||||||
 | 
					            bookIcon = R.drawable.icbg_book_generic_c_unfaded,
 | 
				
			||||||
 | 
					            bookTitle = "Les chroniques d'une orc",
 | 
				
			||||||
 | 
					            documentId = "",
 | 
				
			||||||
 | 
					            updating = true,
 | 
				
			||||||
 | 
					            adventureTitle = null,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        AdventureBookUio(
 | 
				
			||||||
 | 
					            bookIcon = R.drawable.icbg_book_generic_c_unfaded,
 | 
				
			||||||
 | 
					            bookTitle = "Les chroniques d'une orc",
 | 
				
			||||||
 | 
					            documentId = "",
 | 
				
			||||||
 | 
					            updating = false,
 | 
				
			||||||
 | 
					            adventureTitle = null,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -177,8 +177,23 @@ private fun AdventureListPreview(
 | 
				
			||||||
                                bookTitle = "Les chroniques d'une orc",
 | 
					                                bookTitle = "Les chroniques d'une orc",
 | 
				
			||||||
                                bookIcon = null,
 | 
					                                bookIcon = null,
 | 
				
			||||||
                                documentId = "",
 | 
					                                documentId = "",
 | 
				
			||||||
 | 
					                                updating = true,
 | 
				
			||||||
                                adventureTitle = null,
 | 
					                                adventureTitle = null,
 | 
				
			||||||
                            )
 | 
					                            ),
 | 
				
			||||||
 | 
					                            AdventureBookUio(
 | 
				
			||||||
 | 
					                                bookTitle = "Le tome de Strahd",
 | 
				
			||||||
 | 
					                                bookIcon = null,
 | 
				
			||||||
 | 
					                                documentId = "",
 | 
				
			||||||
 | 
					                                updating = false,
 | 
				
			||||||
 | 
					                                adventureTitle = null,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
 | 
					                            AdventureBookUio(
 | 
				
			||||||
 | 
					                                bookTitle = "Carnet de voyage d'Unathana",
 | 
				
			||||||
 | 
					                                bookIcon = null,
 | 
				
			||||||
 | 
					                                documentId = "",
 | 
				
			||||||
 | 
					                                updating = false,
 | 
				
			||||||
 | 
					                                adventureTitle = null,
 | 
				
			||||||
 | 
					                            ),
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,23 +29,19 @@ class AdventureBooksViewModel @Inject constructor(
 | 
				
			||||||
    private val _isLoading = mutableStateOf(false)
 | 
					    private val _isLoading = mutableStateOf(false)
 | 
				
			||||||
    val isLoading: State<Boolean> get() = _isLoading
 | 
					    val isLoading: State<Boolean> get() = _isLoading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _books = adventureRepository.bookFlow()
 | 
					    private val _books = adventureRepository.booksFlow()
 | 
				
			||||||
        .map { adventures ->
 | 
					        .map { adventures ->
 | 
				
			||||||
            adventures
 | 
					            adventures
 | 
				
			||||||
                .groupBy { it.documentId }
 | 
					                .sortedBy { it.title }
 | 
				
			||||||
                .mapValues { (_, adventures) ->
 | 
					                .map { book ->
 | 
				
			||||||
                    val adventure = adventures.first()
 | 
					 | 
				
			||||||
                    AdventureBookUio(
 | 
					                    AdventureBookUio(
 | 
				
			||||||
                        bookTitle = adventure.bookTitle,
 | 
					                        bookTitle = book.title,
 | 
				
			||||||
                        bookIcon = adventure.bookIcon,
 | 
					                        bookIcon = book.icon,
 | 
				
			||||||
                        documentId = adventure.documentId,
 | 
					                        documentId = book.documentId,
 | 
				
			||||||
                        adventureTitle = if (adventures.size == 1) adventure.storyTitle else null
 | 
					                        updating = book.updating,
 | 
				
			||||||
 | 
					                        adventureTitle = book.stories.takeIf { it.size == 1 }?.firstOrNull()?.title
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                .values
 | 
					 | 
				
			||||||
                .sortedBy {
 | 
					 | 
				
			||||||
                    it.bookTitle
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val books: State<List<AdventureBookUio>>
 | 
					    val books: State<List<AdventureBookUio>>
 | 
				
			||||||
| 
						 | 
					@ -67,7 +63,6 @@ class AdventureBooksViewModel @Inject constructor(
 | 
				
			||||||
                        _isLoading.value = true
 | 
					                        _isLoading.value = true
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    withContext(Dispatchers.Default) {
 | 
					                    withContext(Dispatchers.Default) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                        adventureRepository.fetchBooks()
 | 
					                        adventureRepository.fetchBooks()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                } catch (_: Exception) {
 | 
					                } catch (_: Exception) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,24 +38,24 @@ class AdventureStoriesViewModel @Inject constructor(
 | 
				
			||||||
        argument.bookTitle
 | 
					        argument.bookTitle
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _chapters = adventureRepository.storyFlow(bookTitle = argument.bookTitle)
 | 
					    private val _chapters = adventureRepository.bookFlow(documentId = argument.documentId)
 | 
				
			||||||
        .map { adventures ->
 | 
					        .map { book ->
 | 
				
			||||||
            adventures
 | 
					            book?.stories
 | 
				
			||||||
                .filter { it.bookTitle == argument.bookTitle && it.documentId == argument.documentId }
 | 
					                ?.groupBy { it.category ?: "" }
 | 
				
			||||||
                .groupBy { it.storyCategory ?: "" }
 | 
					                ?.flatMap { entry ->
 | 
				
			||||||
                .flatMap { entry ->
 | 
					 | 
				
			||||||
                    val header = listOf(
 | 
					                    val header = listOf(
 | 
				
			||||||
                        AdventureStoriesUio.AdventureCategory(title = entry.key)
 | 
					                        AdventureStoriesUio.AdventureCategory(title = entry.key)
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    val stories = entry.value.map {
 | 
					                    val stories = entry.value.map {
 | 
				
			||||||
                        AdventureStoriesUio.AdventureItem(
 | 
					                        AdventureStoriesUio.AdventureItem(
 | 
				
			||||||
                            documentId = it.documentId,
 | 
					                            documentId = book.documentId,
 | 
				
			||||||
                            bookTitle = it.bookTitle,
 | 
					                            bookTitle = book.title,
 | 
				
			||||||
                            adventureTitle = it.storyTitle,
 | 
					                            adventureTitle = it.title,
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    header + stories
 | 
					                    header + stories
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                ?: emptyList()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val chapters: State<List<AdventureStoriesUio>>
 | 
					    val chapters: State<List<AdventureStoriesUio>>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.adventure.detail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
import androidx.compose.runtime.State
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.collectAsState
 | 
					import androidx.compose.runtime.collectAsState
 | 
				
			||||||
import androidx.compose.runtime.derivedStateOf
 | 
					import androidx.compose.runtime.derivedStateOf
 | 
				
			||||||
| 
						 | 
					@ -10,7 +11,7 @@ import androidx.compose.runtime.remember
 | 
				
			||||||
import androidx.lifecycle.SavedStateHandle
 | 
					import androidx.lifecycle.SavedStateHandle
 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
import androidx.lifecycle.viewModelScope
 | 
					import androidx.lifecycle.viewModelScope
 | 
				
			||||||
import com.pixelized.rplexicon.data.model.adventure.AdventureLine.Format
 | 
					import com.pixelized.rplexicon.data.model.adventure.AdventureLineFormat
 | 
				
			||||||
import com.pixelized.rplexicon.data.repository.adventure.AdventureRepository
 | 
					import com.pixelized.rplexicon.data.repository.adventure.AdventureRepository
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.adventureDetailArgument
 | 
					import com.pixelized.rplexicon.ui.navigation.screens.adventureDetailArgument
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureLineUio.Style
 | 
					import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureLineUio.Style
 | 
				
			||||||
| 
						 | 
					@ -30,9 +31,9 @@ class AdventureDetailViewModel @Inject constructor(
 | 
				
			||||||
) : ViewModel() {
 | 
					) : ViewModel() {
 | 
				
			||||||
    private val argument = savedStateHandle.adventureDetailArgument
 | 
					    private val argument = savedStateHandle.adventureDetailArgument
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val detail = adventureRepository.adventureFlow(
 | 
					    private val detail = adventureRepository.storyFlow(
 | 
				
			||||||
        documentId = argument.documentId,
 | 
					        documentId = argument.documentId,
 | 
				
			||||||
        adventureTitle = argument.adventureTitle,
 | 
					        storyTitle = argument.adventureTitle,
 | 
				
			||||||
    ).stateIn(
 | 
					    ).stateIn(
 | 
				
			||||||
        scope = viewModelScope,
 | 
					        scope = viewModelScope,
 | 
				
			||||||
        started = SharingStarted.Lazily,
 | 
					        started = SharingStarted.Lazily,
 | 
				
			||||||
| 
						 | 
					@ -44,30 +45,30 @@ class AdventureDetailViewModel @Inject constructor(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val background: State<Uri?>
 | 
					    val background: State<Uri?>
 | 
				
			||||||
        @Composable
 | 
					        @Composable
 | 
				
			||||||
 | 
					        @Stable
 | 
				
			||||||
        get() = remember(detail) {
 | 
					        get() = remember(detail) {
 | 
				
			||||||
            detail.map { it?.storyBackground }
 | 
					            detail.map { it?.background }
 | 
				
			||||||
        }.collectAsState(initial = null)
 | 
					        }.collectAsState(initial = null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val adventure: State<List<AdventureLineUio>>
 | 
					    val adventure: State<List<AdventureLineUio>>
 | 
				
			||||||
        @Composable
 | 
					        @Composable
 | 
				
			||||||
        get() = remember(detail) {
 | 
					        get() = remember(detail) {
 | 
				
			||||||
            detail
 | 
					            detail.map { adventure ->
 | 
				
			||||||
                .map { adventure ->
 | 
					                adventure?.lines?.map { line ->
 | 
				
			||||||
                    adventure?.story?.map { line ->
 | 
					                    AdventureLineUio(
 | 
				
			||||||
                        AdventureLineUio(
 | 
					                        text = line.text,
 | 
				
			||||||
                            text = line.text,
 | 
					                        style = when (line.format) {
 | 
				
			||||||
                            style = when (line.format) {
 | 
					                            AdventureLineFormat.TITLE -> Style.TITLE
 | 
				
			||||||
                                Format.TITLE -> Style.TITLE
 | 
					                            AdventureLineFormat.SUB_TITLE -> Style.SUB_TITLE
 | 
				
			||||||
                                Format.SUB_TITLE -> Style.SUB_TITLE
 | 
					                            AdventureLineFormat.CHAPTER -> Style.CHAPTER
 | 
				
			||||||
                                Format.CHAPTER -> Style.CHAPTER
 | 
					                            AdventureLineFormat.PARAGRAPH -> Style.PARAGRAPH
 | 
				
			||||||
                                Format.PARAGRAPH -> Style.PARAGRAPH
 | 
					                            AdventureLineFormat.DIALOGUE -> Style.DIALOGUE
 | 
				
			||||||
                                Format.DIALOGUE -> Style.DIALOGUE
 | 
					                            AdventureLineFormat.ANNEX -> Style.ANNEX
 | 
				
			||||||
                                Format.ANNEX -> Style.ANNEX
 | 
					                            AdventureLineFormat.LEGEND -> Style.LEGEND
 | 
				
			||||||
                                Format.LEGEND -> Style.LEGEND
 | 
					                        },
 | 
				
			||||||
                            },
 | 
					                    )
 | 
				
			||||||
                        )
 | 
					                } ?: emptyList()
 | 
				
			||||||
                    } ?: emptyList()
 | 
					            }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        }.collectAsState(initial = emptyList())
 | 
					        }.collectAsState(initial = emptyList())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val titleCell: State<AdventureLineUio?>
 | 
					    private val titleCell: State<AdventureLineUio?>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue