From 96269cf84a0408a7cee3947d6025ece5a6af1e9c Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Wed, 19 Jun 2024 17:58:20 +0200 Subject: [PATCH] Add specific adventure book refresh feature --- .../2.json | 12 +- .../data/database/adventure/AdventureDbo.kt | 1 + .../data/model/adventure/Adventure.kt | 1 + .../data/model/adventure/AdventureBook.kt | 2 +- .../parser/adventure/AdventureBookParser.kt | 2 +- .../adventure/AdventureDboFactory.kt | 2 + .../adventure/AdventureRepository.kt | 128 ++++++++++++------ .../screens/ComposableAdventureChapter.kt | 13 +- .../screens/adventure/book/AdventureBook.kt | 2 + .../adventure/book/AdventureBooksScreen.kt | 26 ++-- .../adventure/book/AdventureBooksViewModel.kt | 1 + .../chapter/AdventureChaptersScreen.kt | 80 ++++++++--- .../chapter/AdventureChaptersViewModel.kt | 73 +++++++--- 13 files changed, 250 insertions(+), 93 deletions(-) diff --git a/app/schemas/com.pixelized.rplexicon.data.database.CompanionDatabase/2.json b/app/schemas/com.pixelized.rplexicon.data.database.CompanionDatabase/2.json index d3426c0..79e0a5d 100644 --- a/app/schemas/com.pixelized.rplexicon.data.database.CompanionDatabase/2.json +++ b/app/schemas/com.pixelized.rplexicon.data.database.CompanionDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "ad9094e2a7611443722a5415154015bf", + "identityHash": "eecd0da0c8ae6578a5d36c3b926c2fe8", "entities": [ { "tableName": "lexicon", @@ -321,7 +321,7 @@ }, { "tableName": "adventures", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookTitle` TEXT NOT NULL, `adventureTitle` TEXT NOT NULL, `adventureCategory` TEXT, `bookIcon` TEXT, `adventureBackground` TEXT, `index` INTEGER NOT NULL, `revision` INTEGER NOT NULL, PRIMARY KEY(`bookTitle`, `adventureTitle`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookTitle` TEXT NOT NULL, `adventureTitle` TEXT NOT NULL, `adventureCategory` TEXT, `bookIcon` TEXT, `adventureBackground` TEXT, `index` INTEGER NOT NULL, `revision` INTEGER NOT NULL, `documentId` TEXT NOT NULL, PRIMARY KEY(`bookTitle`, `adventureTitle`))", "fields": [ { "fieldPath": "bookTitle", @@ -364,6 +364,12 @@ "columnName": "revision", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "documentId", + "columnName": "documentId", + "affinity": "TEXT", + "notNull": true } ], "primaryKey": { @@ -440,7 +446,7 @@ "views": [], "setupQueries": [ "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, 'ad9094e2a7611443722a5415154015bf')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eecd0da0c8ae6578a5d36c3b926c2fe8')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/database/adventure/AdventureDbo.kt b/app/src/main/java/com/pixelized/rplexicon/data/database/adventure/AdventureDbo.kt index 6e60572..11176a8 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/database/adventure/AdventureDbo.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/database/adventure/AdventureDbo.kt @@ -15,6 +15,7 @@ data class AdventureDbo( val adventureBackground: String?, val index: Int, val revision: Long, + val documentId: String, ) @Entity( diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/Adventure.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/Adventure.kt index 62a1623..0f15c2a 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/Adventure.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/Adventure.kt @@ -8,5 +8,6 @@ data class Adventure( val adventureCategory: String?, val adventureTitle: String, val adventureBackground: Uri?, + val documentId: String, val story: List, ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/AdventureBook.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/AdventureBook.kt index 1ccae92..0433613 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/AdventureBook.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/adventure/AdventureBook.kt @@ -4,6 +4,6 @@ import android.net.Uri data class AdventureBook( val title: String, - val document: String, + val documentId: String, val icon: Uri?, ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/adventure/AdventureBookParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/adventure/AdventureBookParser.kt index f577ee8..92c2bad 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/adventure/AdventureBookParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/adventure/AdventureBookParser.kt @@ -23,7 +23,7 @@ class AdventureBookParser @Inject constructor() { if (character != null && document != null) { val adventure = AdventureBook( title = character, - document = document, + documentId = document, icon = row.parseUri(ICON), ) adventures.add(adventure) diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureDboFactory.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureDboFactory.kt index 83b1ed7..30bff7c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureDboFactory.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureDboFactory.kt @@ -24,6 +24,7 @@ class AdventureDboFactory @Inject constructor() { adventureTitle = adventure.adventureTitle, adventureCategory = adventure.adventureCategory, adventureBackground = adventure.adventureBackground.toUriOrNull(), + documentId = adventure.documentId, story = stories .filter { it.bookTitle == adventure.bookTitle && it.adventureTitle == adventure.adventureTitle @@ -57,6 +58,7 @@ class AdventureDboFactory @Inject constructor() { adventureBackground = story.background.toString(), index = index, revision = story.revision, + documentId = book.documentId, ) fun convertToStoryDbo( diff --git a/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureRepository.kt b/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureRepository.kt index dbcba50..84230f9 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureRepository.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/repository/adventure/AdventureRepository.kt @@ -2,6 +2,8 @@ package com.pixelized.rplexicon.data.repository.adventure import com.pixelized.rplexicon.data.database.CompanionDatabase import com.pixelized.rplexicon.data.database.adventure.AdventureDao +import com.pixelized.rplexicon.data.database.adventure.AdventureDbo +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.AdventureLine @@ -45,47 +47,23 @@ class AdventureRepository @Inject constructor( val database = database.adventureDao() val bookStore = database.findAdventures() - val bookToRemove = bookStore.associate { + val bookToRemove: MutableMap = bookStore.associate { it.bookTitle to true }.toMutableMap() - val storyToRemove = bookStore.associate { + + val storyToRemove: MutableMap, Boolean> = bookStore.associate { (it.bookTitle to it.adventureTitle) to true }.toMutableMap() - val adventures = fetchAdventureBook().flatMap { book -> + val adventures = fetchAdventureBooks().flatMap { book -> // tag this book to keep it in the database bookToRemove[book.title] = false - val stories = fetchAdventureStory(book = book) - stories.mapNotNull { story -> - // tag this story to keep it in the database - storyToRemove[book.title to story.title] = false - - val adventure = adventureDboFactory.convertToAdventureDbo( - book = book, - story = story, - index = stories.indexOf(story), - ) - val cache = database.findAdventure( - bookTitle = book.title, - adventureTitle = story.title, - ) - - if (cache == null || cache.revision < adventure.revision) { - val lines = fetchAdventureLine(book = book, story = story) - .mapIndexed { index, line -> - adventureDboFactory.convertToStoryDbo( - book = book, - story = story, - index = index, - line = line - ) - } - adventure to lines - } else { - null - } - } + fetchAdventuresContent( + database = database, + storyToRemove = storyToRemove, + book = book, + ) } database.update( @@ -99,6 +77,78 @@ class AdventureRepository @Inject constructor( ) } + @Throws(IncompatibleSheetStructure::class, Exception::class) + suspend fun fetchAdventures( + bookTitle: String, + documentId: String, + ) { + val database = database.adventureDao() + val bookStore = database.findAdventures(bookTitle = bookTitle) + + val storyToRemove: MutableMap, Boolean> = bookStore.associate { + (it.bookTitle to it.adventureTitle) to true + }.toMutableMap() + + val adventures = fetchAdventuresContent( + database = database, + storyToRemove = storyToRemove, + book = AdventureBook( + title = bookTitle, + documentId = documentId, + icon = null, + ), + ) + + database.update( + booksToRemove = emptyList(), + storiesToRemove = storyToRemove + .filter { it.value } + .map { AdventureDao.StoryPartialId(it.key.first, it.key.second) }, + adventure = adventures, + ) + } + + private suspend fun fetchAdventuresContent( + database: AdventureDao, + storyToRemove: MutableMap, Boolean>, + book: AdventureBook + ): List>> { + val stories = fetchAdventureStory( + documentId = book.documentId, + ) + return stories.mapNotNull { story -> + // tag this story to keep it in the database + storyToRemove[book.title to story.title] = false + + val adventure = adventureDboFactory.convertToAdventureDbo( + book = book, + story = story, + index = stories.indexOf(story), + ) + val cache = database.findAdventure( + bookTitle = book.title, + adventureTitle = story.title, + ) + + if (cache == null || cache.revision < adventure.revision) { + val lines = fetchAdventureLine( + documentId = book.documentId, + adventureTitle = story.title + ).mapIndexed { index, line -> + adventureDboFactory.convertToStoryDbo( + book = book, + story = story, + index = index, + line = line + ) + } + adventure to lines + } else { + null + } + } + } + fun find(bookTitle: String): List { return adventure.value.filter { adventure -> adventure.bookTitle == bookTitle @@ -112,7 +162,7 @@ class AdventureRepository @Inject constructor( } @Throws(IncompatibleSheetStructure::class, Exception::class) - private suspend fun fetchAdventureBook(): List { + private suspend fun fetchAdventureBooks(): List { return googleRepository.fetch { sheet -> val request = sheet.get(Adventures.ID, Adventures.ADVENTURES) adventureBookParser.parse(sheet = request.execute()) @@ -120,20 +170,20 @@ class AdventureRepository @Inject constructor( } @Throws(IncompatibleSheetStructure::class, Exception::class) - private suspend fun fetchAdventureStory(book: AdventureBook): List { + private suspend fun fetchAdventureStory(documentId: String): List { return googleRepository.fetch { sheet -> - val request = sheet.get(book.document, Adventures.ADVENTURES) + val request = sheet.get(documentId, Adventures.ADVENTURES) adventureStoryParser.parse(sheet = request.execute()) } } private suspend fun fetchAdventureLine( - book: AdventureBook, - story: AdventureStory + documentId: String, + adventureTitle: String, ): List { return try { googleRepository.fetch { sheet -> - val request = sheet.get(book.document, story.title) + val request = sheet.get(documentId, adventureTitle) adventureStoryLineParser.parse(sheet = request.execute()) } } catch (exception: Exception) { diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableAdventureChapter.kt b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableAdventureChapter.kt index f0704cc..85132a1 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableAdventureChapter.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/navigation/screens/ComposableAdventureChapter.kt @@ -15,19 +15,23 @@ import com.pixelized.rplexicon.utilitary.extentions.string.ARG private const val ROUTE = "adventureDetail" private const val BOOK_TITLE_ARG = "bookTitle" +private const val DOCUMENT_ID_ARG = "documentId" -val ADVENTURE_CHAPTER_ROUTE = "$ROUTE?${BOOK_TITLE_ARG.ARG}" +val ADVENTURE_CHAPTER_ROUTE = "$ROUTE?${BOOK_TITLE_ARG.ARG}&${DOCUMENT_ID_ARG.ARG}" @Stable @Immutable class AdventureChapterArgument( val bookTitle: String, + val documentId: String, ) val SavedStateHandle.adventureChaptersArgument: AdventureChapterArgument get() = AdventureChapterArgument( bookTitle = get(BOOK_TITLE_ARG) ?: error("AdventureDetailArgument missing argument: $BOOK_TITLE_ARG"), + documentId = get(DOCUMENT_ID_ARG) + ?: error("AdventureDetailArgument missing argument: $BOOK_TITLE_ARG"), ) fun NavGraphBuilder.composableAdventureChapters() { @@ -39,6 +43,10 @@ fun NavGraphBuilder.composableAdventureChapters() { type = NavType.StringType nullable = false }, + navArgument(name = DOCUMENT_ID_ARG) { + type = NavType.StringType + nullable = false + }, ) ) { AdventureChaptersScreen() @@ -47,8 +55,9 @@ fun NavGraphBuilder.composableAdventureChapters() { fun NavHostController.navigateToAdventureChapters( bookTitle: String, + documentId: String, option: NavOptionsBuilder.() -> Unit = {}, ) { - val route = "$ROUTE?$BOOK_TITLE_ARG=$bookTitle" + val route = "$ROUTE?$BOOK_TITLE_ARG=$bookTitle&$DOCUMENT_ID_ARG=$documentId" navigate(route = route, builder = option) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBook.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBook.kt index 6c734aa..f1382ff 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBook.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBook.kt @@ -31,6 +31,7 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon data class AdventureBookUio( val bookTitle: String, val bookIcon: Uri?, + val documentId: String, ) @Composable @@ -80,6 +81,7 @@ private fun AdventureItemPreview() { item = AdventureBookUio( bookIcon = null, bookTitle = "Les chroniques d'une orc", + documentId = "", ), onClick = {}, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt index cd2e234..de25257 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksScreen.kt @@ -67,11 +67,16 @@ fun AdventureBooksScreen( modifier = Modifier .fillMaxSize() .systemBarsPadding(), - items = viewModel.books, refreshState = refresh, refreshing = viewModel.isLoading, + items = viewModel.books, onBack = { screen.popBackStack() }, - onBook = { screen.navigateToAdventureChapters(bookTitle = it.bookTitle) }, + onBook = { + screen.navigateToAdventureChapters( + bookTitle = it.bookTitle, + documentId = it.documentId, + ) + }, ) } } @@ -82,9 +87,9 @@ private fun AdventureListContent( modifier: Modifier = Modifier, gridState: LazyGridState = rememberLazyGridState(), paddingValues: PaddingValues = PaddingValues(all = 16.dp), - items: State>, refreshState: PullRefreshState, refreshing: State, + items: State>, onBack: () -> Unit, onBook: (AdventureBookUio) -> Unit, ) { @@ -113,7 +118,9 @@ private fun AdventureListContent( contentAlignment = Alignment.TopCenter, ) { LazyVerticalGrid( - modifier = Modifier.fillMaxSize().pullRefresh(state = refreshState), + modifier = Modifier + .fillMaxSize() + .pullRefresh(state = refreshState), state = gridState, columns = GridCells.Fixed(3), contentPadding = paddingValues, @@ -152,21 +159,22 @@ private fun AdventureListPreview( ) { AdventureListContent( modifier = Modifier.fillMaxSize(), + refreshState = rememberPullRefreshState( + refreshing = false, + onRefresh = { }, + ), + refreshing = remember { mutableStateOf(false) }, items = remember { mutableStateOf( listOf( AdventureBookUio( bookTitle = "Les chroniques d'une orc", bookIcon = null, + documentId = "", ) ) ) }, - refreshState = rememberPullRefreshState( - refreshing = false, - onRefresh = { }, - ), - refreshing = remember { mutableStateOf(false) }, onBack = { }, onBook = { }, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksViewModel.kt index e1524a1..b7df7c6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/book/AdventureBooksViewModel.kt @@ -36,6 +36,7 @@ class AdventureBooksViewModel @Inject constructor( AdventureBookUio( bookTitle = adventure.bookTitle, bookIcon = adventure.bookIcon, + documentId = adventure.documentId, ) } .toSet() diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersScreen.kt index 217e810..c818a6e 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersScreen.kt @@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.adventure.chapter import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -11,6 +12,10 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.PullRefreshState +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -22,6 +27,8 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.res.painterResource @@ -29,17 +36,27 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.Loader import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToAdventureDetail import com.pixelized.rplexicon.ui.theme.LexiconTheme +import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterialApi::class) @Composable fun AdventureChaptersScreen( viewModel: AdventureChaptersViewModel = hiltViewModel(), ) { val screen = LocalScreenNavHost.current + val scope = rememberCoroutineScope() + val refresh = rememberPullRefreshState( + refreshing = false, + onRefresh = { + scope.launch { viewModel.update() } + }, + ) Surface( modifier = Modifier.fillMaxSize() @@ -48,6 +65,8 @@ fun AdventureChaptersScreen( modifier = Modifier .fillMaxSize() .systemBarsPadding(), + refreshState = refresh, + refreshing = viewModel.isLoading, bookTitle = viewModel.bookTitle, chapters = viewModel.chapters, onChapter = { @@ -61,12 +80,14 @@ fun AdventureChaptersScreen( } } -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable private fun AdventureChapterContent( modifier: Modifier = Modifier, lazyListState: LazyListState = rememberLazyListState(), paddingValues: PaddingValues = PaddingValues(vertical = 16.dp), + refreshState: PullRefreshState, + refreshing: State, bookTitle: State, chapters: State>, onChapter: (AdventureChapterUio.AdventureItem) -> Unit, @@ -91,38 +112,48 @@ private fun AdventureChapterContent( }, ) }, - content = { it -> - LazyColumn( + content = { + Box( modifier = Modifier.padding(paddingValues = it), - state = lazyListState, - contentPadding = paddingValues, - verticalArrangement = Arrangement.spacedBy(space = 8.dp), + contentAlignment = Alignment.TopCenter, ) { - itemsIndexed(items = chapters.value) { index, item -> - when (item) { - is AdventureChapterUio.AdventureCategory -> { - AdventureChapterCategory( - modifier = Modifier - .padding(top = if (index != 0) 32.dp else 0.dp) - .fillMaxWidth(), - category = item, - ) - } + LazyColumn( + modifier = Modifier.pullRefresh(state = refreshState), + state = lazyListState, + contentPadding = paddingValues, + verticalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + itemsIndexed(items = chapters.value) { index, item -> + when (item) { + is AdventureChapterUio.AdventureCategory -> { + AdventureChapterCategory( + modifier = Modifier + .padding(top = if (index != 0) 32.dp else 0.dp) + .fillMaxWidth(), + category = item, + ) + } - is AdventureChapterUio.AdventureItem -> { - AdventureChapterItem( - modifier = Modifier.fillMaxWidth(), - item = item, - onClick = { onChapter(item) }, - ) + is AdventureChapterUio.AdventureItem -> { + AdventureChapterItem( + modifier = Modifier.fillMaxWidth(), + item = item, + onClick = { onChapter(item) }, + ) + } } } } + Loader( + refreshState = refreshState, + refreshing = refreshing, + ) } } ) } +@OptIn(ExperimentalMaterialApi::class) @Composable @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -131,6 +162,11 @@ private fun AdventureChapterPreview() { Surface { AdventureChapterContent( modifier = Modifier.fillMaxSize(), + refreshState = rememberPullRefreshState( + refreshing = false, + onRefresh = { }, + ), + refreshing = remember { mutableStateOf(false) }, bookTitle = remember { mutableStateOf( "Les chroniques d'une orc" diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersViewModel.kt index 578eda4..4aec1b0 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/adventure/chapter/AdventureChaptersViewModel.kt @@ -1,41 +1,82 @@ package com.pixelized.rplexicon.ui.screens.adventure.chapter +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import com.pixelized.rplexicon.data.repository.adventure.AdventureRepository +import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio +import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio.Structure +import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio.Structure.Type.ADVENTURE import com.pixelized.rplexicon.ui.navigation.screens.adventureChaptersArgument import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext import javax.inject.Inject @HiltViewModel class AdventureChaptersViewModel @Inject constructor( - adventureRepository: AdventureRepository, + private val adventureRepository: AdventureRepository, savedStateHandle: SavedStateHandle, ) : ViewModel() { private val argument = savedStateHandle.adventureChaptersArgument - private val _chapters = mutableStateOf( - adventureRepository.find(bookTitle = argument.bookTitle) - ) + + private val _error = MutableSharedFlow() + + private val _isLoading = mutableStateOf(false) + val isLoading: State get() = _isLoading val bookTitle = derivedStateOf { argument.bookTitle } - val chapters = derivedStateOf { - _chapters.value - .groupBy { it.adventureCategory } - .flatMap { entry -> - val header = entry.key?.let { - listOf(AdventureChapterUio.AdventureCategory(title = it)) - } ?: emptyList() - val stories = entry.value.map { - AdventureChapterUio.AdventureItem( - bookTitle = it.bookTitle, - adventureTitle = it.adventureTitle, + + private val _chapters = adventureRepository.adventure + .map { adventures -> + adventures + .filter { it.bookTitle == argument.bookTitle && it.documentId == argument.documentId } + .groupBy { it.adventureCategory ?: "" } + .flatMap { entry -> + val header = listOf( + AdventureChapterUio.AdventureCategory(title = entry.key) ) + val stories = entry.value.map { + AdventureChapterUio.AdventureItem( + bookTitle = it.bookTitle, + adventureTitle = it.adventureTitle, + ) + } + header + stories } - header + stories + } + + val chapters: State> + @Composable + @Stable + get() = _chapters.collectAsState(initial = emptyList()) + + suspend fun update() { + try { + withContext(Dispatchers.Main) { + _isLoading.value = true } + withContext(Dispatchers.Default) { + adventureRepository.fetchAdventures( + bookTitle = argument.bookTitle, + documentId = argument.documentId, + ) + } + } catch (_: Exception) { + _error.emit(value = Structure(ADVENTURE)) + } finally { + withContext(Dispatchers.Main) { + _isLoading.value = false + } + } } } \ No newline at end of file