Refactor repositories by adding parsers.
Add character sheet poc.
This commit is contained in:
		
							parent
							
								
									bb760392f0
								
							
						
					
					
						commit
						87a1471efe
					
				
					 17 changed files with 761 additions and 286 deletions
				
			
		| 
						 | 
				
			
			@ -2,6 +2,7 @@ package com.pixelized.rplexicon
 | 
			
		|||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import androidx.activity.ComponentActivity
 | 
			
		||||
import androidx.activity.compose.setContent
 | 
			
		||||
import androidx.compose.foundation.layout.WindowInsets
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +20,7 @@ import androidx.compose.runtime.staticCompositionLocalOf
 | 
			
		|||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
 | 
			
		||||
import androidx.core.view.WindowCompat
 | 
			
		||||
import com.pixelized.rplexicon.facotry.RollParser
 | 
			
		||||
import com.pixelized.rplexicon.ui.navigation.ScreenNavHost
 | 
			
		||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
			
		||||
import dagger.hilt.android.AndroidEntryPoint
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
import com.pixelized.rplexicon.model.Lexicon
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class GenderParser @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    fun parse(gender: String?): Lexicon.Gender {
 | 
			
		||||
        return when (gender?.takeIf { it.isNotBlank() }) {
 | 
			
		||||
            Gender.MALE -> Lexicon.Gender.MALE
 | 
			
		||||
            Gender.FEMALE -> Lexicon.Gender.FEMALE
 | 
			
		||||
            else -> Lexicon.Gender.UNDETERMINED
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Gender {
 | 
			
		||||
        const val MALE = "Male"
 | 
			
		||||
        const val FEMALE = "Femelle"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,83 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
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.extentions.checkSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class LexiconParser @Inject constructor(
 | 
			
		||||
    private val portraitParser: PortraitParser,
 | 
			
		||||
    private val genderParser: GenderParser,
 | 
			
		||||
    private val raceParser: RaceParser,
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    @Throws(IncompatibleSheetStructure::class)
 | 
			
		||||
    fun parse(data: ValueRange): List<Lexicon> {
 | 
			
		||||
        val sheet = data.values.sheet()
 | 
			
		||||
        lateinit var sheetStructure: Map<String, Int>
 | 
			
		||||
        var id = 0
 | 
			
		||||
 | 
			
		||||
        return sheet?.mapIndexedNotNull { index, row ->
 | 
			
		||||
            when {
 | 
			
		||||
                index == 0 -> {
 | 
			
		||||
                    sheetStructure = row.checkSheetStructure(model = COLUMNS)
 | 
			
		||||
                    null
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                row is List<*> -> {
 | 
			
		||||
                    val name = row.getOrNull(sheetStructure.name) as? String
 | 
			
		||||
                    val diminutive = row.getOrNull(sheetStructure.diminutive) as? String?
 | 
			
		||||
                    val gender = row.getOrNull(sheetStructure.gender) as? String?
 | 
			
		||||
                    val race = row.getOrNull(sheetStructure.race) as? String?
 | 
			
		||||
                    val portrait = row.getOrNull(sheetStructure.portrait) as? String?
 | 
			
		||||
                    val description = row.getOrNull(sheetStructure.description) as? String?
 | 
			
		||||
                    val history = row.getOrNull(sheetStructure.history) as? String?
 | 
			
		||||
                    val tags = row.getOrNull(sheetStructure.tags) as? String?
 | 
			
		||||
 | 
			
		||||
                    if (name != null) {
 | 
			
		||||
                        Lexicon(
 | 
			
		||||
                            id = id++,
 | 
			
		||||
                            sheetIndex = index,
 | 
			
		||||
                            name = name,
 | 
			
		||||
                            diminutive = diminutive?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            gender = genderParser.parse(gender),
 | 
			
		||||
                            race = raceParser.parser(race),
 | 
			
		||||
                            portrait = portraitParser.parse(portrait),
 | 
			
		||||
                            description = description?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            history = history?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            tags = tags?.takeIf { it.isNotBlank() },
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        } ?: emptyList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val Map<String, Int>.name: Int get() = getValue(COLUMNS[0])
 | 
			
		||||
    private val Map<String, Int>.diminutive: Int get() = getValue(COLUMNS[1])
 | 
			
		||||
    private val Map<String, Int>.gender: Int get() = getValue(COLUMNS[2])
 | 
			
		||||
    private val Map<String, Int>.race: Int get() = getValue(COLUMNS[3])
 | 
			
		||||
    private val Map<String, Int>.portrait: Int get() = getValue(COLUMNS[4])
 | 
			
		||||
    private val Map<String, Int>.description: Int get() = getValue(COLUMNS[5])
 | 
			
		||||
    private val Map<String, Int>.history: Int get() = getValue(COLUMNS[6])
 | 
			
		||||
    private val Map<String, Int>.tags: Int get() = getValue(COLUMNS[7])
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val COLUMNS = listOf(
 | 
			
		||||
            "Nom",
 | 
			
		||||
            "Diminutif",
 | 
			
		||||
            "Sexe",
 | 
			
		||||
            "Race",
 | 
			
		||||
            "Portrait",
 | 
			
		||||
            "Description",
 | 
			
		||||
            "Histoire",
 | 
			
		||||
            "Mots clés",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
			
		||||
import com.pixelized.rplexicon.model.Location
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class LocationParser @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    @Throws(IncompatibleSheetStructure::class)
 | 
			
		||||
    fun parse(data: ValueRange): List<Location> {
 | 
			
		||||
        var id = 0
 | 
			
		||||
        var sheetStructure: Map<String, Int>? = null
 | 
			
		||||
 | 
			
		||||
        return data.values.sheet()?.mapIndexedNotNull { index, item ->
 | 
			
		||||
            when {
 | 
			
		||||
                index == 0 -> {
 | 
			
		||||
                    sheetStructure = item.checkSheetStructure(COLUMNS)
 | 
			
		||||
                    null
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                item is List<*> -> {
 | 
			
		||||
                    val name = item.getOrNull(sheetStructure.name) as? String
 | 
			
		||||
                    val uri = item.getOrNull(sheetStructure.uri)?.toString()?.let { Uri.parse(it) }
 | 
			
		||||
 | 
			
		||||
                    if (name != null && uri != null) {
 | 
			
		||||
                        Location(
 | 
			
		||||
                            id = id++,
 | 
			
		||||
                            sheetIndex = index,
 | 
			
		||||
                            name = name,
 | 
			
		||||
                            uri = uri,
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        } ?: emptyList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val Map<String, Int>?.name: Int get() = this?.getValue(COLUMNS[0]) ?: 0
 | 
			
		||||
    private val Map<String, Int>?.uri: Int get() = this?.getValue(COLUMNS[1]) ?: 1
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val COLUMNS = listOf(
 | 
			
		||||
            "nom", "carte"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
			
		||||
import com.pixelized.rplexicon.model.Location
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class MarqueeParser @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    fun parse(data: ValueRange): List<Location.Marquee> {
 | 
			
		||||
        val sheet = data.values.sheet()
 | 
			
		||||
        lateinit var structure: Map<String, Int>
 | 
			
		||||
 | 
			
		||||
        return sheet?.mapIndexedNotNull { index, item ->
 | 
			
		||||
            when {
 | 
			
		||||
                index == 0 -> {
 | 
			
		||||
                    structure = item.checkSheetStructure(model = COLUMNS)
 | 
			
		||||
                    null
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                item is List<*> -> {
 | 
			
		||||
                    val map = item.getOrNull(structure.map) as? String
 | 
			
		||||
                    val name = item.getOrNull(structure.name) as? String
 | 
			
		||||
                    val x = (item.getOrNull(structure.x) as? String)?.toFloatOrNull()
 | 
			
		||||
                    val y = (item.getOrNull(structure.y) as? String)?.toFloatOrNull()
 | 
			
		||||
                    val description = item.getOrNull(structure.description) as? String?
 | 
			
		||||
 | 
			
		||||
                    if (map != null && name != null && x != null && y != null) {
 | 
			
		||||
                        Location.Marquee(
 | 
			
		||||
                            map = map,
 | 
			
		||||
                            name = name,
 | 
			
		||||
                            x = x,
 | 
			
		||||
                            y = y,
 | 
			
		||||
                            description = description,
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        } ?: emptyList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val Map<String, Int>.map: Int get() = getValue(COLUMNS[0])
 | 
			
		||||
    private val Map<String, Int>.name: Int get() = getValue(COLUMNS[1])
 | 
			
		||||
    private val Map<String, Int>.x: Int get() = getValue(COLUMNS[2])
 | 
			
		||||
    private val Map<String, Int>.y: Int get() = getValue(COLUMNS[3])
 | 
			
		||||
    private val Map<String, Int>.description: Int get() = getValue(COLUMNS[4])
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val COLUMNS = listOf(
 | 
			
		||||
            "carte", "nom", "x", "y", "description"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class PortraitParser @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    fun parse(value: String?): List<Uri> {
 | 
			
		||||
        return value?.split(SEPARATOR)?.mapNotNull { it.toUriOrNull() } ?: emptyList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val SEPARATOR = "\n"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
			
		||||
import com.pixelized.rplexicon.model.QuestEntry
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class QuestParser @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    fun parse(value: ValueRange): List<QuestEntry> {
 | 
			
		||||
        val sheet = value.values.sheet()
 | 
			
		||||
        lateinit var structure: Map<String, Int>
 | 
			
		||||
 | 
			
		||||
        return sheet?.mapIndexedNotNull { index, item ->
 | 
			
		||||
            when {
 | 
			
		||||
                index == 0 -> {
 | 
			
		||||
                    structure = item.checkSheetStructure(model = COLUMNS)
 | 
			
		||||
                    null
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                item is List<*> -> {
 | 
			
		||||
                    val title = item.getOrNull(structure.title) as? String
 | 
			
		||||
                    val subtitle = item.getOrNull(structure.subtitle) as? String?
 | 
			
		||||
                    val complete = item.getOrNull(structure.complete) as? String?
 | 
			
		||||
                    val questGiver = item.getOrNull(structure.questGiver) as? String?
 | 
			
		||||
                    val area = item.getOrNull(structure.area) as? String?
 | 
			
		||||
                    val groupReward = item.getOrNull(structure.groupReward) as? String?
 | 
			
		||||
                    val individualReward = item.getOrNull(structure.individualReward) as? String?
 | 
			
		||||
                    val description = item.getOrNull(structure.description) as? String
 | 
			
		||||
                    val background = item.getOrNull(structure.background) as? String?
 | 
			
		||||
 | 
			
		||||
                    if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) {
 | 
			
		||||
                        QuestEntry(
 | 
			
		||||
                            sheetIndex = index,
 | 
			
		||||
                            title = title,
 | 
			
		||||
                            subtitle = subtitle?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            complete = complete.equals("TRUE", ignoreCase = true),
 | 
			
		||||
                            questGiver = questGiver?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            area = area?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            groupReward = groupReward?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            individualReward = individualReward?.takeIf { it.isNotBlank() },
 | 
			
		||||
                            description = description,
 | 
			
		||||
                            background = background?.toUriOrNull(),
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        } ?: emptyList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val Map<String, Int>.title: Int get() = getValue(COLUMNS[0])
 | 
			
		||||
    private val Map<String, Int>.subtitle: Int get() = getValue(COLUMNS[1])
 | 
			
		||||
    private val Map<String, Int>.complete: Int get() = getValue(COLUMNS[2])
 | 
			
		||||
    private val Map<String, Int>.questGiver: Int get() = getValue(COLUMNS[3])
 | 
			
		||||
    private val Map<String, Int>.area: Int get() = getValue(COLUMNS[4])
 | 
			
		||||
    private val Map<String, Int>.groupReward: Int get() = getValue(COLUMNS[5])
 | 
			
		||||
    private val Map<String, Int>.individualReward: Int get() = getValue(COLUMNS[6])
 | 
			
		||||
    private val Map<String, Int>.description: Int get() = getValue(COLUMNS[7])
 | 
			
		||||
    private val Map<String, Int>.background: Int get() = getValue(COLUMNS[8])
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val COLUMNS = listOf(
 | 
			
		||||
            "Titre",
 | 
			
		||||
            "Sous Titre",
 | 
			
		||||
            "Compléter",
 | 
			
		||||
            "Commanditaire",
 | 
			
		||||
            "Lieu",
 | 
			
		||||
            "Récompense de groupe",
 | 
			
		||||
            "Récompense individuelle",
 | 
			
		||||
            "Description",
 | 
			
		||||
            "fond"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
import com.pixelized.rplexicon.model.Lexicon
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class RaceParser @Inject constructor() {
 | 
			
		||||
 | 
			
		||||
    fun parser(race: String?): Lexicon.Race {
 | 
			
		||||
        return when (race?.takeIf { it.isNotBlank() }) {
 | 
			
		||||
            Race.ELF -> Lexicon.Race.ELF
 | 
			
		||||
            Race.HALFLING -> Lexicon.Race.HALFLING
 | 
			
		||||
            Race.HUMAN -> Lexicon.Race.HUMAN
 | 
			
		||||
            Race.DWARF -> Lexicon.Race.DWARF
 | 
			
		||||
            Race.HALF_ELF -> Lexicon.Race.HALF_ELF
 | 
			
		||||
            Race.HALF_ORC -> Lexicon.Race.HALF_ORC
 | 
			
		||||
            Race.DRAGONBORN -> Lexicon.Race.DRAGONBORN
 | 
			
		||||
            Race.GNOME -> Lexicon.Race.GNOME
 | 
			
		||||
            Race.TIEFLING -> Lexicon.Race.TIEFLING
 | 
			
		||||
            Race.AARAKOCRA -> Lexicon.Race.AARAKOCRA
 | 
			
		||||
            Race.GENASI -> Lexicon.Race.GENASI
 | 
			
		||||
            Race.DEEP_GNOME -> Lexicon.Race.DEEP_GNOME
 | 
			
		||||
            Race.GOLIATH -> Lexicon.Race.GOLIATH
 | 
			
		||||
            else -> Lexicon.Race.UNDETERMINED
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Race {
 | 
			
		||||
        const val ELF = "Elfe"
 | 
			
		||||
        const val HALFLING = "Halfelin"
 | 
			
		||||
        const val HUMAN = "Humain"
 | 
			
		||||
        const val DWARF = "Nain"
 | 
			
		||||
        const val HALF_ELF = "Demi-Elfe"
 | 
			
		||||
        const val HALF_ORC = "Demi-Orc"
 | 
			
		||||
        const val DRAGONBORN = "Drakéide"
 | 
			
		||||
        const val GNOME = "Gnome"
 | 
			
		||||
        const val TIEFLING = "Tieffelin"
 | 
			
		||||
        const val AARAKOCRA = "Aarakocra"
 | 
			
		||||
        const val GENASI = "Génasi"
 | 
			
		||||
        const val DEEP_GNOME = "Gnome des Profondeurs"
 | 
			
		||||
        const val GOLIATH = "Goliath"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,49 @@
 | 
			
		|||
package com.pixelized.rplexicon.facotry
 | 
			
		||||
 | 
			
		||||
import com.pixelized.rplexicon.model.CharacterSheet
 | 
			
		||||
import com.pixelized.rplexicon.model.Roll
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
class RollParser @Inject constructor() {
 | 
			
		||||
    private val diceRegex = Regex("(\\d+)d(\\d+)")
 | 
			
		||||
    private val bonusRegex = Regex("(?:[a-zA-Z]|\\?)[a-zA-Z]+")
 | 
			
		||||
 | 
			
		||||
    fun parseRoll(characterSheet: CharacterSheet, value: String?): Roll {
 | 
			
		||||
        val roll = value?.split(";")
 | 
			
		||||
 | 
			
		||||
        val label = roll?.getOrNull(0)
 | 
			
		||||
        val (dices, bonus) = roll?.getOrNull(1)?.let { item ->
 | 
			
		||||
            val dices = diceRegex.findAll(item).toList().map { it.parseDice() }
 | 
			
		||||
            val bonus = bonusRegex.findAll(item).map { bonus ->
 | 
			
		||||
                Roll.Bonus(
 | 
			
		||||
                    label = bonus.value,
 | 
			
		||||
                    bonus = bonus.value.parseBonus(characterSheet = characterSheet),
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            dices.toList() to bonus.toList()
 | 
			
		||||
        } ?: (null to null)
 | 
			
		||||
 | 
			
		||||
        return Roll(
 | 
			
		||||
            label = label.toString(),
 | 
			
		||||
            dices = dices ?: emptyList(),
 | 
			
		||||
            bonus = bonus ?: emptyList(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun String?.parseBonus(
 | 
			
		||||
        characterSheet: CharacterSheet,
 | 
			
		||||
    ): Int = when (this?.lowercase()) {
 | 
			
		||||
        "bonus" -> characterSheet.proficiency
 | 
			
		||||
        "force" -> characterSheet.strengthBonus
 | 
			
		||||
        else -> 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun MatchResult.parseDice(): Roll.Dice {
 | 
			
		||||
        val (count, faces) = destructured
 | 
			
		||||
        return Roll.Dice(
 | 
			
		||||
            label = value,
 | 
			
		||||
            count = count.toIntOrNull() ?: 0,
 | 
			
		||||
            faces = faces.toIntOrNull() ?: 0,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,84 @@
 | 
			
		|||
package com.pixelized.rplexicon.model
 | 
			
		||||
 | 
			
		||||
import androidx.compose.runtime.Stable
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class CharacterSheet(
 | 
			
		||||
    val hitPoint: Int, // Point de vie
 | 
			
		||||
    val armorClass: Int, // Classe d'armure
 | 
			
		||||
    val proficiency: Int, // Bonus de maîtrise
 | 
			
		||||
    val strength: Int, // Force
 | 
			
		||||
    val dexterity: Int, // Dextérité
 | 
			
		||||
    val constitution: Int, // Constitution
 | 
			
		||||
    val intelligence: Int, // Intelligence
 | 
			
		||||
    val wisdom: Int, // Sagesse
 | 
			
		||||
    val charisma: Int, // Charisme
 | 
			
		||||
    val strengthSavingThrows: Int,  // Jet de sauvegarde: Force
 | 
			
		||||
    val dexteritySavingThrows: Int, // Jet de sauvegarde: Dextérité
 | 
			
		||||
    val constitutionSavingThrows: Int, // Jet de sauvegarde: Constitution
 | 
			
		||||
    val intelligenceSavingThrows: Int, // Jet de sauvegarde: Intelligence
 | 
			
		||||
    val wisdomSavingThrows: Int, // Jet de sauvegarde: Sagesse
 | 
			
		||||
    val charismaSavingThrows: Int, // Jet de sauvegarde: Charisme
 | 
			
		||||
    val acrobatics: Int, // DEX, Acrobaties
 | 
			
		||||
    val animalHandling: Int, // WIS, Arcanes
 | 
			
		||||
    val arcana: Int, // INT, Athlétisme
 | 
			
		||||
    val athletics: Int, // STR, Discrétion
 | 
			
		||||
    val deception: Int, // CHA, Dressage
 | 
			
		||||
    val history: Int, // INT, Escamotage
 | 
			
		||||
    val insight: Int, // WIS, Histoire
 | 
			
		||||
    val intimidation: Int, // CHA, Intimidation
 | 
			
		||||
    val investigation: Int, // INT, Intuition
 | 
			
		||||
    val medicine: Int, // WIS, Investigation
 | 
			
		||||
    val nature: Int, // INT, Médecine
 | 
			
		||||
    val perception: Int, // WIS, Nature
 | 
			
		||||
    val performance: Int, // CHA, Perception
 | 
			
		||||
    val persuasion: Int, // CHA, Persuasion
 | 
			
		||||
    val religion: Int, // INT, Religion
 | 
			
		||||
    val sleightOfHand: Int,  // DEX, Représentation
 | 
			
		||||
    val stealth: Int, // DEX, Survie
 | 
			
		||||
    val survival: Int, // WIS, Tromperie
 | 
			
		||||
) {
 | 
			
		||||
    val proficiencyBonus: Int = kotlin.math.floor(proficiency / 2 - 5f).toInt()
 | 
			
		||||
    val strengthBonus: Int = kotlin.math.floor(strength / 2 - 5f).toInt()
 | 
			
		||||
    val dexterityBonus: Int = kotlin.math.floor(dexterity / 2 - 5f).toInt()
 | 
			
		||||
    val constitutionBonus: Int = kotlin.math.floor(constitution / 2 - 5f).toInt()
 | 
			
		||||
    val intelligenceBonus: Int = kotlin.math.floor(intelligence / 2 - 5f).toInt()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class Roll(
 | 
			
		||||
    val label: String,
 | 
			
		||||
    val dices: List<Dice>,
 | 
			
		||||
    val bonus: List<Bonus>,
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Dice(
 | 
			
		||||
        val label: String,
 | 
			
		||||
        val count: Int,
 | 
			
		||||
        val faces: Int,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Bonus(
 | 
			
		||||
        val label: String,
 | 
			
		||||
        val bonus: Int,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										22
									
								
								app/src/main/java/com/pixelized/rplexicon/model/Location.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/src/main/java/com/pixelized/rplexicon/model/Location.kt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
package com.pixelized.rplexicon.model
 | 
			
		||||
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
import androidx.compose.runtime.Stable
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class Location(
 | 
			
		||||
    val id: Int,
 | 
			
		||||
    val sheetIndex: Int,
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val uri: Uri,
 | 
			
		||||
    val marquees: List<Marquee>,
 | 
			
		||||
) {
 | 
			
		||||
    @Stable
 | 
			
		||||
    data class Marquee(
 | 
			
		||||
        val map: String,
 | 
			
		||||
        val name: String,
 | 
			
		||||
        val x: Float,
 | 
			
		||||
        val y: Float,
 | 
			
		||||
        val description: String?,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,120 @@
 | 
			
		|||
package com.pixelized.rplexicon.repository
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
			
		||||
import com.pixelized.rplexicon.facotry.RollParser
 | 
			
		||||
import com.pixelized.rplexicon.model.CharacterSheet
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.sheet
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import javax.inject.Singleton
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
class CharacterSheetRepository @Inject constructor(
 | 
			
		||||
    private val googleRepository: GoogleSheetServiceRepository,
 | 
			
		||||
    private val rollParser: RollParser,
 | 
			
		||||
) {
 | 
			
		||||
    @Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
 | 
			
		||||
    suspend fun fetchCharacterSheet() {
 | 
			
		||||
        googleRepository.fetch { sheet ->
 | 
			
		||||
            val request = sheet.get(Sheet.ID, Sheet.CHARACTER_SHEET)
 | 
			
		||||
            val data = request.execute()
 | 
			
		||||
            updateData(data = data)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Throws(IncompatibleSheetStructure::class)
 | 
			
		||||
    private fun updateData(data: ValueRange?) {
 | 
			
		||||
        val sheet = data?.values?.sheet()
 | 
			
		||||
        var id = 0
 | 
			
		||||
 | 
			
		||||
        val bru = sheet?.map { (it as? List<*>)?.get(1) }
 | 
			
		||||
 | 
			
		||||
        val characterSheet = CharacterSheet(
 | 
			
		||||
            hitPoint = (bru?.get(Sheet.HIT_POINT) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            armorClass = (bru?.get(Sheet.ARMOR_CLASS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            proficiency = (bru?.get(Sheet.PROFICIENCY) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            strength = (bru?.get(Sheet.STRENGTH) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            dexterity = (bru?.get(Sheet.DEXTERITY) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            constitution = (bru?.get(Sheet.CONSTITUTION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            intelligence = (bru?.get(Sheet.INTELLIGENCE) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            wisdom = (bru?.get(Sheet.WISDOM) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            charisma = (bru?.get(Sheet.CHARISMA) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            strengthSavingThrows = (bru?.get(Sheet.STRENGTH_SAVING_THROWS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            dexteritySavingThrows = (bru?.get(Sheet.DEXTERITY_SAVING_THROWS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            constitutionSavingThrows = (bru?.get(Sheet.CONSTITUTION_SAVING_THROWS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            intelligenceSavingThrows = (bru?.get(Sheet.INTELLIGENCE_SAVING_THROWS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            wisdomSavingThrows = (bru?.get(Sheet.WISDOM_SAVING_THROWS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            charismaSavingThrows = (bru?.get(Sheet.CHARISMA_SAVING_THROWS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            acrobatics = (bru?.get(Sheet.ACROBATICS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            animalHandling = (bru?.get(Sheet.ANIMAL_HANDLING) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            arcana = (bru?.get(Sheet.ARCANA) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            athletics = (bru?.get(Sheet.ATHLETICS) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            deception = (bru?.get(Sheet.DECEPTION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            history = (bru?.get(Sheet.HISTORY) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            insight = (bru?.get(Sheet.INSIGHT) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            intimidation = (bru?.get(Sheet.INTIMIDATION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            investigation = (bru?.get(Sheet.INVESTIGATION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            medicine = (bru?.get(Sheet.MEDICINE) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            nature = (bru?.get(Sheet.NATURE) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            perception = (bru?.get(Sheet.PERCEPTION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            performance = (bru?.get(Sheet.PERFORMANCE) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            persuasion = (bru?.get(Sheet.PERSUASION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            religion = (bru?.get(Sheet.RELIGION) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            sleightOfHand = (bru?.get(Sheet.SLEIGHT_OF_HAND) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            stealth = (bru?.get(Sheet.STEALTH) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
            survival = (bru?.get(Sheet.SURVIVAL) as? String)?.toIntOrNull() ?: 0,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        val rolls = bru?.subList(fromIndex = 34, bru.size)?.mapNotNull {
 | 
			
		||||
            rollParser.parseRoll(characterSheet = characterSheet, value = it?.toString())
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log.e(TAG, characterSheet.toString())
 | 
			
		||||
        Log.e(TAG, rolls.toString())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val TAG = "CharacterSheetRepository"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Sheet {
 | 
			
		||||
        const val ID = "1fHfzeb8y5u9lEQB1iI-jBEhqu7YSip5sAajXcXK7VJ8"
 | 
			
		||||
        const val CHARACTER_SHEET = "Feuille de personnage"
 | 
			
		||||
 | 
			
		||||
        const val HIT_POINT = 1
 | 
			
		||||
        const val ARMOR_CLASS = 2
 | 
			
		||||
        const val PROFICIENCY = 3
 | 
			
		||||
        const val STRENGTH = 4
 | 
			
		||||
        const val DEXTERITY = 5
 | 
			
		||||
        const val CONSTITUTION = 6
 | 
			
		||||
        const val INTELLIGENCE = 7
 | 
			
		||||
        const val WISDOM = 8
 | 
			
		||||
        const val CHARISMA = 9
 | 
			
		||||
        const val STRENGTH_SAVING_THROWS = 10
 | 
			
		||||
        const val DEXTERITY_SAVING_THROWS = 11
 | 
			
		||||
        const val CONSTITUTION_SAVING_THROWS = 12
 | 
			
		||||
        const val INTELLIGENCE_SAVING_THROWS = 13
 | 
			
		||||
        const val WISDOM_SAVING_THROWS = 14
 | 
			
		||||
        const val CHARISMA_SAVING_THROWS = 15
 | 
			
		||||
        const val ACROBATICS = 16
 | 
			
		||||
        const val ANIMAL_HANDLING = 17
 | 
			
		||||
        const val ARCANA = 18
 | 
			
		||||
        const val ATHLETICS = 19
 | 
			
		||||
        const val DECEPTION = 20
 | 
			
		||||
        const val HISTORY = 21
 | 
			
		||||
        const val INSIGHT = 22
 | 
			
		||||
        const val INTIMIDATION = 23
 | 
			
		||||
        const val INVESTIGATION = 24
 | 
			
		||||
        const val MEDICINE = 25
 | 
			
		||||
        const val NATURE = 26
 | 
			
		||||
        const val PERCEPTION = 27
 | 
			
		||||
        const val PERFORMANCE = 28
 | 
			
		||||
        const val PERSUASION = 29
 | 
			
		||||
        const val RELIGION = 30
 | 
			
		||||
        const val SLEIGHT_OF_HAND = 31
 | 
			
		||||
        const val STEALTH = 32
 | 
			
		||||
        const val SURVIVAL = 33
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
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.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import javax.inject.Singleton
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
class GoogleSheetServiceRepository @Inject constructor(
 | 
			
		||||
    authenticationRepository: AuthenticationRepository,
 | 
			
		||||
) {
 | 
			
		||||
    private val service: Sheets? by derivedStateOf {
 | 
			
		||||
        when (authenticationRepository.isAuthenticated.value) {
 | 
			
		||||
            true -> Sheets
 | 
			
		||||
                .Builder(
 | 
			
		||||
                    AndroidHttp.newCompatibleTransport(),
 | 
			
		||||
                    GsonFactory(),
 | 
			
		||||
                    authenticationRepository.credential,
 | 
			
		||||
                )
 | 
			
		||||
                .build()
 | 
			
		||||
 | 
			
		||||
            else -> null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Throws(ServiceNotReady::class)
 | 
			
		||||
    suspend fun fetch(
 | 
			
		||||
        lambda: suspend CoroutineScope.(service: Sheets.Spreadsheets.Values) -> Unit,
 | 
			
		||||
    ) {
 | 
			
		||||
        when (val service = service) {
 | 
			
		||||
            null -> throw ServiceNotReady()
 | 
			
		||||
            else -> withContext(Dispatchers.IO) {
 | 
			
		||||
                lambda(service.spreadsheets().values())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,42 +1,20 @@
 | 
			
		|||
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.facotry.LexiconParser
 | 
			
		||||
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 com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
 | 
			
		||||
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 LexiconRepository @Inject constructor(
 | 
			
		||||
    private val authenticationRepository: AuthenticationRepository
 | 
			
		||||
    private val googleRepository: GoogleSheetServiceRepository,
 | 
			
		||||
    private val lexiconParser: LexiconParser,
 | 
			
		||||
) {
 | 
			
		||||
    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<Lexicon>>(emptyList())
 | 
			
		||||
    val data: StateFlow<List<Lexicon>> get() = _data
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,156 +24,25 @@ class LexiconRepository @Inject constructor(
 | 
			
		|||
 | 
			
		||||
    @Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
 | 
			
		||||
    suspend fun fetchLexicon() {
 | 
			
		||||
        val service = sheetService
 | 
			
		||||
        if (service == null) {
 | 
			
		||||
            throw ServiceNotReady()
 | 
			
		||||
        } else {
 | 
			
		||||
            withContext(Dispatchers.IO) {
 | 
			
		||||
                val request = service.spreadsheets().values().get(Sheet.ID, Sheet.LEXIQUE)
 | 
			
		||||
                val data = request.execute()
 | 
			
		||||
                updateData(data = data)
 | 
			
		||||
            }
 | 
			
		||||
        googleRepository.fetch { sheet ->
 | 
			
		||||
            val request = sheet.get(Sheet.ID, Sheet.LEXICON)
 | 
			
		||||
            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
 | 
			
		||||
        var id = 0
 | 
			
		||||
        val lexicon: List<Lexicon> = sheet?.mapIndexedNotNull { index, row ->
 | 
			
		||||
            when {
 | 
			
		||||
                index == 0 -> {
 | 
			
		||||
                    sheetStructure = row.checkSheetStructure(model = Sheet.COLUMNS)
 | 
			
		||||
                    null
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                row is List<*> -> parseCharacterRow(
 | 
			
		||||
                    sheetStructure = sheetStructure,
 | 
			
		||||
                    id = id,
 | 
			
		||||
                    sheetIndex = index,
 | 
			
		||||
                    row = row,
 | 
			
		||||
                )?.also {
 | 
			
		||||
                    // update next id if parsing is successful.
 | 
			
		||||
                    id = it.id + 1
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        } ?: emptyList()
 | 
			
		||||
    private fun updateData(data: ValueRange) {
 | 
			
		||||
        val lexicon = lexiconParser.parse(data)
 | 
			
		||||
        _data.tryEmit(lexicon)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseCharacterRow(
 | 
			
		||||
        sheetStructure: Map<String, Int>?,
 | 
			
		||||
        id: Int,
 | 
			
		||||
        sheetIndex: Int,
 | 
			
		||||
        row: List<*>?,
 | 
			
		||||
    ): Lexicon? {
 | 
			
		||||
        val name = row?.getOrNull(sheetStructure.name) as? String
 | 
			
		||||
        val diminutive = row?.getOrNull(sheetStructure.diminutive) as? String?
 | 
			
		||||
        val gender = row?.getOrNull(sheetStructure.gender) as? String?
 | 
			
		||||
        val race = row?.getOrNull(sheetStructure.race) as? String?
 | 
			
		||||
        val portrait = row?.getOrNull(sheetStructure.portrait) as? String?
 | 
			
		||||
        val description = row?.getOrNull(sheetStructure.description) as? String?
 | 
			
		||||
        val history = row?.getOrNull(sheetStructure.history) as? String?
 | 
			
		||||
        val tags = row?.getOrNull(sheetStructure.tags) as? String?
 | 
			
		||||
 | 
			
		||||
        return if (name != null) {
 | 
			
		||||
            Lexicon(
 | 
			
		||||
                id = id,
 | 
			
		||||
                sheetIndex = sheetIndex,
 | 
			
		||||
                name = name,
 | 
			
		||||
                diminutive = diminutive?.takeIf { it.isNotBlank() },
 | 
			
		||||
                gender = when (gender?.takeIf { it.isNotBlank() }) {
 | 
			
		||||
                    Gender.MALE -> Lexicon.Gender.MALE
 | 
			
		||||
                    Gender.FEMALE -> Lexicon.Gender.FEMALE
 | 
			
		||||
                    else -> Lexicon.Gender.UNDETERMINED
 | 
			
		||||
                },
 | 
			
		||||
                race = when (race?.takeIf { it.isNotBlank() }) {
 | 
			
		||||
                    Race.ELF -> Lexicon.Race.ELF
 | 
			
		||||
                    Race.HALFLING -> Lexicon.Race.HALFLING
 | 
			
		||||
                    Race.HUMAN -> Lexicon.Race.HUMAN
 | 
			
		||||
                    Race.DWARF -> Lexicon.Race.DWARF
 | 
			
		||||
                    Race.HALF_ELF -> Lexicon.Race.HALF_ELF
 | 
			
		||||
                    Race.HALF_ORC -> Lexicon.Race.HALF_ORC
 | 
			
		||||
                    Race.DRAGONBORN -> Lexicon.Race.DRAGONBORN
 | 
			
		||||
                    Race.GNOME -> Lexicon.Race.GNOME
 | 
			
		||||
                    Race.TIEFLING -> Lexicon.Race.TIEFLING
 | 
			
		||||
                    Race.AARAKOCRA -> Lexicon.Race.AARAKOCRA
 | 
			
		||||
                    Race.GENASI -> Lexicon.Race.GENASI
 | 
			
		||||
                    Race.DEEP_GNOME -> Lexicon.Race.DEEP_GNOME
 | 
			
		||||
                    Race.GOLIATH -> Lexicon.Race.GOLIATH
 | 
			
		||||
                    else -> Lexicon.Race.UNDETERMINED
 | 
			
		||||
                },
 | 
			
		||||
                portrait = portrait?.split("\n")?.mapNotNull { it.toUriOrNull() } ?: emptyList(),
 | 
			
		||||
                description = description?.takeIf { it.isNotBlank() },
 | 
			
		||||
                history = history?.takeIf { it.isNotBlank() },
 | 
			
		||||
                tags = tags?.takeIf { it.isNotBlank() },
 | 
			
		||||
            )
 | 
			
		||||
        } else {
 | 
			
		||||
            null
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val Map<String, Int>?.name: Int get() = this?.getValue(Sheet.NAME) ?: 0
 | 
			
		||||
    private val Map<String, Int>?.diminutive: Int get() = this?.getValue(Sheet.DIMINUTIVE) ?: 1
 | 
			
		||||
    private val Map<String, Int>?.gender: Int get() = this?.getValue(Sheet.GENDER) ?: 2
 | 
			
		||||
    private val Map<String, Int>?.race: Int get() = this?.getValue(Sheet.RACE) ?: 3
 | 
			
		||||
    private val Map<String, Int>?.portrait: Int get() = this?.getValue(Sheet.PORTRAIT) ?: 4
 | 
			
		||||
    private val Map<String, Int>?.description: Int get() = this?.getValue(Sheet.DESCRIPTION) ?: 5
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        const val TAG = "LexiconRepository"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Sheet {
 | 
			
		||||
        const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
 | 
			
		||||
 | 
			
		||||
        const val LEXIQUE = "Lexique"
 | 
			
		||||
        const val META = "MetaData"
 | 
			
		||||
 | 
			
		||||
        val COLUMNS = listOf(
 | 
			
		||||
            "Nom",
 | 
			
		||||
            "Diminutif",
 | 
			
		||||
            "Sexe",
 | 
			
		||||
            "Race",
 | 
			
		||||
            "Portrait",
 | 
			
		||||
            "Description",
 | 
			
		||||
            "Histoire",
 | 
			
		||||
            "Mots clés",
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        val NAME = COLUMNS[0]
 | 
			
		||||
        val DIMINUTIVE = COLUMNS[1]
 | 
			
		||||
        val GENDER = COLUMNS[2]
 | 
			
		||||
        val RACE = COLUMNS[3]
 | 
			
		||||
        val PORTRAIT = COLUMNS[4]
 | 
			
		||||
        val DESCRIPTION = COLUMNS[5]
 | 
			
		||||
        val HISTORY = COLUMNS[6]
 | 
			
		||||
        val TAGS = COLUMNS[7]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Gender {
 | 
			
		||||
        const val MALE = "Male"
 | 
			
		||||
        const val FEMALE = "Femelle"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Race {
 | 
			
		||||
        const val ELF = "Elfe"
 | 
			
		||||
        const val HALFLING = "Halfelin"
 | 
			
		||||
        const val HUMAN = "Humain"
 | 
			
		||||
        const val DWARF = "Nain"
 | 
			
		||||
        const val HALF_ELF = "Demi-Elfe"
 | 
			
		||||
        const val HALF_ORC = "Demi-Orc"
 | 
			
		||||
        const val DRAGONBORN = "Drakéide"
 | 
			
		||||
        const val GNOME = "Gnome"
 | 
			
		||||
        const val TIEFLING = "Tieffelin"
 | 
			
		||||
        const val AARAKOCRA = "Aarakocra"
 | 
			
		||||
        const val GENASI = "Génasi"
 | 
			
		||||
        const val DEEP_GNOME = "Gnome des Profondeurs"
 | 
			
		||||
        const val GOLIATH = "Goliath"
 | 
			
		||||
        const val LEXICON = "Lexique"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
package com.pixelized.rplexicon.repository
 | 
			
		||||
 | 
			
		||||
import com.google.api.services.sheets.v4.Sheets
 | 
			
		||||
import com.google.api.services.sheets.v4.model.ValueRange
 | 
			
		||||
import com.pixelized.rplexicon.facotry.LocationParser
 | 
			
		||||
import com.pixelized.rplexicon.facotry.MarqueeParser
 | 
			
		||||
import com.pixelized.rplexicon.model.Location
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
 | 
			
		||||
import kotlinx.coroutines.async
 | 
			
		||||
import kotlinx.coroutines.awaitAll
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import kotlinx.coroutines.flow.StateFlow
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
import javax.inject.Singleton
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
class LocationRepository @Inject constructor(
 | 
			
		||||
    private val googleRepository: GoogleSheetServiceRepository,
 | 
			
		||||
    private val locationParser: LocationParser,
 | 
			
		||||
    private val marqueeParser: MarqueeParser,
 | 
			
		||||
) {
 | 
			
		||||
    private val _data = MutableStateFlow<List<Location>>(emptyList())
 | 
			
		||||
    val data: StateFlow<List<Location>> get() = _data
 | 
			
		||||
 | 
			
		||||
    @Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
 | 
			
		||||
    suspend fun fetchLocation() {
 | 
			
		||||
        googleRepository.fetch { sheet: Sheets.Spreadsheets.Values ->
 | 
			
		||||
            val (map, marquee) = awaitAll(
 | 
			
		||||
                async { sheet.get(Sheet.ID, Sheet.MAP).execute() },
 | 
			
		||||
                async { sheet.get(Sheet.ID, Sheet.MARQUEE).execute() },
 | 
			
		||||
            )
 | 
			
		||||
            updateData(map = map, marquee = marquee)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Throws(IncompatibleSheetStructure::class)
 | 
			
		||||
    private fun updateData(map: ValueRange, marquee: ValueRange) {
 | 
			
		||||
        val marquees = marqueeParser
 | 
			
		||||
            .parse(data = marquee)
 | 
			
		||||
            .groupBy { it.map }
 | 
			
		||||
 | 
			
		||||
        val maps = locationParser
 | 
			
		||||
            .parse(data = map)
 | 
			
		||||
            .map {
 | 
			
		||||
                val associatedMarquees = marquees[it.name]
 | 
			
		||||
                if (associatedMarquees != null) {
 | 
			
		||||
                    it.copy(marquees = associatedMarquees)
 | 
			
		||||
                } else {
 | 
			
		||||
                    it
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        _data.tryEmit(maps)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val TAG = "LocationRepository"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private object Sheet {
 | 
			
		||||
        const val ID = "1gbWaqXChz8pDJ-O3-Q8a_0wbDAYGlVA2voL7nEJUSn0"
 | 
			
		||||
        const val MAP = "carte"
 | 
			
		||||
        const val MARQUEE = "marqueur"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,82 +1,35 @@
 | 
			
		|||
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.facotry.QuestParser
 | 
			
		||||
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 com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
 | 
			
		||||
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 googleRepository: GoogleSheetServiceRepository,
 | 
			
		||||
    private val questParser: QuestParser,
 | 
			
		||||
) {
 | 
			
		||||
    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)
 | 
			
		||||
            }
 | 
			
		||||
        googleRepository.fetch { sheet ->
 | 
			
		||||
            val request = sheet.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()
 | 
			
		||||
 | 
			
		||||
    @Throws(IncompatibleSheetStructure::class, Exception::class)
 | 
			
		||||
    private fun updateData(data: ValueRange) {
 | 
			
		||||
        val questEntries = questParser.parse(value = data)
 | 
			
		||||
        val questMap = questEntries.groupBy { it.title }
 | 
			
		||||
 | 
			
		||||
        val quests = questMap.keys.mapIndexed { index, item ->
 | 
			
		||||
| 
						 | 
				
			
			@ -86,77 +39,11 @@ class QuestRepository @Inject constructor(
 | 
			
		|||
                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? String?
 | 
			
		||||
        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
 | 
			
		||||
        val background = row?.getOrNull(sheetStructure.background) as? String?
 | 
			
		||||
 | 
			
		||||
        return if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) {
 | 
			
		||||
            QuestEntry(
 | 
			
		||||
                sheetIndex = sheetIndex,
 | 
			
		||||
                title = title,
 | 
			
		||||
                subtitle = subtitle?.takeIf { it.isNotBlank() },
 | 
			
		||||
                complete = complete.equals("TRUE", ignoreCase = true),
 | 
			
		||||
                questGiver = questGiver?.takeIf { it.isNotBlank() },
 | 
			
		||||
                area = area?.takeIf { it.isNotBlank() },
 | 
			
		||||
                groupReward = groupReward?.takeIf { it.isNotBlank() },
 | 
			
		||||
                individualReward = individualReward?.takeIf { it.isNotBlank() },
 | 
			
		||||
                description = description,
 | 
			
		||||
                background = background?.toUriOrNull(),
 | 
			
		||||
            )
 | 
			
		||||
        } 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 val Map<String, Int>?.background: Int get() = this?.getValue(Sheet.BACKGROUND) ?: 8
 | 
			
		||||
 | 
			
		||||
    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",
 | 
			
		||||
            "fond"
 | 
			
		||||
        )
 | 
			
		||||
        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]
 | 
			
		||||
        val BACKGROUND = COLUMNS[8]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue