Add specific adventure book refresh feature
This commit is contained in:
parent
532f5810d7
commit
96269cf84a
13 changed files with 250 additions and 93 deletions
|
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ data class AdventureDbo(
|
|||
val adventureBackground: String?,
|
||||
val index: Int,
|
||||
val revision: Long,
|
||||
val documentId: String,
|
||||
)
|
||||
|
||||
@Entity(
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ data class Adventure(
|
|||
val adventureCategory: String?,
|
||||
val adventureTitle: String,
|
||||
val adventureBackground: Uri?,
|
||||
val documentId: String,
|
||||
val story: List<AdventureLine>,
|
||||
)
|
||||
|
|
@ -4,6 +4,6 @@ import android.net.Uri
|
|||
|
||||
data class AdventureBook(
|
||||
val title: String,
|
||||
val document: String,
|
||||
val documentId: String,
|
||||
val icon: Uri?,
|
||||
)
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<String, Boolean> = bookStore.associate {
|
||||
it.bookTitle to true
|
||||
}.toMutableMap()
|
||||
val storyToRemove = bookStore.associate {
|
||||
|
||||
val storyToRemove: MutableMap<Pair<String, String>, 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<Pair<String, String>, 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<Pair<String, String>, Boolean>,
|
||||
book: AdventureBook
|
||||
): List<Pair<AdventureDbo, List<AdventureStoryDbo>>> {
|
||||
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<Adventure> {
|
||||
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<AdventureBook> {
|
||||
private suspend fun fetchAdventureBooks(): List<AdventureBook> {
|
||||
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<AdventureStory> {
|
||||
private suspend fun fetchAdventureStory(documentId: String): List<AdventureStory> {
|
||||
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<AdventureLine> {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<List<AdventureBookUio>>,
|
||||
refreshState: PullRefreshState,
|
||||
refreshing: State<Boolean>,
|
||||
items: State<List<AdventureBookUio>>,
|
||||
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 = { },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class AdventureBooksViewModel @Inject constructor(
|
|||
AdventureBookUio(
|
||||
bookTitle = adventure.bookTitle,
|
||||
bookIcon = adventure.bookIcon,
|
||||
documentId = adventure.documentId,
|
||||
)
|
||||
}
|
||||
.toSet()
|
||||
|
|
|
|||
|
|
@ -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<Boolean>,
|
||||
bookTitle: State<String>,
|
||||
chapters: State<List<AdventureChapterUio>>,
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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<FetchErrorUio>()
|
||||
|
||||
private val _isLoading = mutableStateOf(false)
|
||||
val isLoading: State<Boolean> 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<List<AdventureChapterUio>>
|
||||
@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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue