Bump version to 0.7.1

This commit is contained in:
Thomas Andres Gomez 2023-11-13 10:32:51 +01:00
parent 2f113a14d6
commit e0593c9cbf
16 changed files with 237 additions and 49 deletions

View file

@ -15,6 +15,7 @@ import com.pixelized.rplexicon.data.repository.character.InventoryRepository
import com.pixelized.rplexicon.data.repository.character.ObjectActionRepository
import com.pixelized.rplexicon.data.repository.character.SkillRepository
import com.pixelized.rplexicon.data.repository.character.SpellRepository
import com.pixelized.rplexicon.data.repository.lexicon.CategoryOrderRepository
import com.pixelized.rplexicon.data.repository.lexicon.LexiconRepository
import com.pixelized.rplexicon.data.repository.lexicon.LocationRepository
import com.pixelized.rplexicon.data.repository.lexicon.QuestRepository
@ -30,6 +31,7 @@ import javax.inject.Inject
@HiltViewModel
class LauncherViewModel @Inject constructor(
categoryRepository: CategoryOrderRepository,
lexiconRepository: LexiconRepository,
locationRepository: LocationRepository,
questRepository: QuestRepository,
@ -52,6 +54,14 @@ class LauncherViewModel @Inject constructor(
init {
viewModelScope.launch {
val order = async {
try {
categoryRepository.fetchCategoryOrder()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.CATEGORY_ORDER))
}
}
val lexicon = async {
try {
lexiconRepository.fetchLexicon()
@ -151,7 +161,7 @@ class LauncherViewModel @Inject constructor(
}
}
awaitAll(lexicon, location, quest)
awaitAll(order, lexicon, location, quest)
awaitAll(description, inventory, equipment, alteration, action, objects, spell, skill)
isLoading = false

View file

@ -6,13 +6,17 @@ import androidx.compose.runtime.Stable
@Stable
data class Quest(
val id: String,
val group: String?,
val title: String,
val entries: List<QuestEntry>,
)
) {
val complete = entries.all { it.complete }
}
@Stable
data class QuestEntry(
val sheetIndex: Int,
val group: String?,
val title: String,
val subtitle: String?,
val complete: Boolean,

View file

@ -0,0 +1,30 @@
package com.pixelized.rplexicon.data.parser
import com.google.api.services.sheets.v4.model.ValueRange
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import javax.inject.Inject
class CategoryOrderParser @Inject constructor() {
@Throws(IncompatibleSheetStructure::class)
fun parse(sheet: ValueRange): Map<String, Map<String, Int>> = parserScope {
val categories = hashMapOf<String, HashMap<String, Int>>()
var currentCategory: String? = null
sheet.forEachLine { line ->
val category = line.getOrNull(0)?.toItem()
val amount = line.getOrNull(1)?.toItem()?.toIntOrNull()
if (category != null) {
if (amount == null) {
currentCategory = category
categories[category] = hashMapOf()
} else if (currentCategory != null) {
categories[currentCategory]?.put(category, amount)
}
}
}
return@parserScope categories
}
}

View file

@ -22,6 +22,7 @@ class QuestParser @Inject constructor(
val entry = QuestEntry(
sheetIndex = index,
title = quest,
group = item.parse(column = GROUP),
subtitle = item.parse(column = SUB_TITLE),
complete = item.parseBool(column = COMPLETED) ?: false,
questGiver = item.parse(column = QUEST_GIVER),
@ -39,10 +40,12 @@ class QuestParser @Inject constructor(
}
val quests = entries.keys.map { quest ->
val relatedEntries = entries[quest] ?: emptyList()
Quest(
id = "$quest-1", // TODO refactor that when quest have ids in the google sheet.
title = quest,
entries = entries[quest] ?: emptyList()
group = relatedEntries.firstNotNullOfOrNull { it.group },
entries = relatedEntries,
)
}
@ -51,6 +54,7 @@ class QuestParser @Inject constructor(
companion object {
private val TITLE = column("Titre")
private val GROUP = column("Groupe")
private val SUB_TITLE = column("Sous Titre")
private val COMPLETED = column("Compléter")
private val QUEST_GIVER = column("Commanditaire")
@ -64,6 +68,7 @@ class QuestParser @Inject constructor(
private val COLUMNS
get() = listOf(
TITLE,
GROUP,
SUB_TITLE,
COMPLETED,
QUEST_GIVER,

View file

@ -34,6 +34,12 @@ class SheetParserScope<T> {
sheet?.mapNotNull { it as? List<*> }?.forEachIndexed(block)
}
inline fun ValueRange.forEachLine(
crossinline block: (line: List<*>) -> Unit
) {
this.values.sheet()?.mapNotNull { it as? List<*> }?.forEach(block)
}
inline fun ValueRange.forEachDataLine(
columns: List<Column>,
crossinline block: (row: List<*>) -> Unit

View file

@ -7,6 +7,7 @@ object LexiconBinder {
const val QUEST_JOURNAL = "Journal de quêtes"
const val MAP = "Cartes"
const val WORLD = "Monde"
const val ORDER = "Ordre des catégories"
}
object CharacterBinder {

View file

@ -0,0 +1,52 @@
package com.pixelized.rplexicon.data.repository.lexicon
import com.pixelized.rplexicon.data.parser.CategoryOrderParser
import com.pixelized.rplexicon.data.repository.GoogleSheetServiceRepository
import com.pixelized.rplexicon.data.repository.LexiconBinder
import com.pixelized.rplexicon.utilitary.Update
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class CategoryOrderRepository @Inject constructor(
private val googleRepository: GoogleSheetServiceRepository,
private val parser: CategoryOrderParser,
) {
private val _data = MutableStateFlow<Map<String, Map<String, Int>>>(emptyMap())
val data: StateFlow<Map<String, Map<String, Int>>> get() = _data
var lastSuccessFullUpdate: Update = Update.INITIAL
private set
fun finLexiconOrder(quest: String?): Int {
return _data.value[LEXICON]?.get(quest) ?: Int.MAX_VALUE
}
fun findMapOrder(quest: String?): Int {
return _data.value[MAP]?.get(quest) ?: Int.MAX_VALUE
}
fun findQuestOrder(quest: String?): Int {
return _data.value[QUEST]?.get(quest) ?: Int.MAX_VALUE
}
@Throws(IncompatibleSheetStructure::class, Exception::class)
suspend fun fetchCategoryOrder() {
googleRepository.fetch { sheet ->
val request = sheet.get(LexiconBinder.ID, LexiconBinder.ORDER)
val data = parser.parse(sheet = request.execute())
_data.tryEmit(data)
lastSuccessFullUpdate = Update.currentTime()
}
}
companion object {
private const val QUEST = "Quêtes"
private const val LEXICON = "Lexique"
private const val MAP = "Cartes"
}
}

View file

@ -31,6 +31,7 @@ sealed class FetchErrorUio {
OBJECT,
SKILL,
SPELL,
CATEGORY_ORDER,
LEXICON,
LOCATION,
QUEST,
@ -57,6 +58,7 @@ fun HandleFetchError(
FetchErrorUio.Structure.Type.OBJECT -> R.string.error_structure_objects
FetchErrorUio.Structure.Type.SKILL -> R.string.error_structure_skill
FetchErrorUio.Structure.Type.SPELL -> R.string.error_structure_spell
FetchErrorUio.Structure.Type.CATEGORY_ORDER -> R.string.error_structure_category_order
FetchErrorUio.Structure.Type.LEXICON -> R.string.error_structure_lexicon
FetchErrorUio.Structure.Type.LOCATION -> R.string.error_structure_location
FetchErrorUio.Structure.Type.QUEST -> R.string.error_structure_quest

View file

@ -0,0 +1,26 @@
package com.pixelized.rplexicon.ui.screens.quest.list
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
@Stable
data class QuestCategoryUio(
val title: String,
)
@Composable
fun QuestCategory(
modifier: Modifier = Modifier,
item: QuestCategoryUio,
) {
Text(
modifier = modifier,
style = MaterialTheme.typography.labelSmall,
fontWeight = FontWeight.Light,
text = item.title,
)
}

View file

@ -0,0 +1,11 @@
package com.pixelized.rplexicon.ui.screens.quest.list
import androidx.compose.runtime.Stable
@Stable
data class QuestGroupUio(
val category: QuestCategoryUio,
val quests: List<QuestItemUio>,
) {
val complete = quests.all { it.complete }
}

View file

@ -56,7 +56,6 @@ fun QuestItem(
item: QuestItemUio,
) {
val typography = MaterialTheme.lexicon.typography
Box(
modifier = modifier,
contentAlignment = Alignment.CenterStart,
@ -64,19 +63,16 @@ fun QuestItem(
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
val alignModifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder }
Text(
modifier = when (item.placeholder) {
true -> Modifier.placeholder { true }
else -> Modifier.alignByBaseline()
},
modifier = alignModifier,
style = typography.base.titleMedium,
text = if (item.complete) LOS_FULL else LOS_HOLLOW,
)
Text(
modifier = when (item.placeholder) {
true -> Modifier.placeholder { true }
else -> Modifier.alignByBaseline()
},
modifier = alignModifier,
style = typography.base.titleMedium,
fontWeight = FontWeight.Bold,
maxLines = 1,

View file

@ -5,6 +5,7 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
@ -23,6 +24,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
@ -76,7 +78,7 @@ private fun QuestListContent(
lazyColumnState: LazyListState,
refreshState: PullRefreshState,
refreshing: State<Boolean>,
items: State<List<QuestItemUio>>,
items: State<List<QuestGroupUio>>,
onItem: (QuestItemUio) -> Unit,
) {
Box(
@ -111,17 +113,29 @@ private fun QuestListContent(
state = lazyColumnState,
contentPadding = MaterialTheme.lexicon.dimens.itemListPadding,
) {
items(
items = items.value,
key = { it.id },
contentType = { "Quest" },
) {
QuestItem(
modifier = Modifier
.clickable { onItem(it) }
.cell(),
item = it,
)
items.value.forEachIndexed { index, entry ->
item(
contentType = { "Header" },
) {
QuestCategory(
modifier = Modifier
.padding(top = if (index == 0) 0.dp else 16.dp)
.padding(horizontal = 16.dp),
item = entry.category,
)
}
items(
items = entry.quests,
key = { it.id },
contentType = { "Quest" },
) {
QuestItem(
modifier = Modifier
.clickable { onItem(it) }
.cell(),
item = it,
)
}
}
}
}
@ -152,13 +166,20 @@ private fun QuestListPreview() {
items = remember {
mutableStateOf(
listOf(
QuestItemUio.preview(
id = "La chasse aux loups",
title = "La chasse aux loups",
),
QuestItemUio.preview(
id = "Les enfants de la caravanes",
title = "Les enfants de la caravanes",
QuestGroupUio(
category = QuestCategoryUio(
title = "Faerûn",
),
quests = listOf(
QuestItemUio.preview(
id = "La chasse aux loups",
title = "La chasse aux loups",
),
QuestItemUio.preview(
id = "Les enfants de la caravanes",
title = "Les enfants de la caravanes",
),
),
),
)
)

View file

@ -1,17 +1,22 @@
package com.pixelized.rplexicon.ui.screens.quest.list
import android.app.Application
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.data.repository.lexicon.CategoryOrderRepository
import com.pixelized.rplexicon.data.repository.lexicon.QuestRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
import com.pixelized.rplexicon.utilitary.extentions.context
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -19,13 +24,15 @@ import javax.inject.Inject
@HiltViewModel
class QuestListViewModel @Inject constructor(
private val repository: QuestRepository,
) : ViewModel() {
private val order: CategoryOrderRepository,
application: Application,
) : AndroidViewModel(application = application) {
private val _isLoading = mutableStateOf(false)
val isLoading: State<Boolean> get() = _isLoading
private val _items = mutableStateOf<List<QuestItemUio>>(emptyList())
val items: State<List<QuestItemUio>> get() = _items
private val _items = mutableStateOf<List<QuestGroupUio>>(emptyList())
val items: State<List<QuestGroupUio>> get() = _items
private val _error = MutableSharedFlow<FetchErrorUio>()
val error: SharedFlow<FetchErrorUio> get() = _error
@ -33,22 +40,35 @@ class QuestListViewModel @Inject constructor(
init {
viewModelScope.launch {
launch(Dispatchers.IO) {
repository.data.collect { items ->
val quest = items
.map { item ->
QuestItemUio(
id = item.id,
title = item.title,
complete = item.entries.all { it.complete },
order.data.combine(repository.data) { _, quests -> quests }
.collect { items ->
val quests = items
.asSequence()
.sortedBy { it.title }
.sortedBy { it.complete }
.groupBy(
keySelector = {
QuestCategoryUio(
title = it.group ?: context.getString(R.string.default_category_other),
)
},
valueTransform = { item ->
QuestItemUio(
id = item.id,
title = item.title,
complete = item.complete,
)
},
)
}
.sortedBy { it.title }
.sortedBy { it.complete }
.map { QuestGroupUio(category = it.key, quests = it.value) }
.sortedBy { order.findQuestOrder(quest = it.category.title) }
.sortedBy { it.complete }
.toList()
withContext(Dispatchers.Main) {
_items.value = quest
withContext(Dispatchers.Main) {
_items.value = quests
}
}
}
}
launch(Dispatchers.IO) {
update(force = false)