Add a quest log feature
This commit is contained in:
parent
b3d3e3e314
commit
d55e782510
58 changed files with 1740 additions and 452 deletions
|
|
@ -9,6 +9,10 @@ import com.google.api.client.json.gson.GsonFactory
|
|||
import com.google.api.services.sheets.v4.Sheets
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -59,7 +63,7 @@ class LexiconRepository @Inject constructor(
|
|||
val lexicon: List<Lexicon> = sheet?.mapIndexedNotNull { index, row ->
|
||||
when {
|
||||
index == 0 -> {
|
||||
sheetStructure = checkSheetStructure(firstRow = row)
|
||||
sheetStructure = row.checkSheetStructure(model = Sheet.COLUMNS)
|
||||
null
|
||||
}
|
||||
|
||||
|
|
@ -79,34 +83,6 @@ class LexiconRepository @Inject constructor(
|
|||
_data.tryEmit(lexicon)
|
||||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
private fun checkSheetStructure(firstRow: Any?): HashMap<String, Int> {
|
||||
// check if the row is a list
|
||||
if (firstRow !is ArrayList<*>) {
|
||||
throw IncompatibleSheetStructure("First row is not a List: $firstRow")
|
||||
}
|
||||
// parse the first line to find element that we recognize.
|
||||
val sheetStructure = hashMapOf<String, Int>()
|
||||
|
||||
firstRow.forEachIndexed { index, cell ->
|
||||
if (cell is String && Sheet.COLUMNS.contains(cell)) {
|
||||
sheetStructure[cell] = index
|
||||
}
|
||||
}
|
||||
// check if we found everything we need.
|
||||
when {
|
||||
sheetStructure.size < Sheet.COLUMNS.size -> throw IncompatibleSheetStructure(
|
||||
message = "Sheet header row does not have enough column: ${firstRow.size}.\nstructure: $firstRow\nheader: $sheetStructure"
|
||||
)
|
||||
|
||||
sheetStructure.size > Sheet.COLUMNS.size -> throw IncompatibleSheetStructure(
|
||||
message = "Sheet header row does have too mush columns: ${firstRow.size}.\nstructure: $firstRow\nheader: $sheetStructure"
|
||||
)
|
||||
}
|
||||
|
||||
return sheetStructure
|
||||
}
|
||||
|
||||
private fun parseCharacterRow(
|
||||
sheetStructure: Map<String, Int>?,
|
||||
id: Int,
|
||||
|
|
@ -159,13 +135,6 @@ class LexiconRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun MutableCollection<Any>?.sheet(): List<*>? {
|
||||
return this?.firstOrNull {
|
||||
val sheet = it as? ArrayList<*>
|
||||
sheet != null
|
||||
} as List<*>?
|
||||
}
|
||||
|
||||
private fun String?.toUriOrNull(): Uri? = try {
|
||||
this?.takeIf { it.isNotBlank() }?.toUri()
|
||||
} catch (_: Exception) {
|
||||
|
|
@ -181,10 +150,6 @@ class LexiconRepository @Inject constructor(
|
|||
private val Map<String, Int>?.history: Int get() = this?.getValue(Sheet.HISTORY) ?: 6
|
||||
private val Map<String, Int>?.tags: Int get() = this?.getValue(Sheet.TAGS) ?: 7
|
||||
|
||||
class ServiceNotReady : Exception()
|
||||
|
||||
class IncompatibleSheetStructure(message: String?) : Exception(message)
|
||||
|
||||
companion object {
|
||||
const val TAG = "LexiconRepository"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
package com.pixelized.rplexicon.repository
|
||||
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import com.google.api.client.extensions.android.http.AndroidHttp
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import com.google.api.services.sheets.v4.Sheets
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.model.Quest
|
||||
import com.pixelized.rplexicon.model.QuestEntry
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class QuestRepository @Inject constructor(
|
||||
private val authenticationRepository: AuthenticationRepository,
|
||||
) {
|
||||
private val sheetService: Sheets? by derivedStateOf {
|
||||
when (authenticationRepository.isAuthenticated.value) {
|
||||
true -> Sheets
|
||||
.Builder(
|
||||
AndroidHttp.newCompatibleTransport(),
|
||||
GsonFactory(),
|
||||
authenticationRepository.credential,
|
||||
)
|
||||
.build()
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val _data = MutableStateFlow<List<Quest>>(emptyList())
|
||||
val data: StateFlow<List<Quest>> get() = _data
|
||||
|
||||
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchQuests() {
|
||||
val service = sheetService
|
||||
if (service == null) {
|
||||
throw ServiceNotReady()
|
||||
} else {
|
||||
withContext(Dispatchers.IO) {
|
||||
val request = service.spreadsheets().values().get(Sheet.ID, Sheet.QUEST_JOURNAL)
|
||||
val data = request.execute()
|
||||
updateData(data = data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
private fun updateData(data: ValueRange?) {
|
||||
val sheet = data?.values?.sheet()
|
||||
var sheetStructure: Map<String, Int>? = null
|
||||
|
||||
val questEntries: List<QuestEntry> = sheet?.mapIndexedNotNull { index, row ->
|
||||
when {
|
||||
index == 0 -> {
|
||||
sheetStructure = row.checkSheetStructure(model = Sheet.COLUMNS)
|
||||
null
|
||||
}
|
||||
|
||||
row is List<*> -> parseQuestRow(
|
||||
sheetStructure = sheetStructure,
|
||||
sheetIndex = index,
|
||||
row = row,
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
val questMap = questEntries.groupBy { it.title }
|
||||
|
||||
val quests = questMap.keys.mapIndexed { index, item ->
|
||||
Quest(
|
||||
id = index,
|
||||
title = item,
|
||||
entries = questMap[item] ?: emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
_data.tryEmit(quests)
|
||||
}
|
||||
|
||||
private fun parseQuestRow(
|
||||
sheetStructure: Map<String, Int>?,
|
||||
sheetIndex: Int,
|
||||
row: List<*>?,
|
||||
): QuestEntry? {
|
||||
val title = row?.getOrNull(sheetStructure.title) as? String
|
||||
val subtitle = row?.getOrNull(sheetStructure.subtitle) as? String?
|
||||
val complete = row?.getOrNull(sheetStructure.complete) as? Boolean? ?: false
|
||||
val questGiver = row?.getOrNull(sheetStructure.questGiver) as? String?
|
||||
val area = row?.getOrNull(sheetStructure.area) as? String?
|
||||
val groupReward = row?.getOrNull(sheetStructure.groupReward) as? String?
|
||||
val individualReward = row?.getOrNull(sheetStructure.individualReward) as? String?
|
||||
val description = row?.getOrNull(sheetStructure.description) as? String
|
||||
|
||||
return if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) {
|
||||
QuestEntry(
|
||||
sheetIndex = sheetIndex,
|
||||
title = title,
|
||||
subtitle = subtitle?.takeIf { it.isNotBlank() },
|
||||
complete = complete,
|
||||
questGiver = questGiver?.takeIf { it.isNotBlank() },
|
||||
area = area?.takeIf { it.isNotBlank() },
|
||||
groupReward = groupReward?.takeIf { it.isNotBlank() },
|
||||
individualReward = individualReward?.takeIf { it.isNotBlank() },
|
||||
description = description,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private val Map<String, Int>?.title: Int get() = this?.getValue(Sheet.TITLE) ?: 0
|
||||
private val Map<String, Int>?.subtitle: Int get() = this?.getValue(Sheet.SUBTITLE) ?: 1
|
||||
private val Map<String, Int>?.complete: Int get() = this?.getValue(Sheet.COMPLETE) ?: 2
|
||||
private val Map<String, Int>?.questGiver: Int get() = this?.getValue(Sheet.QUEST_GIVER) ?: 3
|
||||
private val Map<String, Int>?.area: Int get() = this?.getValue(Sheet.AREA) ?: 4
|
||||
private val Map<String, Int>?.groupReward: Int get() = this?.getValue(Sheet.G_REWARD) ?: 5
|
||||
private val Map<String, Int>?.individualReward: Int get() = this?.getValue(Sheet.I_REWARD) ?: 6
|
||||
private val Map<String, Int>?.description: Int get() = this?.getValue(Sheet.DESCRIPTION) ?: 7
|
||||
|
||||
private object Sheet {
|
||||
const val ID = "1sDAay8DjbRYKM39MvEXWs-RuvyxjOFpOfRZLAEWjIUY"
|
||||
|
||||
const val QUEST_JOURNAL = "Journal de quêtes"
|
||||
|
||||
val COLUMNS = listOf(
|
||||
"Titre",
|
||||
"Sous Titre",
|
||||
"Compléter",
|
||||
"Commanditaire",
|
||||
"Lieu",
|
||||
"Récompense de groupe",
|
||||
"Récompense individuelle",
|
||||
"Description",
|
||||
)
|
||||
val TITLE = COLUMNS[0]
|
||||
val SUBTITLE = COLUMNS[1]
|
||||
val COMPLETE = COLUMNS[2]
|
||||
val QUEST_GIVER = COLUMNS[3]
|
||||
val AREA = COLUMNS[4]
|
||||
val G_REWARD = COLUMNS[5]
|
||||
val I_REWARD = COLUMNS[6]
|
||||
val DESCRIPTION = COLUMNS[7]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue