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,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"identityHash": "ad9094e2a7611443722a5415154015bf",
|
"identityHash": "eecd0da0c8ae6578a5d36c3b926c2fe8",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "lexicon",
|
"tableName": "lexicon",
|
||||||
|
|
@ -321,7 +321,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "adventures",
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "bookTitle",
|
"fieldPath": "bookTitle",
|
||||||
|
|
@ -364,6 +364,12 @@
|
||||||
"columnName": "revision",
|
"columnName": "revision",
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "documentId",
|
||||||
|
"columnName": "documentId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"primaryKey": {
|
"primaryKey": {
|
||||||
|
|
@ -440,7 +446,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '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 adventureBackground: String?,
|
||||||
val index: Int,
|
val index: Int,
|
||||||
val revision: Long,
|
val revision: Long,
|
||||||
|
val documentId: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,6 @@ data class Adventure(
|
||||||
val adventureCategory: String?,
|
val adventureCategory: String?,
|
||||||
val adventureTitle: String,
|
val adventureTitle: String,
|
||||||
val adventureBackground: Uri?,
|
val adventureBackground: Uri?,
|
||||||
|
val documentId: String,
|
||||||
val story: List<AdventureLine>,
|
val story: List<AdventureLine>,
|
||||||
)
|
)
|
||||||
|
|
@ -4,6 +4,6 @@ import android.net.Uri
|
||||||
|
|
||||||
data class AdventureBook(
|
data class AdventureBook(
|
||||||
val title: String,
|
val title: String,
|
||||||
val document: String,
|
val documentId: String,
|
||||||
val icon: Uri?,
|
val icon: Uri?,
|
||||||
)
|
)
|
||||||
|
|
@ -23,7 +23,7 @@ class AdventureBookParser @Inject constructor() {
|
||||||
if (character != null && document != null) {
|
if (character != null && document != null) {
|
||||||
val adventure = AdventureBook(
|
val adventure = AdventureBook(
|
||||||
title = character,
|
title = character,
|
||||||
document = document,
|
documentId = document,
|
||||||
icon = row.parseUri(ICON),
|
icon = row.parseUri(ICON),
|
||||||
)
|
)
|
||||||
adventures.add(adventure)
|
adventures.add(adventure)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class AdventureDboFactory @Inject constructor() {
|
||||||
adventureTitle = adventure.adventureTitle,
|
adventureTitle = adventure.adventureTitle,
|
||||||
adventureCategory = adventure.adventureCategory,
|
adventureCategory = adventure.adventureCategory,
|
||||||
adventureBackground = adventure.adventureBackground.toUriOrNull(),
|
adventureBackground = adventure.adventureBackground.toUriOrNull(),
|
||||||
|
documentId = adventure.documentId,
|
||||||
story = stories
|
story = stories
|
||||||
.filter {
|
.filter {
|
||||||
it.bookTitle == adventure.bookTitle && it.adventureTitle == adventure.adventureTitle
|
it.bookTitle == adventure.bookTitle && it.adventureTitle == adventure.adventureTitle
|
||||||
|
|
@ -57,6 +58,7 @@ class AdventureDboFactory @Inject constructor() {
|
||||||
adventureBackground = story.background.toString(),
|
adventureBackground = story.background.toString(),
|
||||||
index = index,
|
index = index,
|
||||||
revision = story.revision,
|
revision = story.revision,
|
||||||
|
documentId = book.documentId,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun convertToStoryDbo(
|
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.CompanionDatabase
|
||||||
import com.pixelized.rplexicon.data.database.adventure.AdventureDao
|
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.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
|
||||||
|
|
@ -45,47 +47,23 @@ class AdventureRepository @Inject constructor(
|
||||||
val database = database.adventureDao()
|
val database = database.adventureDao()
|
||||||
val bookStore = database.findAdventures()
|
val bookStore = database.findAdventures()
|
||||||
|
|
||||||
val bookToRemove = bookStore.associate {
|
val bookToRemove: MutableMap<String, Boolean> = bookStore.associate {
|
||||||
it.bookTitle to true
|
it.bookTitle to true
|
||||||
}.toMutableMap()
|
}.toMutableMap()
|
||||||
val storyToRemove = bookStore.associate {
|
|
||||||
|
val storyToRemove: MutableMap<Pair<String, String>, Boolean> = bookStore.associate {
|
||||||
(it.bookTitle to it.adventureTitle) to true
|
(it.bookTitle to it.adventureTitle) to true
|
||||||
}.toMutableMap()
|
}.toMutableMap()
|
||||||
|
|
||||||
val adventures = fetchAdventureBook().flatMap { book ->
|
val adventures = fetchAdventureBooks().flatMap { book ->
|
||||||
// tag this book to keep it in the database
|
// tag this book to keep it in the database
|
||||||
bookToRemove[book.title] = false
|
bookToRemove[book.title] = false
|
||||||
|
|
||||||
val stories = fetchAdventureStory(book = book)
|
fetchAdventuresContent(
|
||||||
stories.mapNotNull { story ->
|
database = database,
|
||||||
// tag this story to keep it in the database
|
storyToRemove = storyToRemove,
|
||||||
storyToRemove[book.title to story.title] = false
|
book = book,
|
||||||
|
)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
database.update(
|
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> {
|
fun find(bookTitle: String): List<Adventure> {
|
||||||
return adventure.value.filter { adventure ->
|
return adventure.value.filter { adventure ->
|
||||||
adventure.bookTitle == bookTitle
|
adventure.bookTitle == bookTitle
|
||||||
|
|
@ -112,7 +162,7 @@ class AdventureRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||||
private suspend fun fetchAdventureBook(): List<AdventureBook> {
|
private suspend fun fetchAdventureBooks(): List<AdventureBook> {
|
||||||
return googleRepository.fetch { sheet ->
|
return googleRepository.fetch { sheet ->
|
||||||
val request = sheet.get(Adventures.ID, Adventures.ADVENTURES)
|
val request = sheet.get(Adventures.ID, Adventures.ADVENTURES)
|
||||||
adventureBookParser.parse(sheet = request.execute())
|
adventureBookParser.parse(sheet = request.execute())
|
||||||
|
|
@ -120,20 +170,20 @@ class AdventureRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
@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 ->
|
return googleRepository.fetch { sheet ->
|
||||||
val request = sheet.get(book.document, Adventures.ADVENTURES)
|
val request = sheet.get(documentId, Adventures.ADVENTURES)
|
||||||
adventureStoryParser.parse(sheet = request.execute())
|
adventureStoryParser.parse(sheet = request.execute())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAdventureLine(
|
private suspend fun fetchAdventureLine(
|
||||||
book: AdventureBook,
|
documentId: String,
|
||||||
story: AdventureStory
|
adventureTitle: String,
|
||||||
): List<AdventureLine> {
|
): List<AdventureLine> {
|
||||||
return try {
|
return try {
|
||||||
googleRepository.fetch { sheet ->
|
googleRepository.fetch { sheet ->
|
||||||
val request = sheet.get(book.document, story.title)
|
val request = sheet.get(documentId, adventureTitle)
|
||||||
adventureStoryLineParser.parse(sheet = request.execute())
|
adventureStoryLineParser.parse(sheet = request.execute())
|
||||||
}
|
}
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,23 @@ import com.pixelized.rplexicon.utilitary.extentions.string.ARG
|
||||||
|
|
||||||
private const val ROUTE = "adventureDetail"
|
private const val ROUTE = "adventureDetail"
|
||||||
private const val BOOK_TITLE_ARG = "bookTitle"
|
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
|
@Stable
|
||||||
@Immutable
|
@Immutable
|
||||||
class AdventureChapterArgument(
|
class AdventureChapterArgument(
|
||||||
val bookTitle: String,
|
val bookTitle: String,
|
||||||
|
val documentId: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
val SavedStateHandle.adventureChaptersArgument: AdventureChapterArgument
|
val SavedStateHandle.adventureChaptersArgument: AdventureChapterArgument
|
||||||
get() = AdventureChapterArgument(
|
get() = AdventureChapterArgument(
|
||||||
bookTitle = get(BOOK_TITLE_ARG)
|
bookTitle = get(BOOK_TITLE_ARG)
|
||||||
?: error("AdventureDetailArgument missing argument: $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() {
|
fun NavGraphBuilder.composableAdventureChapters() {
|
||||||
|
|
@ -39,6 +43,10 @@ fun NavGraphBuilder.composableAdventureChapters() {
|
||||||
type = NavType.StringType
|
type = NavType.StringType
|
||||||
nullable = false
|
nullable = false
|
||||||
},
|
},
|
||||||
|
navArgument(name = DOCUMENT_ID_ARG) {
|
||||||
|
type = NavType.StringType
|
||||||
|
nullable = false
|
||||||
|
},
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
AdventureChaptersScreen()
|
AdventureChaptersScreen()
|
||||||
|
|
@ -47,8 +55,9 @@ fun NavGraphBuilder.composableAdventureChapters() {
|
||||||
|
|
||||||
fun NavHostController.navigateToAdventureChapters(
|
fun NavHostController.navigateToAdventureChapters(
|
||||||
bookTitle: String,
|
bookTitle: String,
|
||||||
|
documentId: String,
|
||||||
option: NavOptionsBuilder.() -> Unit = {},
|
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)
|
navigate(route = route, builder = option)
|
||||||
}
|
}
|
||||||
|
|
@ -31,6 +31,7 @@ import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
data class AdventureBookUio(
|
data class AdventureBookUio(
|
||||||
val bookTitle: String,
|
val bookTitle: String,
|
||||||
val bookIcon: Uri?,
|
val bookIcon: Uri?,
|
||||||
|
val documentId: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -80,6 +81,7 @@ private fun AdventureItemPreview() {
|
||||||
item = AdventureBookUio(
|
item = AdventureBookUio(
|
||||||
bookIcon = null,
|
bookIcon = null,
|
||||||
bookTitle = "Les chroniques d'une orc",
|
bookTitle = "Les chroniques d'une orc",
|
||||||
|
documentId = "",
|
||||||
),
|
),
|
||||||
onClick = {},
|
onClick = {},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,16 @@ fun AdventureBooksScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.systemBarsPadding(),
|
.systemBarsPadding(),
|
||||||
items = viewModel.books,
|
|
||||||
refreshState = refresh,
|
refreshState = refresh,
|
||||||
refreshing = viewModel.isLoading,
|
refreshing = viewModel.isLoading,
|
||||||
|
items = viewModel.books,
|
||||||
onBack = { screen.popBackStack() },
|
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,
|
modifier: Modifier = Modifier,
|
||||||
gridState: LazyGridState = rememberLazyGridState(),
|
gridState: LazyGridState = rememberLazyGridState(),
|
||||||
paddingValues: PaddingValues = PaddingValues(all = 16.dp),
|
paddingValues: PaddingValues = PaddingValues(all = 16.dp),
|
||||||
items: State<List<AdventureBookUio>>,
|
|
||||||
refreshState: PullRefreshState,
|
refreshState: PullRefreshState,
|
||||||
refreshing: State<Boolean>,
|
refreshing: State<Boolean>,
|
||||||
|
items: State<List<AdventureBookUio>>,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onBook: (AdventureBookUio) -> Unit,
|
onBook: (AdventureBookUio) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -113,7 +118,9 @@ private fun AdventureListContent(
|
||||||
contentAlignment = Alignment.TopCenter,
|
contentAlignment = Alignment.TopCenter,
|
||||||
) {
|
) {
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
modifier = Modifier.fillMaxSize().pullRefresh(state = refreshState),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(state = refreshState),
|
||||||
state = gridState,
|
state = gridState,
|
||||||
columns = GridCells.Fixed(3),
|
columns = GridCells.Fixed(3),
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
|
|
@ -152,21 +159,22 @@ private fun AdventureListPreview(
|
||||||
) {
|
) {
|
||||||
AdventureListContent(
|
AdventureListContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
refreshState = rememberPullRefreshState(
|
||||||
|
refreshing = false,
|
||||||
|
onRefresh = { },
|
||||||
|
),
|
||||||
|
refreshing = remember { mutableStateOf(false) },
|
||||||
items = remember {
|
items = remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
listOf(
|
listOf(
|
||||||
AdventureBookUio(
|
AdventureBookUio(
|
||||||
bookTitle = "Les chroniques d'une orc",
|
bookTitle = "Les chroniques d'une orc",
|
||||||
bookIcon = null,
|
bookIcon = null,
|
||||||
|
documentId = "",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
refreshState = rememberPullRefreshState(
|
|
||||||
refreshing = false,
|
|
||||||
onRefresh = { },
|
|
||||||
),
|
|
||||||
refreshing = remember { mutableStateOf(false) },
|
|
||||||
onBack = { },
|
onBack = { },
|
||||||
onBook = { },
|
onBook = { },
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class AdventureBooksViewModel @Inject constructor(
|
||||||
AdventureBookUio(
|
AdventureBookUio(
|
||||||
bookTitle = adventure.bookTitle,
|
bookTitle = adventure.bookTitle,
|
||||||
bookIcon = adventure.bookIcon,
|
bookIcon = adventure.bookIcon,
|
||||||
|
documentId = adventure.documentId,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.pixelized.rplexicon.ui.screens.adventure.chapter
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
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.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
|
@ -22,6 +27,8 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.res.painterResource
|
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.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ui.composable.Loader
|
||||||
import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow
|
import com.pixelized.rplexicon.ui.composable.rememberAnimatedShadow
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToAdventureDetail
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToAdventureDetail
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AdventureChaptersScreen(
|
fun AdventureChaptersScreen(
|
||||||
viewModel: AdventureChaptersViewModel = hiltViewModel(),
|
viewModel: AdventureChaptersViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val screen = LocalScreenNavHost.current
|
val screen = LocalScreenNavHost.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val refresh = rememberPullRefreshState(
|
||||||
|
refreshing = false,
|
||||||
|
onRefresh = {
|
||||||
|
scope.launch { viewModel.update() }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
|
@ -48,6 +65,8 @@ fun AdventureChaptersScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.systemBarsPadding(),
|
.systemBarsPadding(),
|
||||||
|
refreshState = refresh,
|
||||||
|
refreshing = viewModel.isLoading,
|
||||||
bookTitle = viewModel.bookTitle,
|
bookTitle = viewModel.bookTitle,
|
||||||
chapters = viewModel.chapters,
|
chapters = viewModel.chapters,
|
||||||
onChapter = {
|
onChapter = {
|
||||||
|
|
@ -61,12 +80,14 @@ fun AdventureChaptersScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun AdventureChapterContent(
|
private fun AdventureChapterContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
lazyListState: LazyListState = rememberLazyListState(),
|
lazyListState: LazyListState = rememberLazyListState(),
|
||||||
paddingValues: PaddingValues = PaddingValues(vertical = 16.dp),
|
paddingValues: PaddingValues = PaddingValues(vertical = 16.dp),
|
||||||
|
refreshState: PullRefreshState,
|
||||||
|
refreshing: State<Boolean>,
|
||||||
bookTitle: State<String>,
|
bookTitle: State<String>,
|
||||||
chapters: State<List<AdventureChapterUio>>,
|
chapters: State<List<AdventureChapterUio>>,
|
||||||
onChapter: (AdventureChapterUio.AdventureItem) -> Unit,
|
onChapter: (AdventureChapterUio.AdventureItem) -> Unit,
|
||||||
|
|
@ -91,38 +112,48 @@ private fun AdventureChapterContent(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = { it ->
|
content = {
|
||||||
LazyColumn(
|
Box(
|
||||||
modifier = Modifier.padding(paddingValues = it),
|
modifier = Modifier.padding(paddingValues = it),
|
||||||
state = lazyListState,
|
contentAlignment = Alignment.TopCenter,
|
||||||
contentPadding = paddingValues,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
|
||||||
) {
|
) {
|
||||||
itemsIndexed(items = chapters.value) { index, item ->
|
LazyColumn(
|
||||||
when (item) {
|
modifier = Modifier.pullRefresh(state = refreshState),
|
||||||
is AdventureChapterUio.AdventureCategory -> {
|
state = lazyListState,
|
||||||
AdventureChapterCategory(
|
contentPadding = paddingValues,
|
||||||
modifier = Modifier
|
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||||
.padding(top = if (index != 0) 32.dp else 0.dp)
|
) {
|
||||||
.fillMaxWidth(),
|
itemsIndexed(items = chapters.value) { index, item ->
|
||||||
category = 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 -> {
|
is AdventureChapterUio.AdventureItem -> {
|
||||||
AdventureChapterItem(
|
AdventureChapterItem(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
item = item,
|
item = item,
|
||||||
onClick = { onChapter(item) },
|
onClick = { onChapter(item) },
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loader(
|
||||||
|
refreshState = refreshState,
|
||||||
|
refreshing = refreshing,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@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)
|
||||||
|
|
@ -131,6 +162,11 @@ private fun AdventureChapterPreview() {
|
||||||
Surface {
|
Surface {
|
||||||
AdventureChapterContent(
|
AdventureChapterContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
refreshState = rememberPullRefreshState(
|
||||||
|
refreshing = false,
|
||||||
|
onRefresh = { },
|
||||||
|
),
|
||||||
|
refreshing = remember { mutableStateOf(false) },
|
||||||
bookTitle = remember {
|
bookTitle = remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
"Les chroniques d'une orc"
|
"Les chroniques d'une orc"
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,82 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.adventure.chapter
|
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.derivedStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.pixelized.rplexicon.data.repository.adventure.AdventureRepository
|
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 com.pixelized.rplexicon.ui.navigation.screens.adventureChaptersArgument
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AdventureChaptersViewModel @Inject constructor(
|
class AdventureChaptersViewModel @Inject constructor(
|
||||||
adventureRepository: AdventureRepository,
|
private val adventureRepository: AdventureRepository,
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val argument = savedStateHandle.adventureChaptersArgument
|
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 {
|
val bookTitle = derivedStateOf {
|
||||||
argument.bookTitle
|
argument.bookTitle
|
||||||
}
|
}
|
||||||
val chapters = derivedStateOf {
|
|
||||||
_chapters.value
|
private val _chapters = adventureRepository.adventure
|
||||||
.groupBy { it.adventureCategory }
|
.map { adventures ->
|
||||||
.flatMap { entry ->
|
adventures
|
||||||
val header = entry.key?.let {
|
.filter { it.bookTitle == argument.bookTitle && it.documentId == argument.documentId }
|
||||||
listOf(AdventureChapterUio.AdventureCategory(title = it))
|
.groupBy { it.adventureCategory ?: "" }
|
||||||
} ?: emptyList()
|
.flatMap { entry ->
|
||||||
val stories = entry.value.map {
|
val header = listOf(
|
||||||
AdventureChapterUio.AdventureItem(
|
AdventureChapterUio.AdventureCategory(title = entry.key)
|
||||||
bookTitle = it.bookTitle,
|
|
||||||
adventureTitle = it.adventureTitle,
|
|
||||||
)
|
)
|
||||||
|
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