Rework the data update throughout the app.
This commit is contained in:
parent
cbe945d7f5
commit
b36cb9b601
19 changed files with 323 additions and 121 deletions
|
|
@ -0,0 +1,99 @@
|
|||
package com.pixelized.rplexicon
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.repository.data.ActionRepository
|
||||
import com.pixelized.rplexicon.repository.data.AlterationRepository
|
||||
import com.pixelized.rplexicon.repository.data.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.repository.data.LexiconRepository
|
||||
import com.pixelized.rplexicon.repository.data.LocationRepository
|
||||
import com.pixelized.rplexicon.repository.data.QuestRepository
|
||||
import com.pixelized.rplexicon.repository.data.SpellRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class LauncherViewModel @Inject constructor(
|
||||
lexiconRepository: LexiconRepository,
|
||||
locationRepository: LocationRepository,
|
||||
questRepository: QuestRepository,
|
||||
alterationRepository: AlterationRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
actionRepository: ActionRepository,
|
||||
spellRepository: SpellRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _error = MutableSharedFlow<String>()
|
||||
val error: SharedFlow<String> get() = _error
|
||||
|
||||
var isLoading by mutableStateOf(true)
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
val lexicon = async {
|
||||
try {
|
||||
lexiconRepository.fetchLexicon()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("Lexicon fail to update")
|
||||
}
|
||||
}
|
||||
val location = async {
|
||||
try {
|
||||
locationRepository.fetchLocation()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("Location fail to update")
|
||||
}
|
||||
}
|
||||
val quest = async {
|
||||
try {
|
||||
questRepository.fetchQuests()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("Quest fail to update")
|
||||
}
|
||||
}
|
||||
val alteration = async {
|
||||
try {
|
||||
alterationRepository.fetchAlterationSheet()
|
||||
alterationRepository.fetchStatusSheet()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("Alteration fail to update")
|
||||
}
|
||||
}
|
||||
val characterSheet = async {
|
||||
try {
|
||||
characterSheetRepository.fetchCharacterSheet()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("CharacterSheet fail to update")
|
||||
}
|
||||
}
|
||||
awaitAll(lexicon, location, quest, alteration, characterSheet)
|
||||
|
||||
val action = async {
|
||||
try {
|
||||
actionRepository.fetchActions()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("Action fail to update")
|
||||
}
|
||||
}
|
||||
val spell = async {
|
||||
try {
|
||||
spellRepository.fetchSpells()
|
||||
} catch (exception: Exception) {
|
||||
_error.tryEmit("Spell fail to update")
|
||||
}
|
||||
}
|
||||
awaitAll(action, spell)
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
|||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
|
|
@ -15,6 +16,7 @@ import androidx.compose.material3.SnackbarHost
|
|||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
|
|
@ -22,6 +24,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.rplexicon.ui.composable.BlurredOverlayHost
|
||||
import com.pixelized.rplexicon.ui.navigation.ScreenNavHost
|
||||
import com.pixelized.rplexicon.ui.screens.rolls.BlurredRollOverlayHostState
|
||||
|
|
@ -45,6 +48,9 @@ val NO_WINDOW_INSETS = WindowInsets(0, 0, 0, 0)
|
|||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private val launcherViewModel: LauncherViewModel by viewModels()
|
||||
private val rollViewModel: RollOverlayViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
|
|
@ -53,19 +59,19 @@ class MainActivity : ComponentActivity() {
|
|||
|
||||
// splashscreen management
|
||||
installSplashScreen().apply {
|
||||
setKeepOnScreenCondition { false }
|
||||
setKeepOnScreenCondition { launcherViewModel.isLoading }
|
||||
}
|
||||
|
||||
setContent {
|
||||
LexiconTheme {
|
||||
val rollViewModel = hiltViewModel<RollOverlayViewModel>()
|
||||
val snack = remember { SnackbarHostState() }
|
||||
val overlay = rememberBlurredRollOverlayHostState(
|
||||
viewModel = rollViewModel,
|
||||
)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalActivity provides this,
|
||||
LocalSnack provides remember { SnackbarHostState() },
|
||||
LocalSnack provides snack,
|
||||
LocalRollOverlay provides overlay,
|
||||
) {
|
||||
Scaffold(
|
||||
|
|
@ -101,6 +107,12 @@ class MainActivity : ComponentActivity() {
|
|||
overlay.hideOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = "LauncherError") {
|
||||
launcherViewModel.error.collect {
|
||||
snack.showSnackbar(message = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.pixelized.rplexicon.repository.data
|
|||
import com.pixelized.rplexicon.repository.parser.AttackParser
|
||||
import com.pixelized.rplexicon.model.Attack
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -18,6 +19,9 @@ class ActionRepository @Inject constructor(
|
|||
private val _data = MutableStateFlow<Map<String, List<Attack>>>(emptyMap())
|
||||
val data: StateFlow<Map<String, List<Attack>>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun find(name: String?): List<Attack>? {
|
||||
return name?.let { _data.value[it] }
|
||||
}
|
||||
|
|
@ -31,6 +35,7 @@ class ActionRepository @Inject constructor(
|
|||
charactersSheets = characterSheetRepository.data.value
|
||||
)
|
||||
_data.emit(data)
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import com.pixelized.rplexicon.model.Alteration
|
|||
import com.pixelized.rplexicon.model.Counter
|
||||
import com.pixelized.rplexicon.model.Property
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -29,6 +30,12 @@ class AlterationRepository @Inject constructor(
|
|||
private val _counter = MutableStateFlow<Map<String, List<Counter>>>(emptyMap())
|
||||
val counter: StateFlow<Map<String, List<Counter>>> get() = _counter
|
||||
|
||||
var lastAlterationSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
var lastStatusSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun getAlterations(character: String): List<Alteration> {
|
||||
return assignedAlterations.value[character]?.mapNotNull { alterationName ->
|
||||
alterationsLexicon.value.firstOrNull { it.name == alterationName }
|
||||
|
|
@ -49,6 +56,7 @@ class AlterationRepository @Inject constructor(
|
|||
val request = sheet.get(Sheet.Character.ID, Sheet.Character.ALTERATION)
|
||||
val data = alterationParser.parse(value = request.execute())
|
||||
_alterationsLexicon.emit(data)
|
||||
lastAlterationSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +68,7 @@ class AlterationRepository @Inject constructor(
|
|||
_assignedAlterations.emit(status)
|
||||
val counter = counterParser.parse(values = request.execute())
|
||||
_counter.emit(counter)
|
||||
lastStatusSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.rplexicon.repository.data
|
|||
import com.pixelized.rplexicon.repository.parser.CharacterSheetParser
|
||||
import com.pixelized.rplexicon.model.CharacterSheet
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -17,6 +18,13 @@ class CharacterSheetRepository @Inject constructor(
|
|||
private val _data = MutableStateFlow<Map<String, CharacterSheet>>(emptyMap())
|
||||
val data: StateFlow<Map<String, CharacterSheet>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun haveSheet(name: String?): Boolean {
|
||||
return _data.value.containsKey(name)
|
||||
}
|
||||
|
||||
fun find(name: String?): CharacterSheet? {
|
||||
return name?.let { _data.value[name] }
|
||||
}
|
||||
|
|
@ -27,6 +35,7 @@ class CharacterSheetRepository @Inject constructor(
|
|||
val request = sheet.get(Sheet.Character.ID, Sheet.Character.CHARACTER)
|
||||
val data = characterSheetParser.parse(value = request.execute())
|
||||
_data.emit(data)
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.pixelized.rplexicon.repository.data
|
|||
import com.pixelized.rplexicon.repository.parser.LexiconParser
|
||||
import com.pixelized.rplexicon.model.Lexicon
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -17,6 +18,9 @@ class LexiconRepository @Inject constructor(
|
|||
private val _data = MutableStateFlow<List<Lexicon>>(emptyList())
|
||||
val data: StateFlow<List<Lexicon>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun findId(name: String?): Int? {
|
||||
return name?.let { _data.value.firstOrNull { item -> item.name == it }?.id }
|
||||
}
|
||||
|
|
@ -27,6 +31,8 @@ class LexiconRepository @Inject constructor(
|
|||
val request = sheet.get(Sheet.Lexicon.ID, Sheet.Lexicon.LEXICON)
|
||||
val data = lexiconParser.parse(data = request.execute())
|
||||
_data.tryEmit(data)
|
||||
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.pixelized.rplexicon.repository.parser.LocationParser
|
|||
import com.pixelized.rplexicon.repository.parser.MarqueeParser
|
||||
import com.pixelized.rplexicon.model.Location
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -22,6 +23,9 @@ class LocationRepository @Inject constructor(
|
|||
private val _data = MutableStateFlow<List<Location>>(emptyList())
|
||||
val data: StateFlow<List<Location>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun findId(name: String?): Int? {
|
||||
return name?.let { _data.value.firstOrNull { item -> item.name == it }?.id }
|
||||
}
|
||||
|
|
@ -34,6 +38,7 @@ class LocationRepository @Inject constructor(
|
|||
async { sheet.get(Sheet.Lexicon.ID, Sheet.Lexicon.MARQUEE).execute() },
|
||||
)
|
||||
updateData(map = map, marquee = marquee)
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.google.api.services.sheets.v4.model.ValueRange
|
|||
import com.pixelized.rplexicon.repository.parser.QuestParser
|
||||
import com.pixelized.rplexicon.model.Quest
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
@ -18,12 +19,16 @@ class QuestRepository @Inject constructor(
|
|||
private val _data = MutableStateFlow<List<Quest>>(emptyList())
|
||||
val data: StateFlow<List<Quest>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchQuests() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(Sheet.Lexicon.ID, Sheet.Lexicon.QUEST_JOURNAL)
|
||||
val data = request.execute()
|
||||
updateData(data = data)
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.pixelized.rplexicon.repository.parser.spell.SpellBookParser
|
|||
import com.pixelized.rplexicon.model.AssignedSpell
|
||||
import com.pixelized.rplexicon.model.Spell
|
||||
import com.pixelized.rplexicon.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -23,6 +24,9 @@ class SpellRepository @Inject constructor(
|
|||
private val _spells = MutableStateFlow<Map<String, List<AssignedSpell>>>(emptyMap())
|
||||
val spells: StateFlow<Map<String, List<AssignedSpell>>> get() = _spells
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun findAssignedSpells(character: String?): List<AssignedSpell>? {
|
||||
return character?.let { _spells.value[it] }
|
||||
}
|
||||
|
|
@ -42,6 +46,7 @@ class SpellRepository @Inject constructor(
|
|||
val assignedSpells = assignedSpellParser.parse(data = magic, spells = spellsBook)
|
||||
this@SpellRepository.spellsBook = spellsBook
|
||||
_spells.emit(assignedSpells)
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ fun CharacterSheetScreen(
|
|||
)
|
||||
val refresh = rememberPullRefreshState(
|
||||
refreshing = false,
|
||||
onRefresh = { scope.launch { viewModel.update() } },
|
||||
onRefresh = { scope.launch { viewModel.update(force = true) } },
|
||||
)
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
|
|
@ -107,7 +107,7 @@ fun CharacterSheetScreen(
|
|||
sheetState = sheetState,
|
||||
refreshState = refresh,
|
||||
onRefresh = {
|
||||
scope.launch { viewModel.update() }
|
||||
scope.launch { viewModel.update(force = true) }
|
||||
},
|
||||
onBack = {
|
||||
screen.popBackStack()
|
||||
|
|
|
|||
|
|
@ -41,53 +41,79 @@ class CharacterSheetViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
characterRepository.data.collect { sheets ->
|
||||
_header.value = withContext(Dispatchers.Default) {
|
||||
headerFactory.convert(model = sheets.getValue(character))
|
||||
launch {
|
||||
characterRepository.data.collect { sheets ->
|
||||
_header.value = withContext(Dispatchers.Default) {
|
||||
headerFactory.convert(model = sheets.getValue(character))
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
update(force = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update() = coroutineScope {
|
||||
suspend fun update(force: Boolean) = coroutineScope {
|
||||
_isLoading.value = true
|
||||
val characterRequest = async {
|
||||
try {
|
||||
characterRepository.fetchCharacterSheet()
|
||||
actionRepository.fetchActions()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val statusRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchStatusSheet()
|
||||
if (force || characterRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
characterRepository.fetchCharacterSheet()
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val alterationRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchAlterationSheet()
|
||||
if (force || alterationRepository.lastAlterationSuccessFullUpdate.shouldUpdate()) {
|
||||
alterationRepository.fetchAlterationSheet()
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val statusRequest = async {
|
||||
try {
|
||||
if (force || alterationRepository.lastStatusSuccessFullUpdate.shouldUpdate()) {
|
||||
alterationRepository.fetchStatusSheet()
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
awaitAll(characterRequest, statusRequest, alterationRequest)
|
||||
|
||||
val actionRequest = async {
|
||||
try {
|
||||
actionRepository.fetchActions()
|
||||
if (force || actionRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
actionRepository.fetchActions()
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val spellRequest = async {
|
||||
try {
|
||||
spellRepository.fetchSpells()
|
||||
if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
spellRepository.fetchSpells()
|
||||
} else {
|
||||
Unit
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
awaitAll(characterRequest, statusRequest, alterationRequest, actionRequest, spellRequest)
|
||||
awaitAll(actionRequest, spellRequest)
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.rplexicon.R
|
||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||
import com.pixelized.rplexicon.utilitary.LOS_FULL
|
||||
import com.pixelized.rplexicon.utilitary.LOS_HOLLOW
|
||||
import com.pixelized.rplexicon.utilitary.extentions.cell
|
||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||
|
|
@ -38,6 +39,7 @@ data class LexiconItemUio(
|
|||
val diminutive: String?,
|
||||
@StringRes val gender: Int,
|
||||
@StringRes val race: Int,
|
||||
val isPlayingCharacter: Boolean = false,
|
||||
val placeholder: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
|
|
@ -48,6 +50,7 @@ data class LexiconItemUio(
|
|||
diminutive: String? = null,
|
||||
@StringRes gender: Int = R.string.gender_female_short,
|
||||
@StringRes race: Int = R.string.race_half_orc,
|
||||
isPlayingCharacter: Boolean = false,
|
||||
placeholder: Boolean = false,
|
||||
) = LexiconItemUio(
|
||||
id = id,
|
||||
|
|
@ -55,6 +58,7 @@ data class LexiconItemUio(
|
|||
diminutive = diminutive,
|
||||
gender = gender,
|
||||
race = race,
|
||||
isPlayingCharacter = isPlayingCharacter,
|
||||
placeholder = placeholder,
|
||||
)
|
||||
}
|
||||
|
|
@ -79,7 +83,7 @@ fun LexiconItem(
|
|||
modifier = Modifier
|
||||
.alignByBaseline()
|
||||
.placeholder { item.placeholder },
|
||||
text = LOS_HOLLOW,
|
||||
text = if (item.isPlayingCharacter) LOS_FULL else LOS_HOLLOW,
|
||||
)
|
||||
|
||||
FlowRow(
|
||||
|
|
@ -172,6 +176,7 @@ private class LexiconItemPreviewProvider : PreviewParameterProvider<LexiconItemU
|
|||
name = "Mundas-Naltum-Brulkhai-Arauishi",
|
||||
diminutive = "Mun-Nalt-Bru-Arahi",
|
||||
placeholder = false,
|
||||
isPlayingCharacter = true,
|
||||
),
|
||||
LexiconItemUio.preview(
|
||||
name = "Brulkhai",
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ fun LexiconScreen(
|
|||
refreshing = false,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
viewModel.updateLexicon()
|
||||
viewModel.updateLexicon(force = true)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -91,7 +91,7 @@ fun LexiconScreen(
|
|||
HandleFetchError(
|
||||
errors = viewModel.error,
|
||||
onPermissionGranted = {
|
||||
viewModel.updateLexicon()
|
||||
viewModel.updateLexicon(force = true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -27,9 +28,6 @@ import javax.inject.Inject
|
|||
class LexiconViewModel @Inject constructor(
|
||||
private val lexiconRepository: LexiconRepository,
|
||||
private val characterSheetRepository: CharacterSheetRepository,
|
||||
private val alterationRepository: AlterationRepository,
|
||||
private val actionRepository: ActionRepository,
|
||||
private val spellRepository: SpellRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _isLoading = mutableStateOf(false)
|
||||
|
|
@ -43,98 +41,97 @@ class LexiconViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
lexiconRepository.data.collect { items ->
|
||||
_items.value = items.map { item ->
|
||||
LexiconItemUio(
|
||||
id = item.id,
|
||||
name = item.name,
|
||||
diminutive = item.diminutive?.takeIf { it.isNotBlank() }?.let { "./ $it" },
|
||||
gender = when (item.gender) {
|
||||
Lexicon.Gender.MALE -> R.string.gender_male_short
|
||||
Lexicon.Gender.FEMALE -> R.string.gender_female_short
|
||||
Lexicon.Gender.UNDETERMINED -> R.string.gender_undetermined_short
|
||||
},
|
||||
race = when (item.race) {
|
||||
Lexicon.Race.ELF -> R.string.race_elf
|
||||
Lexicon.Race.HALFLING -> R.string.race_halfling
|
||||
Lexicon.Race.HUMAN -> R.string.race_human
|
||||
Lexicon.Race.DWARF -> R.string.race_dwarf
|
||||
Lexicon.Race.HALF_ELF -> R.string.race_half_elf
|
||||
Lexicon.Race.HALF_ORC -> R.string.race_half_orc
|
||||
Lexicon.Race.DRAGONBORN -> R.string.race_dragonborn
|
||||
Lexicon.Race.GNOME -> R.string.race_gnome
|
||||
Lexicon.Race.TIEFLING -> R.string.race_tiefling
|
||||
Lexicon.Race.AARAKOCRA -> R.string.race_aarakocra
|
||||
Lexicon.Race.GENASI -> R.string.race_genasi
|
||||
Lexicon.Race.DEEP_GNOME -> R.string.race_deep_gnome
|
||||
Lexicon.Race.GOLIATH -> R.string.race_goliath
|
||||
Lexicon.Race.UNDETERMINED -> R.string.race_undetermined
|
||||
},
|
||||
)
|
||||
}.sortedBy { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
val characterRequest = async {
|
||||
try {
|
||||
characterSheetRepository.fetchCharacterSheet()
|
||||
actionRepository.fetchActions()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
launch {
|
||||
lexiconRepository.data.collect { items ->
|
||||
_items.value = items
|
||||
.sortedBy { it.name }
|
||||
.sortedBy { !characterSheetRepository.haveSheet(it.name) }
|
||||
.map { item ->
|
||||
LexiconItemUio(
|
||||
id = item.id,
|
||||
name = item.name,
|
||||
diminutive = item.diminutive?.takeIf { it.isNotBlank() }
|
||||
?.let { "./ $it" },
|
||||
gender = when (item.gender) {
|
||||
Lexicon.Gender.MALE -> R.string.gender_male_short
|
||||
Lexicon.Gender.FEMALE -> R.string.gender_female_short
|
||||
Lexicon.Gender.UNDETERMINED -> R.string.gender_undetermined_short
|
||||
},
|
||||
race = when (item.race) {
|
||||
Lexicon.Race.ELF -> R.string.race_elf
|
||||
Lexicon.Race.HALFLING -> R.string.race_halfling
|
||||
Lexicon.Race.HUMAN -> R.string.race_human
|
||||
Lexicon.Race.DWARF -> R.string.race_dwarf
|
||||
Lexicon.Race.HALF_ELF -> R.string.race_half_elf
|
||||
Lexicon.Race.HALF_ORC -> R.string.race_half_orc
|
||||
Lexicon.Race.DRAGONBORN -> R.string.race_dragonborn
|
||||
Lexicon.Race.GNOME -> R.string.race_gnome
|
||||
Lexicon.Race.TIEFLING -> R.string.race_tiefling
|
||||
Lexicon.Race.AARAKOCRA -> R.string.race_aarakocra
|
||||
Lexicon.Race.GENASI -> R.string.race_genasi
|
||||
Lexicon.Race.DEEP_GNOME -> R.string.race_deep_gnome
|
||||
Lexicon.Race.GOLIATH -> R.string.race_goliath
|
||||
Lexicon.Race.UNDETERMINED -> R.string.race_undetermined
|
||||
},
|
||||
isPlayingCharacter = characterSheetRepository.haveSheet(item.name)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val alterationRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchAlterationSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
launch {
|
||||
updateLexicon(force = false)
|
||||
}
|
||||
val statusRequest = async {
|
||||
try {
|
||||
alterationRepository.fetchStatusSheet()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val magicRequest = async {
|
||||
try {
|
||||
spellRepository.fetchSpells()
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
}
|
||||
}
|
||||
val lexiconRequest = async {
|
||||
fetchLexicon()
|
||||
}
|
||||
awaitAll(
|
||||
characterRequest,
|
||||
alterationRequest,
|
||||
statusRequest,
|
||||
lexiconRequest,
|
||||
magicRequest,
|
||||
)
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateLexicon() {
|
||||
suspend fun updateLexicon(force: Boolean) = coroutineScope {
|
||||
_isLoading.value = true
|
||||
fetchLexicon()
|
||||
val lexicon = async {
|
||||
fetchLexicon(force = force)
|
||||
}
|
||||
val sheets = async {
|
||||
fetchCharacterSheet(force = force)
|
||||
}
|
||||
awaitAll(lexicon, sheets)
|
||||
_isLoading.value = false
|
||||
}
|
||||
|
||||
private suspend fun fetchLexicon() {
|
||||
private suspend fun fetchLexicon(force: Boolean) {
|
||||
try {
|
||||
lexiconRepository.fetchLexicon()
|
||||
if (force || lexiconRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
lexiconRepository.fetchLexicon()
|
||||
}
|
||||
}
|
||||
// user need to accept OAuth2 permission.
|
||||
catch (exception: UserRecoverableAuthIOException) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
|
||||
} catch (exception: IncompatibleSheetStructure) {
|
||||
}
|
||||
// the data sheet structure is not as expected
|
||||
catch (exception: IncompatibleSheetStructure) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Structure)
|
||||
}
|
||||
// default exception
|
||||
catch (exception: Exception) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Default)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchCharacterSheet(force: Boolean) {
|
||||
try {
|
||||
if (force || characterSheetRepository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
characterSheetRepository.fetchCharacterSheet()
|
||||
}
|
||||
}
|
||||
// user need to accept OAuth2 permission.
|
||||
catch (exception: UserRecoverableAuthIOException) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Permission(intent = exception.intent))
|
||||
}
|
||||
// the data sheet structure is not as expected
|
||||
catch (exception: IncompatibleSheetStructure) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
_error.emit(FetchErrorUio.Structure)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.rplexicon.ui.screens.location.list
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -48,7 +47,7 @@ fun LocationScreen(
|
|||
refreshing = false,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
viewModel.fetchLocation()
|
||||
viewModel.update(force = true)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -67,7 +66,7 @@ fun LocationScreen(
|
|||
HandleFetchError(
|
||||
errors = viewModel.error,
|
||||
onPermissionGranted = {
|
||||
viewModel.fetchLocation()
|
||||
viewModel.update(force = true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,15 +42,17 @@ class LocationViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
launch {
|
||||
fetchLocation()
|
||||
update(force = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchLocation() {
|
||||
suspend fun update(force: Boolean) {
|
||||
_isLoading.value = true
|
||||
try {
|
||||
_isLoading.value = true
|
||||
repository.fetchLocation()
|
||||
if (force || repository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
repository.fetchLocation()
|
||||
}
|
||||
}
|
||||
// user need to accept OAuth2 permission.
|
||||
catch (exception: UserRecoverableAuthIOException) {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@ package com.pixelized.rplexicon.ui.screens.quest.list
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -51,7 +47,7 @@ fun QuestListScreen(
|
|||
refreshing = false,
|
||||
onRefresh = {
|
||||
scope.launch {
|
||||
viewModel.fetchQuests()
|
||||
viewModel.update(force = true)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -70,7 +66,7 @@ fun QuestListScreen(
|
|||
HandleFetchError(
|
||||
errors = viewModel.error,
|
||||
onPermissionGranted = {
|
||||
viewModel.fetchQuests()
|
||||
viewModel.update(force = true)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,15 +43,17 @@ class QuestListViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
launch {
|
||||
fetchQuests()
|
||||
update(force = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchQuests() {
|
||||
suspend fun update(force: Boolean) {
|
||||
_isLoading.value = true
|
||||
try {
|
||||
_isLoading.value = true
|
||||
repository.fetchQuests()
|
||||
if (force || repository.lastSuccessFullUpdate.shouldUpdate()) {
|
||||
repository.fetchQuests()
|
||||
}
|
||||
}
|
||||
// user need to accept OAuth2 permission.
|
||||
catch (exception: UserRecoverableAuthIOException) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.pixelized.rplexicon.utilitary
|
||||
|
||||
|
||||
@JvmInline
|
||||
value class Update(private val value: Long) {
|
||||
|
||||
fun shouldUpdate(): Boolean {
|
||||
return System.currentTimeMillis() - value > DELTA
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DELTA = 5 * 60 * 1000
|
||||
|
||||
val INITIAL = Update(0)
|
||||
|
||||
fun currentTime(): Update {
|
||||
return Update(System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue