Update the error management

This commit is contained in:
Thomas Andres Gomez 2023-10-24 14:21:35 +02:00
parent a55e2c8162
commit 4123eaeccd
10 changed files with 152 additions and 109 deletions

View file

@ -12,16 +12,18 @@ import com.pixelized.rplexicon.repository.data.character.CharacterSheetRepositor
import com.pixelized.rplexicon.repository.data.character.DescriptionRepository
import com.pixelized.rplexicon.repository.data.character.EquipmentRepository
import com.pixelized.rplexicon.repository.data.character.InventoryRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.repository.data.lexicon.LexiconRepository
import com.pixelized.rplexicon.repository.data.lexicon.LocationRepository
import com.pixelized.rplexicon.repository.data.lexicon.QuestRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio.Structure.Type
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.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -40,8 +42,8 @@ class LauncherViewModel @Inject constructor(
equipmentRepository: EquipmentRepository,
) : ViewModel() {
private val _error = MutableSharedFlow<String>()
val error: SharedFlow<String> get() = _error
private val _error = MutableStateFlow<FetchErrorUio?>(null)
val error: Flow<FetchErrorUio?> get() = _error
var isLoading by mutableStateOf(true)
private set
@ -53,7 +55,7 @@ class LauncherViewModel @Inject constructor(
lexiconRepository.fetchLexicon()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Lexicon fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.LEXICON))
}
}
val location = async {
@ -61,7 +63,7 @@ class LauncherViewModel @Inject constructor(
locationRepository.fetchLocation()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Location fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.LOCATION))
}
}
val quest = async {
@ -69,7 +71,7 @@ class LauncherViewModel @Inject constructor(
questRepository.fetchQuests()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Quest fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.QUEST))
}
}
val characterSheet = async {
@ -77,7 +79,7 @@ class LauncherViewModel @Inject constructor(
characterSheetRepository.fetchCharacterSheet()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("CharacterSheet fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.CHARACTER))
}
}
val description = async {
@ -85,7 +87,7 @@ class LauncherViewModel @Inject constructor(
descriptionRepository.fetchDescription()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Skill/Spell description fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.DESCRIPTION))
}
}
val inventory = async {
@ -93,7 +95,7 @@ class LauncherViewModel @Inject constructor(
inventoryRepository.fetchInventory()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Inventories fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.INVENTORY))
}
}
val equipment = async {
@ -101,7 +103,7 @@ class LauncherViewModel @Inject constructor(
equipmentRepository.fetchEquipment()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Equipments fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.EQUIPMENT))
}
}
awaitAll(characterSheet)
@ -111,7 +113,7 @@ class LauncherViewModel @Inject constructor(
alterationRepository.fetchAlterationSheet(sheets = characterSheetRepository.sheets)
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Alteration lexicon fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.ALTERATION))
}
}
val action = async {
@ -119,7 +121,7 @@ class LauncherViewModel @Inject constructor(
actionRepository.fetchActions(characters = characterSheetRepository.data.value)
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Action fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.ACTION))
}
}
val spell = async {
@ -127,7 +129,7 @@ class LauncherViewModel @Inject constructor(
spellRepository.fetchSpells()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Spell fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.SPELL))
}
}
val skill = async {
@ -135,13 +137,12 @@ class LauncherViewModel @Inject constructor(
skillRepository.fetchSkills()
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.tryEmit("Skill fail to update")
_error.emit(FetchErrorUio.Structure(type = Type.SKILL))
}
}
awaitAll(
lexicon, location, quest, description, inventory,
equipment, alteration, action, spell, skill,
)
awaitAll(lexicon, location, quest)
awaitAll(description, inventory, equipment, alteration, action, spell, skill)
isLoading = false
}

View file

@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.pixelized.rplexicon.ui.composable.BlurredOverlayHost
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.ScreenNavHost
import com.pixelized.rplexicon.ui.screens.rolls.BlurredRollOverlayHostState
import com.pixelized.rplexicon.ui.screens.rolls.RollOverlay
@ -124,6 +125,7 @@ class MainActivity : ComponentActivity() {
),
content = {
Text(
modifier = Modifier.padding(vertical = 8.dp),
color = MaterialTheme.colorScheme.onSurface,
text = it.visuals.message,
)
@ -137,11 +139,10 @@ class MainActivity : ComponentActivity() {
}
}
LaunchedEffect(key1 = "LauncherError") {
launcherViewModel.error.collect {
snack.showSnackbar(message = it)
}
}
HandleFetchError(
snack = snack,
errors = launcherViewModel.error,
)
}
}
}

View file

@ -5,6 +5,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.platform.LocalContext
import com.pixelized.rplexicon.LocalSnack
@ -17,7 +18,23 @@ sealed class FetchErrorUio {
data class Firebase(val exception: Exception) : FetchErrorUio()
@Stable
data object Structure : FetchErrorUio()
data class Structure(val type: Type) : FetchErrorUio() {
@Stable
enum class Type {
UNKNOWN,
ACTION,
ALTERATION,
CHARACTER,
DESCRIPTION,
EQUIPMENT,
INVENTORY,
SKILL,
SPELL,
LEXICON,
LOCATION,
QUEST,
}
}
@Stable
data object Default : FetchErrorUio()
@ -25,32 +42,46 @@ sealed class FetchErrorUio {
@Composable
fun HandleFetchError(
errors: Flow<FetchErrorUio>,
onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snack, _ ->
snack.showSnackbar(message = context.getString(R.string.error_structure))
snack: SnackbarHostState = LocalSnack.current,
errors: Flow<FetchErrorUio?>,
onStructureError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Structure) -> Unit = { context, snack, error ->
val messageResources = when (error.type) {
FetchErrorUio.Structure.Type.UNKNOWN -> R.string.error_structure_unknowed
FetchErrorUio.Structure.Type.ACTION -> R.string.error_structure_action
FetchErrorUio.Structure.Type.ALTERATION -> R.string.error_structure_alteration
FetchErrorUio.Structure.Type.CHARACTER -> R.string.error_structure_character
FetchErrorUio.Structure.Type.DESCRIPTION -> R.string.error_structure_description
FetchErrorUio.Structure.Type.EQUIPMENT -> R.string.error_structure_equipment
FetchErrorUio.Structure.Type.INVENTORY -> R.string.error_structure_inventory
FetchErrorUio.Structure.Type.SKILL -> R.string.error_structure_skill
FetchErrorUio.Structure.Type.SPELL -> R.string.error_structure_spell
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
}
snack.showSnackbar(message = context.getString(messageResources))
},
onFirebaseError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Firebase) -> Unit = { context, snack, error ->
snack.showSnackbar(
message = error.exception.localizedMessage ?: context.getString(R.string.error_generic)
)
},
onDefaultError: suspend (context: Context, snack: SnackbarHostState, error: FetchErrorUio.Default) -> Unit = { context, snack, _ ->
snack.showSnackbar(message = context.getString(R.string.error_generic))
},
) {
val context = LocalContext.current
val snack = LocalSnack.current
val currentOnStructureError = rememberUpdatedState(newValue = onStructureError)
val currentOnDefaultError = rememberUpdatedState(newValue = onDefaultError)
val currentOnStructureError by rememberUpdatedState(newValue = onStructureError)
val currentOnFirebaseError by rememberUpdatedState(newValue = onFirebaseError)
val currentOnDefaultError by rememberUpdatedState(newValue = onDefaultError)
LaunchedEffect(key1 = "HandleFetchError") {
errors.collect { error ->
when (error) {
is FetchErrorUio.Firebase -> Unit
is FetchErrorUio.Structure -> currentOnStructureError.value.invoke(
context, snack, error,
)
is FetchErrorUio.Default -> currentOnDefaultError.value.invoke(
context, snack, error,
)
is FetchErrorUio.Firebase -> currentOnFirebaseError.invoke(context, snack, error)
is FetchErrorUio.Structure -> currentOnStructureError.invoke(context, snack, error)
is FetchErrorUio.Default -> currentOnDefaultError.invoke(context, snack, error)
else -> Unit
}
}
}

View file

@ -41,13 +41,11 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
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.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -63,6 +61,7 @@ import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.Loader
import com.pixelized.rplexicon.ui.composable.error.HandleFetchError
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.screens.character.composable.character.ProficiencyUio.ID.*
import com.pixelized.rplexicon.ui.screens.character.composable.character.StatUio.ID.*
@ -181,13 +180,9 @@ fun CharacterSheetScreen(
},
)
LaunchedEffect(key1 = "Error") {
viewModel.errors.collect { error ->
snack.showSnackbar(
message = error.exception.localizedMessage ?: ""
)
}
}
HandleFetchError(
errors = viewModel.error,
)
BackHandler(enabled = sheetState.isVisible) {
scope.launch { sheetState.hide() }

View file

@ -16,6 +16,7 @@ import com.pixelized.rplexicon.repository.data.character.InventoryRepository
import com.pixelized.rplexicon.repository.data.character.SkillRepository
import com.pixelized.rplexicon.repository.data.character.SpellRepository
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio
import com.pixelized.rplexicon.ui.composable.error.FetchErrorUio.Structure.Type
import com.pixelized.rplexicon.ui.navigation.screens.characterSheetArgument
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.async
@ -42,8 +43,8 @@ class CharacterSheetViewModel @Inject constructor(
private val _isLoading = mutableStateOf(false)
val isLoading: State<Boolean> get() = _isLoading
private val _error = MutableSharedFlow<FetchErrorUio.Firebase>()
val errors: SharedFlow<FetchErrorUio.Firebase> get() = _error
private val _error = MutableSharedFlow<FetchErrorUio>()
val error: SharedFlow<FetchErrorUio> get() = _error
val character = savedStateHandle.characterSheetArgument.name
@ -63,92 +64,84 @@ class CharacterSheetViewModel @Inject constructor(
suspend fun update(force: Boolean, full: Boolean = false) = coroutineScope {
_isLoading.value = true
val characters = async {
try {
if (force || characterRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || characterRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
characterRepository.fetchCharacterSheet()
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.CHARACTER))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
val description = async {
try {
if (full && (force || characterRepository.lastSuccessFullUpdate.shouldUpdate())) {
if (full && (force || descriptionRepository.lastSuccessFullUpdate.shouldUpdate())) {
try {
descriptionRepository.fetchDescription()
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.DESCRIPTION))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
val inventory = async {
try {
if (force || inventoryRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || inventoryRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
inventoryRepository.fetchInventory()
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.INVENTORY))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
val equipment = async {
try {
if (force || equipmentRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || equipmentRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
equipmentRepository.fetchEquipment()
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.EQUIPMENT))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
awaitAll(characters)
val alterations = async {
try {
if (force || alterationRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || alterationRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
alterationRepository.fetchAlterationSheet(sheets = characterRepository.sheets)
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.ALTERATION))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
val actions = async {
try {
if (force || actionRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || actionRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
actionRepository.fetchActions(characters = characterRepository.data.value)
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.ACTION))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
val spells = async {
try {
if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || spellRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
spellRepository.fetchSpells()
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.SPELL))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
val skill = async {
try {
if (force || skillRepository.lastSuccessFullUpdate.shouldUpdate()) {
if (force || skillRepository.lastSuccessFullUpdate.shouldUpdate()) {
try {
skillRepository.fetchSkills()
} else {
Unit
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure(type = Type.SKILL))
}
} catch (exception: Exception) {
Log.e(TAG, exception.message, exception)
}
}
awaitAll(description, alterations, inventory, equipment, actions, spells, skill)

View file

@ -101,7 +101,7 @@ class LexiconViewModel @Inject constructor(
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
_error.emit(FetchErrorUio.Structure(type = FetchErrorUio.Structure.Type.LEXICON))
}
// default exception
catch (exception: Exception) {
@ -119,7 +119,7 @@ class LexiconViewModel @Inject constructor(
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
_error.emit(FetchErrorUio.Structure(type = FetchErrorUio.Structure.Type.CHARACTER))
}
// default exception
catch (exception: Exception) {

View file

@ -56,7 +56,7 @@ class LocationViewModel @Inject constructor(
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
_error.emit(FetchErrorUio.Structure(type = FetchErrorUio.Structure.Type.LOCATION))
}
// default exception
catch (exception: Exception) {

View file

@ -57,7 +57,7 @@ class QuestListViewModel @Inject constructor(
// the data sheet structure is not as expected
catch (exception: IncompatibleSheetStructure) {
Log.e(TAG, exception.message, exception)
_error.emit(FetchErrorUio.Structure)
_error.emit(FetchErrorUio.Structure(type = FetchErrorUio.Structure.Type.QUEST))
}
// default exception
catch (exception: Exception) {

View file

@ -4,7 +4,18 @@
<string name="action_close">Fermer</string>
<string name="error_generic">Une erreur s\'est produite.</string>
<string name="error_structure">La structure du fichier semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_unknowed">La structure des données semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_action">La structure de la feuille d\'actions semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_alteration">La structure de la feuille d\'altération semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_character">La structure de la feuille des personnages semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_description">La structure de la feuille de description semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_equipment">La structure de la feuille d\'équipement semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_inventory">La structure de la feuille d\'inventaire semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_skill">La structure de la feuille des talents semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_spell">La structure de la feuille des sorts semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_lexicon">La structure du lexique semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_location">La structure des cartes semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="error_structure_quest">La structure des quêtes semble avoir changé et n\'est plus compatible avec cette application.</string>
<string name="generic_critical_success">SUCCÈS CRITIQUE</string>
<string name="generic_critical_failure">ÉCHEC CRITIQUE</string>

View file

@ -3,8 +3,19 @@
<string name="action_close">Close</string>
<string name="error_generic">An error occur.</string>
<string name="error_structure">The file structure appears to have changed and is no longer compatible with this application</string>
<string name="error_generic">An error occurred.</string>
<string name="error_structure_unknowed">The file structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_action">The action sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_alteration">The alteration sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_character">The character sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_description">The description sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_equipment">The equipment sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_inventory">The inventory sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_skill">The skill sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_spell">The spell sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_lexicon">The lexicon sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_location">The location sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="error_structure_quest">The quest sheet structure appears to have changed and is no longer compatible with this application</string>
<string name="generic_critical_success">CRITICAL SUCCESS</string>
<string name="generic_critical_failure">CRITICAL FAILURE</string>