Change the parsing mechanist to allow more flexibility.

This commit is contained in:
Andres Gomez, Thomas (ITDV CC) - AF (ext) 2023-07-17 10:56:38 +02:00
parent f5c10c5154
commit fb31de8130
19 changed files with 577 additions and 140 deletions

View file

@ -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 {

View file

@ -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"
} }
], ],

View file

@ -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
} }
} }

View file

@ -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,
) )
} }

View file

@ -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"
} }
} }

View file

@ -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(),
) )
} }

View file

@ -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"),

View file

@ -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,

View file

@ -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,
) )
) )
} }

View file

@ -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,
) )
) )
) )

View file

@ -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) {

View file

@ -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,
)

View file

@ -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
) )
} }
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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,
)

View file

@ -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>

View file

@ -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>