Change the parsing mechanist to allow more flexibility.
This commit is contained in:
		
							parent
							
								
									f5c10c5154
								
							
						
					
					
						commit
						fb31de8130
					
				
					 19 changed files with 577 additions and 140 deletions
				
			
		| 
						 | 
					@ -24,8 +24,8 @@ android {
 | 
				
			||||||
        applicationId = "com.pixelized.rplexicon"
 | 
					        applicationId = "com.pixelized.rplexicon"
 | 
				
			||||||
        minSdk = 26
 | 
					        minSdk = 26
 | 
				
			||||||
        targetSdk = 33
 | 
					        targetSdk = 33
 | 
				
			||||||
        versionCode = 1
 | 
					        versionCode = 2
 | 
				
			||||||
        versionName = "0.1.0"
 | 
					        versionName = "0.1.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 | 
					        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 | 
				
			||||||
        vectorDrawables {
 | 
					        vectorDrawables {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,8 +11,8 @@
 | 
				
			||||||
      "type": "SINGLE",
 | 
					      "type": "SINGLE",
 | 
				
			||||||
      "filters": [],
 | 
					      "filters": [],
 | 
				
			||||||
      "attributes": [],
 | 
					      "attributes": [],
 | 
				
			||||||
      "versionCode": 1,
 | 
					      "versionCode": 2,
 | 
				
			||||||
      "versionName": "0.1.0",
 | 
					      "versionName": "0.1.1",
 | 
				
			||||||
      "outputFile": "app-release.apk"
 | 
					      "outputFile": "app-release.apk"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,13 +6,32 @@ data class Lexicon(
 | 
				
			||||||
    val id: Int,
 | 
					    val id: Int,
 | 
				
			||||||
    val name: String,
 | 
					    val name: String,
 | 
				
			||||||
    val diminutive: String?,
 | 
					    val diminutive: String?,
 | 
				
			||||||
    val gender: Gender = Gender.UNDETERMINED,
 | 
					    val gender: Gender,
 | 
				
			||||||
    val race: String?,
 | 
					    val race: Race,
 | 
				
			||||||
    val portrait: List<Uri>,
 | 
					    val portrait: List<Uri>,
 | 
				
			||||||
    val description: String?,
 | 
					    val description: String?,
 | 
				
			||||||
    val history: String?,
 | 
					    val history: String?,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    enum class Gender {
 | 
					    enum class Gender {
 | 
				
			||||||
        MALE, FEMALE, UNDETERMINED
 | 
					        MALE,
 | 
				
			||||||
 | 
					        FEMALE,
 | 
				
			||||||
 | 
					        UNDETERMINED
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    enum class Race {
 | 
				
			||||||
 | 
					        ELF,
 | 
				
			||||||
 | 
					        HALFLING,
 | 
				
			||||||
 | 
					        HUMAN,
 | 
				
			||||||
 | 
					        DWARF,
 | 
				
			||||||
 | 
					        HALF_ELF,
 | 
				
			||||||
 | 
					        HALF_ORC,
 | 
				
			||||||
 | 
					        DRAGONBORN,
 | 
				
			||||||
 | 
					        GNOME,
 | 
				
			||||||
 | 
					        TIEFLING,
 | 
				
			||||||
 | 
					        AARAKOCRA,
 | 
				
			||||||
 | 
					        GENASI,
 | 
				
			||||||
 | 
					        DEEP_GNOME,
 | 
				
			||||||
 | 
					        GOLIATH,
 | 
				
			||||||
 | 
					        UNDETERMINED
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ class AuthenticationRepository @Inject constructor(
 | 
				
			||||||
                .setBackOff(ExponentialBackOff())
 | 
					                .setBackOff(ExponentialBackOff())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            credential.selectedAccount = signInCredential.value?.let {
 | 
					            credential.selectedAccount = signInCredential.value?.let {
 | 
				
			||||||
                Account(it.id, "google")
 | 
					                Account(it.id, ACCOUNT_TYPE)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return credential
 | 
					            return credential
 | 
				
			||||||
| 
						 | 
					@ -40,8 +40,8 @@ class AuthenticationRepository @Inject constructor(
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
 | 
					        private const val ACCOUNT_TYPE = "google"
 | 
				
			||||||
        private val capabilities = listOf(
 | 
					        private val capabilities = listOf(
 | 
				
			||||||
//            SheetsScopes.SPREADSHEETS,
 | 
					 | 
				
			||||||
            SheetsScopes.SPREADSHEETS_READONLY,
 | 
					            SheetsScopes.SPREADSHEETS_READONLY,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +28,6 @@ class LexiconRepository @Inject constructor(
 | 
				
			||||||
                    GsonFactory(),
 | 
					                    GsonFactory(),
 | 
				
			||||||
                    authenticationRepository.credential,
 | 
					                    authenticationRepository.credential,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .setApplicationName("RP-Lexique")
 | 
					 | 
				
			||||||
                .build()
 | 
					                .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            else -> null
 | 
					            else -> null
 | 
				
			||||||
| 
						 | 
					@ -45,7 +44,7 @@ class LexiconRepository @Inject constructor(
 | 
				
			||||||
            throw ServiceNotReady()
 | 
					            throw ServiceNotReady()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            withContext(Dispatchers.IO) {
 | 
					            withContext(Dispatchers.IO) {
 | 
				
			||||||
                val request = service.spreadsheets().values().get(ID, LEXIQUE)
 | 
					                val request = service.spreadsheets().values().get(Sheet.ID, Sheet.LEXIQUE)
 | 
				
			||||||
                val data = request.execute()
 | 
					                val data = request.execute()
 | 
				
			||||||
                updateData(data = data)
 | 
					                updateData(data = data)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -55,53 +54,98 @@ class LexiconRepository @Inject constructor(
 | 
				
			||||||
    @Throws(IncompatibleSheetStructure::class)
 | 
					    @Throws(IncompatibleSheetStructure::class)
 | 
				
			||||||
    private fun updateData(data: ValueRange?) {
 | 
					    private fun updateData(data: ValueRange?) {
 | 
				
			||||||
        val sheet = data?.values?.sheet()
 | 
					        val sheet = data?.values?.sheet()
 | 
				
			||||||
 | 
					        var sheetStructure: Map<String, Int>? = null
 | 
				
			||||||
        val lexicon: List<Lexicon> = sheet?.mapIndexedNotNull { index, row ->
 | 
					        val lexicon: List<Lexicon> = sheet?.mapIndexedNotNull { index, row ->
 | 
				
			||||||
            if (index == 0) {
 | 
					            when {
 | 
				
			||||||
                checkSheetStructure(firstRow = row)
 | 
					                index == 0 -> {
 | 
				
			||||||
 | 
					                    sheetStructure = checkSheetStructure(firstRow = row)
 | 
				
			||||||
                    null
 | 
					                    null
 | 
				
			||||||
            } else {
 | 
					                }
 | 
				
			||||||
                parseCharacterRow(index = index - 1, row = row as? List<Any>?)
 | 
					
 | 
				
			||||||
 | 
					                row is List<*> -> parseCharacterRow(
 | 
				
			||||||
 | 
					                    sheetStructure = sheetStructure,
 | 
				
			||||||
 | 
					                    index = index - 1,
 | 
				
			||||||
 | 
					                    row = row,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                else -> null
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } ?: emptyList()
 | 
					        } ?: emptyList()
 | 
				
			||||||
        _data.tryEmit(lexicon)
 | 
					        _data.tryEmit(lexicon)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Throws(IncompatibleSheetStructure::class)
 | 
					    @Throws(IncompatibleSheetStructure::class)
 | 
				
			||||||
    private fun checkSheetStructure(firstRow: Any?) {
 | 
					    private fun checkSheetStructure(firstRow: Any?): HashMap<String, Int> {
 | 
				
			||||||
 | 
					        // check if the row is a list
 | 
				
			||||||
 | 
					        if (firstRow !is ArrayList<*>) {
 | 
				
			||||||
 | 
					            throw IncompatibleSheetStructure("First row is not a List: $firstRow")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // parse the first line to find element that we recognize.
 | 
				
			||||||
 | 
					        val sheetStructure = hashMapOf<String, Int>()
 | 
				
			||||||
 | 
					        firstRow.forEachIndexed { index, cell ->
 | 
				
			||||||
 | 
					            when (cell as? String) {
 | 
				
			||||||
 | 
					                Sheet.NAME,
 | 
				
			||||||
 | 
					                Sheet.DIMINUTIVE,
 | 
				
			||||||
 | 
					                Sheet.GENDER,
 | 
				
			||||||
 | 
					                Sheet.RACE,
 | 
				
			||||||
 | 
					                Sheet.PORTRAIT,
 | 
				
			||||||
 | 
					                Sheet.DESCRIPTION,
 | 
				
			||||||
 | 
					                Sheet.HISTORY -> sheetStructure[cell] = index
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // check if we found everything we need.
 | 
				
			||||||
        when {
 | 
					        when {
 | 
				
			||||||
            firstRow !is ArrayList<*> -> throw IncompatibleSheetStructure("First row is not a List: $firstRow")
 | 
					            sheetStructure.size < Sheet.KNOWN_COLUMN -> throw IncompatibleSheetStructure(
 | 
				
			||||||
            firstRow.size < 7 -> throw IncompatibleSheetStructure("First row have not enough column: ${firstRow.size}, $firstRow")
 | 
					                message = "Sheet header row does not have enough column: ${firstRow.size}.\nstructure: $firstRow\nheader: $sheetStructure"
 | 
				
			||||||
            firstRow.size > 7 -> throw IncompatibleSheetStructure("First row have too mush columns: ${firstRow.size}, $firstRow")
 | 
					            )
 | 
				
			||||||
            else -> {
 | 
					
 | 
				
			||||||
                for (index in 0..6) {
 | 
					            sheetStructure.size > Sheet.KNOWN_COLUMN -> throw IncompatibleSheetStructure(
 | 
				
			||||||
                    if (columns[index] != firstRow[index]) {
 | 
					                message = "Sheet header row does have too mush columns: ${firstRow.size}.\nstructure: $firstRow\nheader: $sheetStructure"
 | 
				
			||||||
                        throw IncompatibleSheetStructure("Column at index:$index should be ${columns[index]} but was ${firstRow[index]}")
 | 
					            )
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun parseCharacterRow(index: Int, row: List<Any>?): Lexicon? {
 | 
					        return sheetStructure
 | 
				
			||||||
        val name = row?.getOrNull(0) as? String
 | 
					    }
 | 
				
			||||||
        val diminutive = row?.getOrNull(1) as? String?
 | 
					
 | 
				
			||||||
        val gender = row?.getOrNull(2) as? String?
 | 
					    private fun parseCharacterRow(
 | 
				
			||||||
        val race = row?.getOrNull(3) as? String?
 | 
					        sheetStructure: Map<String, Int>?,
 | 
				
			||||||
        val portrait = row?.getOrNull(4) as? String?
 | 
					        index: Int,
 | 
				
			||||||
        val description = row?.getOrNull(5) as? String?
 | 
					        row: List<*>?,
 | 
				
			||||||
        val history = row?.getOrNull(6) as? String?
 | 
					    ): 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?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return if (name != null) {
 | 
					        return if (name != null) {
 | 
				
			||||||
            Lexicon(
 | 
					            Lexicon(
 | 
				
			||||||
                id = index,
 | 
					                id = index,
 | 
				
			||||||
                name = name,
 | 
					                name = name,
 | 
				
			||||||
                diminutive = diminutive?.takeIf { it.isNotBlank() },
 | 
					                diminutive = diminutive?.takeIf { it.isNotBlank() },
 | 
				
			||||||
                gender = when (gender) {
 | 
					                gender = when (gender?.takeIf { it.isNotBlank() }) {
 | 
				
			||||||
                    "Male" -> Lexicon.Gender.MALE
 | 
					                    Gender.MALE -> Lexicon.Gender.MALE
 | 
				
			||||||
                    "Femelle" -> Lexicon.Gender.FEMALE
 | 
					                    Gender.FEMALE -> Lexicon.Gender.FEMALE
 | 
				
			||||||
                    else -> Lexicon.Gender.UNDETERMINED
 | 
					                    else -> Lexicon.Gender.UNDETERMINED
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                race = race?.takeIf { it.isNotBlank() },
 | 
					                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(),
 | 
					                portrait = portrait?.split("\n")?.mapNotNull { it.toUriOrNull() } ?: emptyList(),
 | 
				
			||||||
                description = description?.takeIf { it.isNotBlank() },
 | 
					                description = description?.takeIf { it.isNotBlank() },
 | 
				
			||||||
                history = history?.takeIf { it.isNotBlank() },
 | 
					                history = history?.takeIf { it.isNotBlank() },
 | 
				
			||||||
| 
						 | 
					@ -124,16 +168,53 @@ class LexiconRepository @Inject constructor(
 | 
				
			||||||
        null
 | 
					        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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class ServiceNotReady : Exception()
 | 
					    class ServiceNotReady : Exception()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class IncompatibleSheetStructure(message: String?) : Exception(message)
 | 
					    class IncompatibleSheetStructure(message: String?) : Exception(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
        const val TAG = "LexiconRepository"
 | 
					        const val TAG = "LexiconRepository"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private object Sheet {
 | 
				
			||||||
        const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
 | 
					        const val ID = "1oL9Nu5y37BPEbKxHre4TN9o8nrgy2JQoON4RRkdAHMs"
 | 
				
			||||||
        const val LEXIQUE = "Lexique"
 | 
					        const val LEXIQUE = "Lexique"
 | 
				
			||||||
 | 
					        const val KNOWN_COLUMN = 7
 | 
				
			||||||
 | 
					        const val NAME = "Nom"
 | 
				
			||||||
 | 
					        const val DIMINUTIVE = "Diminutif"
 | 
				
			||||||
 | 
					        const val GENDER = "Sexe"
 | 
				
			||||||
 | 
					        const val RACE = "Race"
 | 
				
			||||||
 | 
					        const val PORTRAIT = "Portrait"
 | 
				
			||||||
 | 
					        const val DESCRIPTION = "Description"
 | 
				
			||||||
 | 
					        const val HISTORY = "Histoire"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        val columns =
 | 
					    private object Gender {
 | 
				
			||||||
            listOf("Nom", "Diminutif", "Sexe", "Race", "Portrait", "Description", "Histoire")
 | 
					        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"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2,11 +2,13 @@ package com.pixelized.rplexicon.ui.screens.authentication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
				
			||||||
 | 
					import androidx.compose.foundation.border
 | 
				
			||||||
import androidx.compose.foundation.layout.Arrangement
 | 
					import androidx.compose.foundation.layout.Arrangement
 | 
				
			||||||
import androidx.compose.foundation.layout.Column
 | 
					import androidx.compose.foundation.layout.Column
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
					import androidx.compose.foundation.layout.fillMaxSize
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
					import androidx.compose.foundation.layout.fillMaxWidth
 | 
				
			||||||
import androidx.compose.foundation.layout.padding
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
 | 
					import androidx.compose.foundation.shape.CircleShape
 | 
				
			||||||
import androidx.compose.material3.Button
 | 
					import androidx.compose.material3.Button
 | 
				
			||||||
import androidx.compose.material3.ButtonDefaults
 | 
					import androidx.compose.material3.ButtonDefaults
 | 
				
			||||||
import androidx.compose.material3.LocalTextStyle
 | 
					import androidx.compose.material3.LocalTextStyle
 | 
				
			||||||
| 
						 | 
					@ -22,6 +24,7 @@ import androidx.compose.ui.Modifier
 | 
				
			||||||
import androidx.compose.ui.res.stringResource
 | 
					import androidx.compose.ui.res.stringResource
 | 
				
			||||||
import androidx.compose.ui.text.AnnotatedString
 | 
					import androidx.compose.ui.text.AnnotatedString
 | 
				
			||||||
import androidx.compose.ui.text.buildAnnotatedString
 | 
					import androidx.compose.ui.text.buildAnnotatedString
 | 
				
			||||||
 | 
					import androidx.compose.ui.text.font.FontStyle
 | 
				
			||||||
import androidx.compose.ui.text.font.FontWeight
 | 
					import androidx.compose.ui.text.font.FontWeight
 | 
				
			||||||
import androidx.compose.ui.text.withStyle
 | 
					import androidx.compose.ui.text.withStyle
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
| 
						 | 
					@ -81,23 +84,28 @@ private fun AuthenticationScreenContent(
 | 
				
			||||||
    version: VersionViewModel.Version,
 | 
					    version: VersionViewModel.Version,
 | 
				
			||||||
    onGoogleSignIn: () -> Unit,
 | 
					    onGoogleSignIn: () -> Unit,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
					    val typography = MaterialTheme.typography
 | 
				
			||||||
    Column(
 | 
					    Column(
 | 
				
			||||||
        modifier = modifier,
 | 
					        modifier = modifier,
 | 
				
			||||||
        verticalArrangement = Arrangement.spacedBy(4.dp, Alignment.Bottom),
 | 
					        verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Bottom),
 | 
				
			||||||
        horizontalAlignment = Alignment.End,
 | 
					        horizontalAlignment = Alignment.End,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        Button(
 | 
					        Button(
 | 
				
			||||||
            modifier = Modifier
 | 
					            modifier = Modifier
 | 
				
			||||||
                .padding(all = 16.dp)
 | 
					                .fillMaxWidth()
 | 
				
			||||||
                .fillMaxWidth(),
 | 
					                .border(
 | 
				
			||||||
            colors = ButtonDefaults.buttonColors(),
 | 
					                    width = 2.dp,
 | 
				
			||||||
 | 
					                    color = MaterialTheme.colorScheme.primary,
 | 
				
			||||||
 | 
					                    shape = CircleShape,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					            colors = ButtonDefaults.outlinedButtonColors(),
 | 
				
			||||||
            onClick = onGoogleSignIn,
 | 
					            onClick = onGoogleSignIn,
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            Text(text = rememeberGoogleStringResource())
 | 
					            Text(text = rememeberGoogleStringResource())
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Text(
 | 
					        Text(
 | 
				
			||||||
            style = MaterialTheme.typography.labelSmall,
 | 
					            style = remember { typography.labelSmall.copy(fontStyle = FontStyle.Italic) },
 | 
				
			||||||
            text = version.toText(),
 | 
					            text = version.toText(),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ package com.pixelized.rplexicon.ui.screens.detail
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
				
			||||||
import android.net.Uri
 | 
					import android.net.Uri
 | 
				
			||||||
 | 
					import androidx.annotation.StringRes
 | 
				
			||||||
import androidx.compose.foundation.ScrollState
 | 
					import androidx.compose.foundation.ScrollState
 | 
				
			||||||
import androidx.compose.foundation.background
 | 
					import androidx.compose.foundation.background
 | 
				
			||||||
import androidx.compose.foundation.clickable
 | 
					import androidx.compose.foundation.clickable
 | 
				
			||||||
| 
						 | 
					@ -48,6 +49,7 @@ import androidx.compose.ui.graphics.Shadow
 | 
				
			||||||
import androidx.compose.ui.layout.ContentScale
 | 
					import androidx.compose.ui.layout.ContentScale
 | 
				
			||||||
import androidx.compose.ui.platform.LocalDensity
 | 
					import androidx.compose.ui.platform.LocalDensity
 | 
				
			||||||
import androidx.compose.ui.res.painterResource
 | 
					import androidx.compose.ui.res.painterResource
 | 
				
			||||||
 | 
					import androidx.compose.ui.res.stringResource
 | 
				
			||||||
import androidx.compose.ui.text.font.FontStyle
 | 
					import androidx.compose.ui.text.font.FontStyle
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
import androidx.compose.ui.unit.Dp
 | 
					import androidx.compose.ui.unit.Dp
 | 
				
			||||||
| 
						 | 
					@ -63,8 +65,8 @@ import com.skydoves.landscapist.glide.GlideImage
 | 
				
			||||||
data class CharacterDetailUio(
 | 
					data class CharacterDetailUio(
 | 
				
			||||||
    val name: String?,
 | 
					    val name: String?,
 | 
				
			||||||
    val diminutive: String?,
 | 
					    val diminutive: String?,
 | 
				
			||||||
    val gender: String?,
 | 
					    @StringRes val gender: Int,
 | 
				
			||||||
    val race: String?,
 | 
					    @StringRes val race: Int,
 | 
				
			||||||
    val portrait: List<Uri>,
 | 
					    val portrait: List<Uri>,
 | 
				
			||||||
    val description: String?,
 | 
					    val description: String?,
 | 
				
			||||||
    val history: String?,
 | 
					    val history: String?,
 | 
				
			||||||
| 
						 | 
					@ -111,7 +113,9 @@ private fun CharacterDetailScreenContent(
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                title = { },
 | 
					                title = {
 | 
				
			||||||
 | 
					                    Text(text = stringResource(id = R.string.detail_title))
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ) { paddingValues ->
 | 
					    ) { paddingValues ->
 | 
				
			||||||
| 
						 | 
					@ -175,24 +179,20 @@ private fun CharacterDetailScreenContent(
 | 
				
			||||||
                    modifier = Modifier.padding(horizontal = 16.dp),
 | 
					                    modifier = Modifier.padding(horizontal = 16.dp),
 | 
				
			||||||
                    horizontalArrangement = Arrangement.spacedBy(4.dp)
 | 
					                    horizontalArrangement = Arrangement.spacedBy(4.dp)
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
                    item.value.gender?.let {
 | 
					 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
					                        style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
				
			||||||
                            text = it,
 | 
					                        text = stringResource(id = item.value.gender),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    item.value.race?.let {
 | 
					 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
					                        style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
				
			||||||
                            text = it,
 | 
					                        text = stringResource(id = item.value.race),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                item.value.description?.let {
 | 
					                item.value.description?.let {
 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
 | 
					                        modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
 | 
				
			||||||
                        style = typography.titleMedium,
 | 
					                        style = typography.titleMedium,
 | 
				
			||||||
                        text = "Description",
 | 
					                        text = stringResource(id = R.string.detail_description),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        modifier = Modifier.padding(horizontal = 16.dp),
 | 
					                        modifier = Modifier.padding(horizontal = 16.dp),
 | 
				
			||||||
| 
						 | 
					@ -211,7 +211,7 @@ private fun CharacterDetailScreenContent(
 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
 | 
					                        modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
 | 
				
			||||||
                        style = typography.titleMedium,
 | 
					                        style = typography.titleMedium,
 | 
				
			||||||
                        text = "Histoire",
 | 
					                        text = stringResource(id = R.string.detail_history),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        modifier = Modifier.padding(horizontal = 16.dp),
 | 
					                        modifier = Modifier.padding(horizontal = 16.dp),
 | 
				
			||||||
| 
						 | 
					@ -223,7 +223,7 @@ private fun CharacterDetailScreenContent(
 | 
				
			||||||
                    Text(
 | 
					                    Text(
 | 
				
			||||||
                        modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
 | 
					                        modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
 | 
				
			||||||
                        style = typography.titleMedium,
 | 
					                        style = typography.titleMedium,
 | 
				
			||||||
                        text = "Portrait",
 | 
					                        text = stringResource(id = R.string.detail_portrait),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                    LazyRow(
 | 
					                    LazyRow(
 | 
				
			||||||
                        contentPadding = PaddingValues(horizontal = 16.dp),
 | 
					                        contentPadding = PaddingValues(horizontal = 16.dp),
 | 
				
			||||||
| 
						 | 
					@ -281,8 +281,8 @@ private fun CharacterDetailScreenContentPreview() {
 | 
				
			||||||
                    CharacterDetailUio(
 | 
					                    CharacterDetailUio(
 | 
				
			||||||
                        name = "Brulkhai",
 | 
					                        name = "Brulkhai",
 | 
				
			||||||
                        diminutive = "./ Bru",
 | 
					                        diminutive = "./ Bru",
 | 
				
			||||||
                        gender = "female",
 | 
					                        gender = R.string.gender_female,
 | 
				
			||||||
                        race = "Demi-Orc",
 | 
					                        race = R.string.race_half_orc,
 | 
				
			||||||
                        portrait = listOf(
 | 
					                        portrait = listOf(
 | 
				
			||||||
                            Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/889/large/bayard-wu-0716.jpg?1468642855"),
 | 
					                            Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/889/large/bayard-wu-0716.jpg?1468642855"),
 | 
				
			||||||
                            Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/877/large/bayard-wu-0714.jpg?1468642665"),
 | 
					                            Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/877/large/bayard-wu-0714.jpg?1468642665"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
import androidx.lifecycle.SavedStateHandle
 | 
					import androidx.lifecycle.SavedStateHandle
 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.model.Lexicon
 | 
					import com.pixelized.rplexicon.model.Lexicon
 | 
				
			||||||
import com.pixelized.rplexicon.repository.LexiconRepository
 | 
					import com.pixelized.rplexicon.repository.LexiconRepository
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.characterDetailArgument
 | 
					import com.pixelized.rplexicon.ui.navigation.screens.characterDetailArgument
 | 
				
			||||||
| 
						 | 
					@ -23,11 +24,26 @@ class CharacterDetailViewModel @Inject constructor(
 | 
				
			||||||
            name = source.name,
 | 
					            name = source.name,
 | 
				
			||||||
            diminutive = source.diminutive?.let { "./ $it" },
 | 
					            diminutive = source.diminutive?.let { "./ $it" },
 | 
				
			||||||
            gender = when (source.gender) {
 | 
					            gender = when (source.gender) {
 | 
				
			||||||
                Lexicon.Gender.MALE -> "Male"
 | 
					                Lexicon.Gender.MALE -> R.string.gender_male
 | 
				
			||||||
                Lexicon.Gender.FEMALE -> "Femelle"
 | 
					                Lexicon.Gender.FEMALE -> R.string.gender_female
 | 
				
			||||||
                Lexicon.Gender.UNDETERMINED -> "Inconnu"
 | 
					                Lexicon.Gender.UNDETERMINED -> R.string.gender_undetermined
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            race = when (source.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
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            race = source.race,
 | 
					 | 
				
			||||||
            portrait = source.portrait,
 | 
					            portrait = source.portrait,
 | 
				
			||||||
            description = source.description,
 | 
					            description = source.description,
 | 
				
			||||||
            history = source.history,
 | 
					            history = source.history,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,14 @@ package com.pixelized.rplexicon.ui.screens.lexicon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
				
			||||||
 | 
					import androidx.annotation.StringRes
 | 
				
			||||||
import androidx.compose.foundation.layout.Arrangement
 | 
					import androidx.compose.foundation.layout.Arrangement
 | 
				
			||||||
import androidx.compose.foundation.layout.Column
 | 
					import androidx.compose.foundation.layout.Column
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.ExperimentalLayoutApi
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.FlowRow
 | 
				
			||||||
import androidx.compose.foundation.layout.Row
 | 
					import androidx.compose.foundation.layout.Row
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
					import androidx.compose.foundation.layout.fillMaxWidth
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
import androidx.compose.material3.MaterialTheme
 | 
					import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
import androidx.compose.material3.Surface
 | 
					import androidx.compose.material3.Surface
 | 
				
			||||||
import androidx.compose.material3.Text
 | 
					import androidx.compose.material3.Text
 | 
				
			||||||
| 
						 | 
					@ -13,21 +17,37 @@ import androidx.compose.runtime.Composable
 | 
				
			||||||
import androidx.compose.runtime.Stable
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
import androidx.compose.runtime.remember
 | 
					import androidx.compose.runtime.remember
 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
 | 
					import androidx.compose.ui.res.stringResource
 | 
				
			||||||
import androidx.compose.ui.text.font.FontStyle
 | 
					import androidx.compose.ui.text.font.FontStyle
 | 
				
			||||||
import androidx.compose.ui.text.font.FontWeight
 | 
					import androidx.compose.ui.text.font.FontWeight
 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
import androidx.compose.ui.unit.dp
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
					import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.utilitary.extentions.placeholder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Stable
 | 
					@Stable
 | 
				
			||||||
data class LexiconItemUio(
 | 
					data class LexiconItemUio(
 | 
				
			||||||
    val id: Int,
 | 
					    val id: Int,
 | 
				
			||||||
    val name: String,
 | 
					    val name: String,
 | 
				
			||||||
    val diminutive: String?,
 | 
					    val diminutive: String?,
 | 
				
			||||||
    val gender: String?,
 | 
					    @StringRes val gender: Int,
 | 
				
			||||||
    val race: String?,
 | 
					    @StringRes val race: Int,
 | 
				
			||||||
 | 
					    val placeholder: Boolean = false,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        val Brulkhai = LexiconItemUio(
 | 
				
			||||||
 | 
					            id = 0,
 | 
				
			||||||
 | 
					            name = "Brulkhai",
 | 
				
			||||||
 | 
					            diminutive = "Bru",
 | 
				
			||||||
 | 
					            gender = R.string.gender_female_short,
 | 
				
			||||||
 | 
					            race = R.string.race_half_orc,
 | 
				
			||||||
 | 
					            placeholder = true,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@OptIn(ExperimentalLayoutApi::class)
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun LexiconItem(
 | 
					fun LexiconItem(
 | 
				
			||||||
    modifier: Modifier = Modifier,
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
| 
						 | 
					@ -39,17 +59,23 @@ fun LexiconItem(
 | 
				
			||||||
        modifier = modifier,
 | 
					        modifier = modifier,
 | 
				
			||||||
        verticalArrangement = Arrangement.spacedBy(4.dp),
 | 
					        verticalArrangement = Arrangement.spacedBy(4.dp),
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        Row(
 | 
					        FlowRow(
 | 
				
			||||||
            horizontalArrangement = Arrangement.spacedBy(4.dp),
 | 
					            horizontalArrangement = Arrangement.spacedBy(4.dp),
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
                modifier = Modifier.alignByBaseline(),
 | 
					                modifier = Modifier
 | 
				
			||||||
 | 
					                    .alignByBaseline()
 | 
				
			||||||
 | 
					                    .placeholder { item.placeholder },
 | 
				
			||||||
                style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
 | 
					                style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
 | 
				
			||||||
 | 
					                maxLines = 1,
 | 
				
			||||||
                text = item.name,
 | 
					                text = item.name,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
                modifier = Modifier.alignByBaseline(),
 | 
					                modifier = Modifier
 | 
				
			||||||
 | 
					                    .alignByBaseline()
 | 
				
			||||||
 | 
					                    .placeholder { item.placeholder },
 | 
				
			||||||
                style = typography.labelMedium,
 | 
					                style = typography.labelMedium,
 | 
				
			||||||
 | 
					                maxLines = 1,
 | 
				
			||||||
                text = item.diminutive ?: ""
 | 
					                text = item.diminutive ?: ""
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -57,12 +83,20 @@ fun LexiconItem(
 | 
				
			||||||
            horizontalArrangement = Arrangement.spacedBy(4.dp),
 | 
					            horizontalArrangement = Arrangement.spacedBy(4.dp),
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
 | 
					                modifier = Modifier
 | 
				
			||||||
 | 
					                    .alignByBaseline()
 | 
				
			||||||
 | 
					                    .placeholder { item.placeholder },
 | 
				
			||||||
                style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
					                style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
				
			||||||
                text = item.gender ?: ""
 | 
					                maxLines = 1,
 | 
				
			||||||
 | 
					                text = stringResource(id = item.gender)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
 | 
					                modifier = Modifier
 | 
				
			||||||
 | 
					                    .alignByBaseline()
 | 
				
			||||||
 | 
					                    .placeholder { item.placeholder },
 | 
				
			||||||
                style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
					                style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
 | 
				
			||||||
                text = item.race ?: ""
 | 
					                maxLines = 1,
 | 
				
			||||||
 | 
					                text = stringResource(id = item.race)
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -76,13 +110,15 @@ private fun LexiconItemContentPreview() {
 | 
				
			||||||
    LexiconTheme {
 | 
					    LexiconTheme {
 | 
				
			||||||
        Surface {
 | 
					        Surface {
 | 
				
			||||||
            LexiconItem(
 | 
					            LexiconItem(
 | 
				
			||||||
                modifier = Modifier.fillMaxWidth(),
 | 
					                modifier = Modifier
 | 
				
			||||||
 | 
					                    .fillMaxWidth()
 | 
				
			||||||
 | 
					                    .padding(vertical = 8.dp, horizontal = 16.dp),
 | 
				
			||||||
                item = LexiconItemUio(
 | 
					                item = LexiconItemUio(
 | 
				
			||||||
                    id = 0,
 | 
					                    id = 0,
 | 
				
			||||||
                    name = "Brulkhai",
 | 
					                    name = "Brulkhai",
 | 
				
			||||||
                    diminutive = "Bru",
 | 
					                    diminutive = "Bru",
 | 
				
			||||||
                    gender = "f.",
 | 
					                    gender = R.string.gender_female_short,
 | 
				
			||||||
                    race = "Demi-Orc",
 | 
					                    race = R.string.race_half_orc,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,8 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
				
			||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
					import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
				
			||||||
import androidx.activity.compose.rememberLauncherForActivityResult
 | 
					import androidx.activity.compose.rememberLauncherForActivityResult
 | 
				
			||||||
import androidx.activity.result.contract.ActivityResultContracts
 | 
					import androidx.activity.result.contract.ActivityResultContracts
 | 
				
			||||||
 | 
					import androidx.compose.animation.AnimatedContent
 | 
				
			||||||
 | 
					import androidx.compose.foundation.ExperimentalFoundationApi
 | 
				
			||||||
import androidx.compose.foundation.clickable
 | 
					import androidx.compose.foundation.clickable
 | 
				
			||||||
import androidx.compose.foundation.layout.Box
 | 
					import androidx.compose.foundation.layout.Box
 | 
				
			||||||
import androidx.compose.foundation.layout.PaddingValues
 | 
					import androidx.compose.foundation.layout.PaddingValues
 | 
				
			||||||
| 
						 | 
					@ -16,14 +18,14 @@ import androidx.compose.foundation.lazy.LazyColumn
 | 
				
			||||||
import androidx.compose.foundation.lazy.LazyListState
 | 
					import androidx.compose.foundation.lazy.LazyListState
 | 
				
			||||||
import androidx.compose.foundation.lazy.items
 | 
					import androidx.compose.foundation.lazy.items
 | 
				
			||||||
import androidx.compose.foundation.lazy.rememberLazyListState
 | 
					import androidx.compose.foundation.lazy.rememberLazyListState
 | 
				
			||||||
 | 
					import androidx.compose.foundation.shape.CircleShape
 | 
				
			||||||
import androidx.compose.material.ExperimentalMaterialApi
 | 
					import androidx.compose.material.ExperimentalMaterialApi
 | 
				
			||||||
 | 
					import androidx.compose.material.LinearProgressIndicator
 | 
				
			||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
 | 
					import androidx.compose.material.pullrefresh.PullRefreshIndicator
 | 
				
			||||||
import androidx.compose.material.pullrefresh.PullRefreshState
 | 
					import androidx.compose.material.pullrefresh.PullRefreshState
 | 
				
			||||||
import androidx.compose.material.pullrefresh.pullRefresh
 | 
					import androidx.compose.material.pullrefresh.pullRefresh
 | 
				
			||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
 | 
					import androidx.compose.material.pullrefresh.rememberPullRefreshState
 | 
				
			||||||
import androidx.compose.material3.ExperimentalMaterial3Api
 | 
					import androidx.compose.material3.ExperimentalMaterial3Api
 | 
				
			||||||
import androidx.compose.material3.Icon
 | 
					 | 
				
			||||||
import androidx.compose.material3.MaterialTheme
 | 
					 | 
				
			||||||
import androidx.compose.material3.Scaffold
 | 
					import androidx.compose.material3.Scaffold
 | 
				
			||||||
import androidx.compose.material3.Surface
 | 
					import androidx.compose.material3.Surface
 | 
				
			||||||
import androidx.compose.material3.Text
 | 
					import androidx.compose.material3.Text
 | 
				
			||||||
| 
						 | 
					@ -38,20 +40,20 @@ import androidx.compose.runtime.remember
 | 
				
			||||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
					import androidx.compose.runtime.rememberCoroutineScope
 | 
				
			||||||
import androidx.compose.ui.Alignment
 | 
					import androidx.compose.ui.Alignment
 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
import androidx.compose.ui.res.painterResource
 | 
					import androidx.compose.ui.draw.clip
 | 
				
			||||||
 | 
					import androidx.compose.ui.platform.LocalContext
 | 
				
			||||||
import androidx.compose.ui.res.stringResource
 | 
					import androidx.compose.ui.res.stringResource
 | 
				
			||||||
import androidx.compose.ui.text.font.FontWeight
 | 
					 | 
				
			||||||
import androidx.compose.ui.tooling.preview.Preview
 | 
					import androidx.compose.ui.tooling.preview.Preview
 | 
				
			||||||
import androidx.compose.ui.unit.dp
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
import androidx.hilt.navigation.compose.hiltViewModel
 | 
					import androidx.hilt.navigation.compose.hiltViewModel
 | 
				
			||||||
import com.pixelized.rplexicon.LocalSnack
 | 
					import com.pixelized.rplexicon.LocalSnack
 | 
				
			||||||
import com.pixelized.rplexicon.R
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.ui.composable.FloatingActionButton
 | 
					 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
					import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterDetail
 | 
					import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterDetail
 | 
				
			||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch
 | 
					import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Default
 | 
					import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Default
 | 
				
			||||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Permission
 | 
					import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Permission
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Structure
 | 
				
			||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
					import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
				
			||||||
import kotlinx.coroutines.flow.SharedFlow
 | 
					import kotlinx.coroutines.flow.SharedFlow
 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
| 
						 | 
					@ -61,6 +63,9 @@ sealed class LexiconErrorUio {
 | 
				
			||||||
    @Stable
 | 
					    @Stable
 | 
				
			||||||
    data class Permission(val intent: Intent) : LexiconErrorUio()
 | 
					    data class Permission(val intent: Intent) : LexiconErrorUio()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Stable
 | 
				
			||||||
 | 
					    object Structure : LexiconErrorUio()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Stable
 | 
					    @Stable
 | 
				
			||||||
    object Default : LexiconErrorUio()
 | 
					    object Default : LexiconErrorUio()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -75,7 +80,7 @@ fun LexiconScreen(
 | 
				
			||||||
    val screen = LocalScreenNavHost.current
 | 
					    val screen = LocalScreenNavHost.current
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val refresh = rememberPullRefreshState(
 | 
					    val refresh = rememberPullRefreshState(
 | 
				
			||||||
        refreshing = viewModel.isLoading.value,
 | 
					        refreshing = false,
 | 
				
			||||||
        onRefresh = {
 | 
					        onRefresh = {
 | 
				
			||||||
            scope.launch {
 | 
					            scope.launch {
 | 
				
			||||||
                viewModel.fetchLexicon()
 | 
					                viewModel.fetchLexicon()
 | 
				
			||||||
| 
						 | 
					@ -113,7 +118,9 @@ fun LexiconScreen(
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
 | 
					@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class,
 | 
				
			||||||
 | 
					    ExperimentalFoundationApi::class
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
private fun LexiconScreenContent(
 | 
					private fun LexiconScreenContent(
 | 
				
			||||||
    modifier: Modifier = Modifier,
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
| 
						 | 
					@ -156,9 +163,9 @@ private fun LexiconScreenContent(
 | 
				
			||||||
//                },
 | 
					//                },
 | 
				
			||||||
//            )
 | 
					//            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    ) {
 | 
					    ) { padding ->
 | 
				
			||||||
        Box(
 | 
					        Box(
 | 
				
			||||||
            modifier = Modifier.padding(paddingValues = it),
 | 
					            modifier = Modifier.padding(paddingValues = padding),
 | 
				
			||||||
            contentAlignment = Alignment.TopCenter,
 | 
					            contentAlignment = Alignment.TopCenter,
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            LazyColumn(
 | 
					            LazyColumn(
 | 
				
			||||||
| 
						 | 
					@ -171,30 +178,72 @@ private fun LexiconScreenContent(
 | 
				
			||||||
                    bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
 | 
					                    bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                items(items = items.value) { item ->
 | 
					                if (items.value.isEmpty()) {
 | 
				
			||||||
 | 
					                    items(
 | 
				
			||||||
 | 
					                        count = 6,
 | 
				
			||||||
 | 
					                        key = { it },
 | 
				
			||||||
 | 
					                        contentType = { "Lexicon" },
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
                        LexiconItem(
 | 
					                        LexiconItem(
 | 
				
			||||||
                            modifier = Modifier
 | 
					                            modifier = Modifier
 | 
				
			||||||
                            .clickable { onItem(item) }
 | 
					                                .animateItemPlacement()
 | 
				
			||||||
                                .fillMaxWidth()
 | 
					                                .fillMaxWidth()
 | 
				
			||||||
                                .padding(vertical = 8.dp, horizontal = 16.dp),
 | 
					                                .padding(vertical = 8.dp, horizontal = 16.dp),
 | 
				
			||||||
                        item = item,
 | 
					                            item = LexiconItemUio.Brulkhai,
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    items(
 | 
				
			||||||
 | 
					                        items = items.value,
 | 
				
			||||||
 | 
					                        key = { it.id },
 | 
				
			||||||
 | 
					                        contentType = { "Lexicon" },
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        LexiconItem(
 | 
				
			||||||
 | 
					                            modifier = Modifier
 | 
				
			||||||
 | 
					                                .animateItemPlacement()
 | 
				
			||||||
 | 
					                                .clickable { onItem(it) }
 | 
				
			||||||
 | 
					                                .fillMaxWidth()
 | 
				
			||||||
 | 
					                                .padding(vertical = 8.dp, horizontal = 16.dp),
 | 
				
			||||||
 | 
					                            item = it,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            PullRefreshIndicator(
 | 
					            Loader(
 | 
				
			||||||
                refreshing = refreshing.value,
 | 
					                refreshState = refreshState,
 | 
				
			||||||
                state = refreshState,
 | 
					                refreshing = refreshing,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@OptIn(ExperimentalMaterialApi::class)
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					private fun Loader(
 | 
				
			||||||
 | 
					    refreshState: PullRefreshState,
 | 
				
			||||||
 | 
					    refreshing: State<Boolean>,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    if (refreshing.value) {
 | 
				
			||||||
 | 
					        LinearProgressIndicator(
 | 
				
			||||||
 | 
					            modifier = Modifier
 | 
				
			||||||
 | 
					                .fillMaxWidth()
 | 
				
			||||||
 | 
					                .clip(shape = CircleShape)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PullRefreshIndicator(
 | 
				
			||||||
 | 
					        refreshing = false,
 | 
				
			||||||
 | 
					        state = refreshState,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun HandleError(
 | 
					fun HandleError(
 | 
				
			||||||
    errors: SharedFlow<LexiconErrorUio>,
 | 
					    errors: SharedFlow<LexiconErrorUio>,
 | 
				
			||||||
    onLexiconPermissionGranted: suspend () -> Unit,
 | 
					    onLexiconPermissionGranted: suspend () -> Unit,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
					    val context = LocalContext.current
 | 
				
			||||||
    val snack = LocalSnack.current
 | 
					    val snack = LocalSnack.current
 | 
				
			||||||
    val scope = rememberCoroutineScope()
 | 
					    val scope = rememberCoroutineScope()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -212,7 +261,8 @@ fun HandleError(
 | 
				
			||||||
        errors.collect { error ->
 | 
					        errors.collect { error ->
 | 
				
			||||||
            when (error) {
 | 
					            when (error) {
 | 
				
			||||||
                is Permission -> launcher.launch(error.intent)
 | 
					                is Permission -> launcher.launch(error.intent)
 | 
				
			||||||
                is Default -> snack.showSnackbar(message = "Oops")
 | 
					                is Structure -> snack.showSnackbar(message = context.getString(R.string.error_structure))
 | 
				
			||||||
 | 
					                is Default -> snack.showSnackbar(message = context.getString(R.string.error_generic))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -238,11 +288,11 @@ private fun LexiconScreenContentPreview() {
 | 
				
			||||||
                    mutableStateOf(
 | 
					                    mutableStateOf(
 | 
				
			||||||
                        listOf(
 | 
					                        listOf(
 | 
				
			||||||
                            LexiconItemUio(
 | 
					                            LexiconItemUio(
 | 
				
			||||||
                                id = 2,
 | 
					                                id = 0,
 | 
				
			||||||
                                name = "Brulkhai",
 | 
					                                name = "Brulkhai",
 | 
				
			||||||
                                diminutive = "Bru",
 | 
					                                diminutive = "Bru",
 | 
				
			||||||
                                gender = "f.",
 | 
					                                gender = R.string.gender_female_short,
 | 
				
			||||||
                                race = "Demi-Orc",
 | 
					                                race = R.string.race_half_orc,
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,10 @@ import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
import androidx.lifecycle.viewModelScope
 | 
					import androidx.lifecycle.viewModelScope
 | 
				
			||||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
 | 
					import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.R
 | 
				
			||||||
import com.pixelized.rplexicon.model.Lexicon
 | 
					import com.pixelized.rplexicon.model.Lexicon
 | 
				
			||||||
import com.pixelized.rplexicon.repository.LexiconRepository
 | 
					import com.pixelized.rplexicon.repository.LexiconRepository
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.repository.LexiconRepository.IncompatibleSheetStructure
 | 
				
			||||||
import dagger.hilt.android.lifecycle.HiltViewModel
 | 
					import dagger.hilt.android.lifecycle.HiltViewModel
 | 
				
			||||||
import kotlinx.coroutines.flow.MutableSharedFlow
 | 
					import kotlinx.coroutines.flow.MutableSharedFlow
 | 
				
			||||||
import kotlinx.coroutines.flow.SharedFlow
 | 
					import kotlinx.coroutines.flow.SharedFlow
 | 
				
			||||||
| 
						 | 
					@ -37,11 +39,26 @@ class LexiconViewModel @Inject constructor(
 | 
				
			||||||
                        name = item.name,
 | 
					                        name = item.name,
 | 
				
			||||||
                        diminutive = item.diminutive?.takeIf { it.isNotBlank() }?.let { "./ $it" },
 | 
					                        diminutive = item.diminutive?.takeIf { it.isNotBlank() }?.let { "./ $it" },
 | 
				
			||||||
                        gender = when (item.gender) {
 | 
					                        gender = when (item.gender) {
 | 
				
			||||||
                            Lexicon.Gender.MALE -> "m."
 | 
					                            Lexicon.Gender.MALE -> R.string.gender_male_short
 | 
				
			||||||
                            Lexicon.Gender.FEMALE -> "f."
 | 
					                            Lexicon.Gender.FEMALE -> R.string.gender_female_short
 | 
				
			||||||
                            Lexicon.Gender.UNDETERMINED -> "u."
 | 
					                            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
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
                        race = item.race,
 | 
					 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }.sortedBy { it.name }
 | 
					                }.sortedBy { it.name }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -61,6 +78,9 @@ class LexiconViewModel @Inject constructor(
 | 
				
			||||||
        catch (exception: UserRecoverableAuthIOException) {
 | 
					        catch (exception: UserRecoverableAuthIOException) {
 | 
				
			||||||
            Log.e(TAG, exception.message, exception)
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
            _error.emit(LexiconErrorUio.Permission(intent = exception.intent))
 | 
					            _error.emit(LexiconErrorUio.Permission(intent = exception.intent))
 | 
				
			||||||
 | 
					        } catch (exception: IncompatibleSheetStructure) {
 | 
				
			||||||
 | 
					            Log.e(TAG, exception.message, exception)
 | 
				
			||||||
 | 
					            _error.emit(LexiconErrorUio.Structure)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        // default exception
 | 
					        // default exception
 | 
				
			||||||
        catch (exception: Exception) {
 | 
					        catch (exception: Exception) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,45 @@
 | 
				
			||||||
package com.pixelized.rplexicon.ui.theme
 | 
					package com.pixelized.rplexicon.ui.theme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.material3.ColorScheme
 | 
				
			||||||
 | 
					import androidx.compose.material3.darkColorScheme
 | 
				
			||||||
 | 
					import androidx.compose.material3.lightColorScheme
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Immutable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
import androidx.compose.ui.graphics.Color
 | 
					import androidx.compose.ui.graphics.Color
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.theme.colors.BaseDark
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.theme.colors.BaseLight
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val Purple80 = Color(0xFFD0BCFF)
 | 
					@Stable
 | 
				
			||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
 | 
					@Immutable
 | 
				
			||||||
val Pink80 = Color(0xFFEFB8C8)
 | 
					class LexiconColors(
 | 
				
			||||||
 | 
					    val base: ColorScheme,
 | 
				
			||||||
 | 
					    val placeholder: Color,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val Purple40 = Color(0xFF6650a4)
 | 
					@Stable
 | 
				
			||||||
val PurpleGrey40 = Color(0xFF625b71)
 | 
					fun darkColorScheme(
 | 
				
			||||||
val Pink40 = Color(0xFF7D5260)
 | 
					    base: ColorScheme = darkColorScheme(
 | 
				
			||||||
 | 
					        primary = BaseDark.Purple80,
 | 
				
			||||||
 | 
					        secondary = BaseDark.PurpleGrey80,
 | 
				
			||||||
 | 
					        tertiary = BaseDark.Pink80,
 | 
				
			||||||
 | 
					        onPrimary = Color.White,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    placeholder: Color = Color(red = 49, green = 48, blue = 51),
 | 
				
			||||||
 | 
					) = LexiconColors(
 | 
				
			||||||
 | 
					    base = base,
 | 
				
			||||||
 | 
					    placeholder = placeholder,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Stable
 | 
				
			||||||
 | 
					fun lightColorScheme(
 | 
				
			||||||
 | 
					    base: ColorScheme = lightColorScheme(
 | 
				
			||||||
 | 
					        primary = BaseLight.Purple40,
 | 
				
			||||||
 | 
					        secondary = BaseLight.PurpleGrey40,
 | 
				
			||||||
 | 
					        tertiary = BaseLight.Pink40,
 | 
				
			||||||
 | 
					        onPrimary = Color.White,
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    placeholder: Color = Color(red = 230, green = 225, blue = 229),
 | 
				
			||||||
 | 
					) = LexiconColors(
 | 
				
			||||||
 | 
					    base = base,
 | 
				
			||||||
 | 
					    placeholder = placeholder,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,27 +3,23 @@ package com.pixelized.rplexicon.ui.theme
 | 
				
			||||||
import android.app.Activity
 | 
					import android.app.Activity
 | 
				
			||||||
import androidx.compose.foundation.isSystemInDarkTheme
 | 
					import androidx.compose.foundation.isSystemInDarkTheme
 | 
				
			||||||
import androidx.compose.material3.MaterialTheme
 | 
					import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
import androidx.compose.material3.darkColorScheme
 | 
					 | 
				
			||||||
import androidx.compose.material3.lightColorScheme
 | 
					 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.CompositionLocalProvider
 | 
				
			||||||
import androidx.compose.runtime.SideEffect
 | 
					import androidx.compose.runtime.SideEffect
 | 
				
			||||||
import androidx.compose.ui.graphics.Color
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.compositionLocalOf
 | 
				
			||||||
 | 
					import androidx.compose.runtime.remember
 | 
				
			||||||
import androidx.compose.ui.graphics.toArgb
 | 
					import androidx.compose.ui.graphics.toArgb
 | 
				
			||||||
import androidx.compose.ui.platform.LocalView
 | 
					import androidx.compose.ui.platform.LocalView
 | 
				
			||||||
import androidx.core.view.WindowCompat
 | 
					import androidx.core.view.WindowCompat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private val DarkColorScheme = darkColorScheme(
 | 
					val LocalLexiconTheme = compositionLocalOf<LexiconTheme> {
 | 
				
			||||||
    primary = Purple80,
 | 
					    error("LocalLexiconTheme not ready yet.")
 | 
				
			||||||
    secondary = PurpleGrey80,
 | 
					}
 | 
				
			||||||
    tertiary = Pink80,
 | 
					 | 
				
			||||||
    onPrimary = Color.White,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
private val LightColorScheme = lightColorScheme(
 | 
					@Stable
 | 
				
			||||||
    primary = Purple40,
 | 
					data class LexiconTheme(
 | 
				
			||||||
    secondary = PurpleGrey40,
 | 
					    val colorScheme: LexiconColors,
 | 
				
			||||||
    tertiary = Pink40,
 | 
					 | 
				
			||||||
    onPrimary = Color.White,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
| 
						 | 
					@ -31,16 +27,20 @@ fun LexiconTheme(
 | 
				
			||||||
    darkTheme: Boolean = isSystemInDarkTheme(),
 | 
					    darkTheme: Boolean = isSystemInDarkTheme(),
 | 
				
			||||||
    content: @Composable () -> Unit
 | 
					    content: @Composable () -> Unit
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    val colorScheme = when {
 | 
					    val lexiconTheme = remember {
 | 
				
			||||||
        darkTheme -> DarkColorScheme
 | 
					        LexiconTheme(
 | 
				
			||||||
        else -> LightColorScheme
 | 
					            colorScheme = when (darkTheme) {
 | 
				
			||||||
 | 
					                true -> darkColorScheme()
 | 
				
			||||||
 | 
					                else -> lightColorScheme()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val view = LocalView.current
 | 
					    val view = LocalView.current
 | 
				
			||||||
    if (!view.isInEditMode) {
 | 
					    if (!view.isInEditMode) {
 | 
				
			||||||
        SideEffect {
 | 
					        SideEffect {
 | 
				
			||||||
            val window = (view.context as Activity).window
 | 
					            val window = (view.context as Activity).window
 | 
				
			||||||
            colorScheme.background.toArgb().let {
 | 
					            lexiconTheme.colorScheme.base.background.toArgb().let {
 | 
				
			||||||
                window.statusBarColor = it
 | 
					                window.statusBarColor = it
 | 
				
			||||||
                window.navigationBarColor = it
 | 
					                window.navigationBarColor = it
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -51,9 +51,13 @@ fun LexiconTheme(
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    CompositionLocalProvider(
 | 
				
			||||||
 | 
					        LocalLexiconTheme provides lexiconTheme,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
        MaterialTheme(
 | 
					        MaterialTheme(
 | 
				
			||||||
        colorScheme = colorScheme,
 | 
					            colorScheme = lexiconTheme.colorScheme.base,
 | 
				
			||||||
            typography = Typography,
 | 
					            typography = Typography,
 | 
				
			||||||
            content = content
 | 
					            content = content
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,57 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.ui.theme.colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Immutable
 | 
				
			||||||
 | 
					import androidx.compose.ui.graphics.Color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Immutable
 | 
				
			||||||
 | 
					object BaseDark {
 | 
				
			||||||
 | 
					    val Purple80 = Color(0xFFD0BCFF)
 | 
				
			||||||
 | 
					    val PurpleGrey80 = Color(0xFFCCC2DC)
 | 
				
			||||||
 | 
					    val Pink80 = Color(0xFFEFB8C8)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Immutable
 | 
				
			||||||
 | 
					object BaseLight {
 | 
				
			||||||
 | 
					    val Purple40 = Color(0xFF6650a4)
 | 
				
			||||||
 | 
					    val PurpleGrey40 = Color(0xFF625b71)
 | 
				
			||||||
 | 
					    val Pink40 = Color(0xFF7D5260)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Immutable
 | 
				
			||||||
 | 
					object BaseColorPalette {
 | 
				
			||||||
 | 
					    val VeryDarkBlue: Color = Color(0xFF09179D)
 | 
				
			||||||
 | 
					    val DarkBlue: Color = Color(0xFF1A2BDB)
 | 
				
			||||||
 | 
					    val Blue: Color = Color(0xFF2970F2)
 | 
				
			||||||
 | 
					    val LightBlue: Color = Color(0xFF1A91DB)
 | 
				
			||||||
 | 
					    val VeryLightBlue: Color = Color(0xFF1EDDEF)
 | 
				
			||||||
 | 
					    val VeryDarkPurple: Color = Color(0xFF5F0E9E)
 | 
				
			||||||
 | 
					    val DarkPurple: Color = Color(0xFF8330DB)
 | 
				
			||||||
 | 
					    val Purple: Color = Color(0xFF9B54C3)
 | 
				
			||||||
 | 
					    val LightPurple: Color = Color(0xFFBC52D9)
 | 
				
			||||||
 | 
					    val VeryLightPurple: Color = Color(0xFFC856D1)
 | 
				
			||||||
 | 
					    val VeryDarkGreen: Color = Color(0xFF16544A)
 | 
				
			||||||
 | 
					    val DarkGreen: Color = Color(0xFF207A6B)
 | 
				
			||||||
 | 
					    val Green: Color = Color(0xFF269482)
 | 
				
			||||||
 | 
					    val LightGreen: Color = Color(0xFF2AA18D)
 | 
				
			||||||
 | 
					    val VeryLightGreen: Color = Color(0xFF3AE0C5)
 | 
				
			||||||
 | 
					    val VeryDarkRed: Color = Color(0xFF631221)
 | 
				
			||||||
 | 
					    val DarkRed: Color = Color(0xFFA21D36)
 | 
				
			||||||
 | 
					    val Red: Color = Color(0xFFC92443)
 | 
				
			||||||
 | 
					    val LightRed: Color = Color(0xFFE32849)
 | 
				
			||||||
 | 
					    val VeryLightRed: Color = Color(0xFFF02B4F)
 | 
				
			||||||
 | 
					    val VeryDarkPink: Color = Color(0xFF960064)
 | 
				
			||||||
 | 
					    val DarkPink: Color = Color(0xFFBD007E)
 | 
				
			||||||
 | 
					    val Pink: Color = Color(0xFFD6008F)
 | 
				
			||||||
 | 
					    val LightPink: Color = Color(0xFFE35BB5)
 | 
				
			||||||
 | 
					    val VeryLightPink: Color = Color(0xFFFF66CC)
 | 
				
			||||||
 | 
					    val VeryDarkYellow: Color = Color(0xFFB76036)
 | 
				
			||||||
 | 
					    val DarkYellow: Color = Color(0xFFD48341)
 | 
				
			||||||
 | 
					    val Yellow: Color = Color(0xFFF3A850)
 | 
				
			||||||
 | 
					    val LightYellow: Color = Color(0xFFF5BF63)
 | 
				
			||||||
 | 
					    val VeryLightYellow: Color = Color(0xFFF9D679)
 | 
				
			||||||
 | 
					    val VeryDarkGrey: Color = Color(0xFF1D1D1D)
 | 
				
			||||||
 | 
					    val DarkGrey: Color = Color(0xFF424242)
 | 
				
			||||||
 | 
					    val Grey: Color = Color(0xFF919195)
 | 
				
			||||||
 | 
					    val LightGrey: Color = Color(0xFFDFDFDF)
 | 
				
			||||||
 | 
					    val VeryLightGrey: Color = Color(0xFFF9F9F9)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.ui.theme.colors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Immutable
 | 
				
			||||||
 | 
					import androidx.compose.ui.graphics.Color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Immutable
 | 
				
			||||||
 | 
					object ShadowPalette {
 | 
				
			||||||
 | 
					    val system: Color = Color.Black.copy(alpha = 0.37f)
 | 
				
			||||||
 | 
					    val scrim: Color = Color.Black.copy(alpha = 0.37f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,12 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.utilitary.extentions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
				
			||||||
 | 
					import com.pixelized.rplexicon.ui.theme.LocalLexiconTheme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val MaterialTheme.lexicon: LexiconTheme
 | 
				
			||||||
 | 
					    @Composable
 | 
				
			||||||
 | 
					    @Stable
 | 
				
			||||||
 | 
					    get() = LocalLexiconTheme.current
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,30 @@
 | 
				
			||||||
 | 
					package com.pixelized.rplexicon.utilitary.extentions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.animation.core.FiniteAnimationSpec
 | 
				
			||||||
 | 
					import androidx.compose.animation.core.Transition
 | 
				
			||||||
 | 
					import androidx.compose.animation.core.spring
 | 
				
			||||||
 | 
					import androidx.compose.foundation.shape.CircleShape
 | 
				
			||||||
 | 
					import androidx.compose.material3.MaterialTheme
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
 | 
					import androidx.compose.ui.graphics.Color
 | 
				
			||||||
 | 
					import androidx.compose.ui.graphics.Shape
 | 
				
			||||||
 | 
					import com.google.accompanist.placeholder.PlaceholderHighlight
 | 
				
			||||||
 | 
					import com.google.accompanist.placeholder.placeholder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					fun Modifier.placeholder(
 | 
				
			||||||
 | 
					    color: Color = MaterialTheme.lexicon.colorScheme.placeholder,
 | 
				
			||||||
 | 
					    shape: Shape = CircleShape,
 | 
				
			||||||
 | 
					    highlight: PlaceholderHighlight? = null,
 | 
				
			||||||
 | 
					    placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
 | 
				
			||||||
 | 
					    contentFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
 | 
				
			||||||
 | 
					    visible: () -> Boolean,
 | 
				
			||||||
 | 
					): Modifier = placeholder(
 | 
				
			||||||
 | 
					    visible = visible(),
 | 
				
			||||||
 | 
					    color = color,
 | 
				
			||||||
 | 
					    shape = shape,
 | 
				
			||||||
 | 
					    highlight = highlight,
 | 
				
			||||||
 | 
					    placeholderFadeTransitionSpec = placeholderFadeTransitionSpec,
 | 
				
			||||||
 | 
					    contentFadeTransitionSpec = contentFadeTransitionSpec,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,35 @@
 | 
				
			||||||
<resources>
 | 
					<resources>
 | 
				
			||||||
    <string name="app_name">Lexique</string>
 | 
					    <string name="app_name">Rp-Lexique</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="error_generic">Ah !? y\'a un truc qui foire quelque part.</string>
 | 
				
			||||||
 | 
					    <string name="error_structure">La structure du fichier semble avoir changé et n\'est plus compatible avec cette application.</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="gender_male">Mâle</string>
 | 
				
			||||||
 | 
					    <string name="gender_female">Femelle</string>
 | 
				
			||||||
 | 
					    <string name="gender_undetermined">Indéterminé</string>
 | 
				
			||||||
 | 
					    <string name="gender_male_short">m.</string>
 | 
				
			||||||
 | 
					    <string name="gender_female_short">f.</string>
 | 
				
			||||||
 | 
					    <string name="gender_undetermined_short">i.</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="race_elf">Elfe</string>
 | 
				
			||||||
 | 
					    <string name="race_halfling">Halfelin</string>
 | 
				
			||||||
 | 
					    <string name="race_human">Humain</string>
 | 
				
			||||||
 | 
					    <string name="race_dwarf">Nain</string>
 | 
				
			||||||
 | 
					    <string name="race_half_elf">Demi-Elfe</string>
 | 
				
			||||||
 | 
					    <string name="race_half_orc">Demi-Orc</string>
 | 
				
			||||||
 | 
					    <string name="race_dragonborn">Drakéide</string>
 | 
				
			||||||
 | 
					    <string name="race_gnome">Gnome</string>
 | 
				
			||||||
 | 
					    <string name="race_tiefling">Tieffelin</string>
 | 
				
			||||||
 | 
					    <string name="race_aarakocra">Aarakocra</string>
 | 
				
			||||||
 | 
					    <string name="race_genasi">Génasi</string>
 | 
				
			||||||
 | 
					    <string name="race_deep_gnome">Gnome des Profondeurs</string>
 | 
				
			||||||
 | 
					    <string name="race_goliath">Goliath</string>
 | 
				
			||||||
 | 
					    <string name="race_undetermined">Indéterminé</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="action_google_sign_in">Se connecter avec</string>
 | 
					    <string name="action_google_sign_in">Se connecter avec</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="detail_title">Détails du personnage</string>
 | 
				
			||||||
 | 
					    <string name="detail_description">Description</string>
 | 
				
			||||||
 | 
					    <string name="detail_history">History</string>
 | 
				
			||||||
 | 
					    <string name="detail_portrait">Portrait</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,35 @@
 | 
				
			||||||
<resources>
 | 
					<resources>
 | 
				
			||||||
    <string name="app_name">Lexique</string>
 | 
					    <string name="app_name">Rp-Lexicon</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="error_generic">Oups, it should not be rocket science.</string>
 | 
				
			||||||
 | 
					    <string name="error_structure">The file structure appears to have changed and is no longer compatible with this application</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="gender_male">Male</string>
 | 
				
			||||||
 | 
					    <string name="gender_female">Female</string>
 | 
				
			||||||
 | 
					    <string name="gender_undetermined">Undetermined</string>
 | 
				
			||||||
 | 
					    <string name="gender_male_short">m.</string>
 | 
				
			||||||
 | 
					    <string name="gender_female_short">f.</string>
 | 
				
			||||||
 | 
					    <string name="gender_undetermined_short">u.</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="race_elf">Elf</string>
 | 
				
			||||||
 | 
					    <string name="race_halfling">Halfling</string>
 | 
				
			||||||
 | 
					    <string name="race_human">Human</string>
 | 
				
			||||||
 | 
					    <string name="race_dwarf">Dwarf</string>
 | 
				
			||||||
 | 
					    <string name="race_half_elf">Half-elf</string>
 | 
				
			||||||
 | 
					    <string name="race_half_orc">Half-Orc</string>
 | 
				
			||||||
 | 
					    <string name="race_dragonborn">Dragonborn</string>
 | 
				
			||||||
 | 
					    <string name="race_gnome">Gnome</string>
 | 
				
			||||||
 | 
					    <string name="race_tiefling">Tiefling</string>
 | 
				
			||||||
 | 
					    <string name="race_aarakocra">Aarakocra</string>
 | 
				
			||||||
 | 
					    <string name="race_genasi">Genasi</string>
 | 
				
			||||||
 | 
					    <string name="race_deep_gnome">Deep Gnome</string>
 | 
				
			||||||
 | 
					    <string name="race_goliath">Goliath</string>
 | 
				
			||||||
 | 
					    <string name="race_undetermined">Undetermined</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="action_google_sign_in">Sign in with</string>
 | 
					    <string name="action_google_sign_in">Sign in with</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="detail_title">Character\'s details</string>
 | 
				
			||||||
 | 
					    <string name="detail_description">Description</string>
 | 
				
			||||||
 | 
					    <string name="detail_history">Histoire</string>
 | 
				
			||||||
 | 
					    <string name="detail_portrait">Portrait</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue