Update players protrait and authentication animation
| 
						 | 
				
			
			@ -39,22 +39,30 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
 | 
			
		|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import com.pixelized.rplexicon.R
 | 
			
		||||
import com.pixelized.rplexicon.model.Lexicon
 | 
			
		||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
data class DropDownFieldUio<T>(
 | 
			
		||||
    @StringRes val label: Int,
 | 
			
		||||
    val values: List<Pair<T, Int>>,
 | 
			
		||||
    val value: State<Pair<T?, String>>,
 | 
			
		||||
    val onValueChange: (T?, String) -> Unit,
 | 
			
		||||
    val values: List<T>,
 | 
			
		||||
    val value: State<T?>,
 | 
			
		||||
    val valueLabel: @Composable (T) -> String,
 | 
			
		||||
    val onValueChange: (T?) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        fun <T> preview(@StringRes label: Int, id: T?, value: String) = DropDownFieldUio(
 | 
			
		||||
        fun <T> preview(
 | 
			
		||||
            @StringRes label: Int,
 | 
			
		||||
            id: T?,
 | 
			
		||||
            valueLabel: @Composable (T) -> String,
 | 
			
		||||
        ) = DropDownFieldUio(
 | 
			
		||||
            label = label,
 | 
			
		||||
            values = emptyList(),
 | 
			
		||||
            value = mutableStateOf(id to value),
 | 
			
		||||
            onValueChange = { _, _ -> },
 | 
			
		||||
            value = mutableStateOf(id),
 | 
			
		||||
            valueLabel = valueLabel,
 | 
			
		||||
            onValueChange = { },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +78,7 @@ fun <T> DropDownField(
 | 
			
		|||
    ExposedDropdownMenuBox(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        expanded = expended,
 | 
			
		||||
        onExpandedChange = { expended = !expended && field.value.value.first == null },
 | 
			
		||||
        onExpandedChange = { expended = !expended && field.value.value == null },
 | 
			
		||||
    ) {
 | 
			
		||||
        OutlinedTextField(
 | 
			
		||||
            modifier = Modifier.menuAnchor(),
 | 
			
		||||
| 
						 | 
				
			
			@ -85,13 +93,13 @@ fun <T> DropDownField(
 | 
			
		|||
            trailingIcon = {
 | 
			
		||||
                AnimatedContent(
 | 
			
		||||
                    modifier = Modifier.size(size = 48.dp),
 | 
			
		||||
                    targetState = field.value.value.first != null,
 | 
			
		||||
                    targetState = field.value.value != null,
 | 
			
		||||
                    transitionSpec = { fadeIn() with fadeOut() },
 | 
			
		||||
                    label = "DropDownFieldTrailingIconAnimation",
 | 
			
		||||
                ) {
 | 
			
		||||
                    when (it) {
 | 
			
		||||
                        true -> IconButton(
 | 
			
		||||
                            onClick = { field.onValueChange(null, "") },
 | 
			
		||||
                            onClick = { field.onValueChange(null) },
 | 
			
		||||
                        ) {
 | 
			
		||||
                            Icon(
 | 
			
		||||
                                modifier = Modifier.size(size = 18.dp),
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +124,7 @@ fun <T> DropDownField(
 | 
			
		|||
                focusedContainerColor = MaterialTheme.colorScheme.surface,
 | 
			
		||||
                unfocusedContainerColor = MaterialTheme.colorScheme.surface,
 | 
			
		||||
            ),
 | 
			
		||||
            value = field.value.value.second,
 | 
			
		||||
            value = field.value.value?.let { field.valueLabel(it) } ?: "",
 | 
			
		||||
            onValueChange = {},
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -125,14 +133,13 @@ fun <T> DropDownField(
 | 
			
		|||
            onDismissRequest = { expended = false },
 | 
			
		||||
        ) {
 | 
			
		||||
            field.values.forEach {
 | 
			
		||||
                val label = stringResource(id = it.second)
 | 
			
		||||
                DropdownMenuItem(
 | 
			
		||||
                    onClick = {
 | 
			
		||||
                        expended = false
 | 
			
		||||
                        field.onValueChange(it.first, label)
 | 
			
		||||
                        field.onValueChange(it)
 | 
			
		||||
                    },
 | 
			
		||||
                    text = {
 | 
			
		||||
                        Text(text = label)
 | 
			
		||||
                        Text(text = field.valueLabel(it))
 | 
			
		||||
                    },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +151,7 @@ fun <T> DropDownField(
 | 
			
		|||
@Preview(uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
 | 
			
		||||
private fun DropDownFieldPreview(
 | 
			
		||||
    @PreviewParameter(DropDownFieldPreviewProvider::class) preview: Pair<String?, String>,
 | 
			
		||||
    @PreviewParameter(DropDownFieldPreviewProvider::class) preview: Lexicon.Race?,
 | 
			
		||||
) {
 | 
			
		||||
    LexiconTheme {
 | 
			
		||||
        Surface {
 | 
			
		||||
| 
						 | 
				
			
			@ -153,16 +160,17 @@ private fun DropDownFieldPreview(
 | 
			
		|||
                    .fillMaxWidth()
 | 
			
		||||
                    .padding(all = 8.dp),
 | 
			
		||||
                field = DropDownFieldUio(
 | 
			
		||||
                    label = R.string.lexicon_search,
 | 
			
		||||
                    label = R.string.search_field_race,
 | 
			
		||||
                    values = emptyList(),
 | 
			
		||||
                    value = remember { mutableStateOf(preview) },
 | 
			
		||||
                    onValueChange = { _, _ -> },
 | 
			
		||||
                    valueLabel = { stringResource(id = it) },
 | 
			
		||||
                    onValueChange = { },
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private class DropDownFieldPreviewProvider : PreviewParameterProvider<Pair<String?, String>> {
 | 
			
		||||
    override val values: Sequence<Pair<String?, String>> = sequenceOf(null to "", "" to "preview")
 | 
			
		||||
private class DropDownFieldPreviewProvider : PreviewParameterProvider<Lexicon.Race?> {
 | 
			
		||||
    override val values: Sequence<Lexicon.Race?> = sequenceOf(null, Lexicon.Race.HALF_ORC)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +30,7 @@ import androidx.compose.runtime.LaunchedEffect
 | 
			
		|||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.derivedStateOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.runtime.saveable.rememberSaveable
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Brush
 | 
			
		||||
| 
						 | 
				
			
			@ -145,7 +146,7 @@ private fun PartyBackground(
 | 
			
		|||
    Gyroscope {
 | 
			
		||||
        val balance = remember {
 | 
			
		||||
            derivedStateOf {
 | 
			
		||||
                max(-1f, min(1f, this.gyroscope.value.roll))
 | 
			
		||||
                max(-1f, min(1f, this.gyroscope.value.roll * 2.5f))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        val colorFilter = remember {
 | 
			
		||||
| 
						 | 
				
			
			@ -263,13 +264,13 @@ private fun PartyBackground(
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun rememberPortrait(): List<Int> = remember {
 | 
			
		||||
private fun rememberPortrait(): List<Int> = rememberSaveable {
 | 
			
		||||
    listOf(
 | 
			
		||||
        R.drawable.im_tigrane,
 | 
			
		||||
        R.drawable.im_unathana,
 | 
			
		||||
        R.drawable.im_brulkhai,
 | 
			
		||||
        R.drawable.im_nelia,
 | 
			
		||||
        R.drawable.im_leandre,
 | 
			
		||||
        R.drawable.im_nelia,
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -279,14 +280,14 @@ private fun animatedWeight(
 | 
			
		|||
    amplitude: Float = E.toFloat(),
 | 
			
		||||
    divergence: Float = 0.95f,
 | 
			
		||||
    position: Float,
 | 
			
		||||
    step: Float = .16f,
 | 
			
		||||
    step: Float = .10f,
 | 
			
		||||
): Float {
 | 
			
		||||
    val animatedWeight = animateFloatAsState(
 | 
			
		||||
        targetValue = divergence * amplitude.pow(-(position - progress.value).pow(2f) / step) + (1f - divergence),
 | 
			
		||||
        label = "AnimatedBackgroundWeight",
 | 
			
		||||
        animationSpec = spring(
 | 
			
		||||
            dampingRatio = Spring.DampingRatioNoBouncy,
 | 
			
		||||
            stiffness = Spring.StiffnessLow,
 | 
			
		||||
            stiffness = (Spring.StiffnessLow + Spring.StiffnessVeryLow) / 2f,
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    return animatedWeight.value
 | 
			
		||||
| 
						 | 
				
			
			@ -378,6 +379,8 @@ private fun rememeberGoogleStringResource(): AnnotatedString {
 | 
			
		|||
private fun AuthenticationScreenContentPreview() {
 | 
			
		||||
    LexiconTheme {
 | 
			
		||||
        Surface {
 | 
			
		||||
            PartyBackground()
 | 
			
		||||
 | 
			
		||||
            AuthenticationScreenContent(
 | 
			
		||||
                modifier = Modifier
 | 
			
		||||
                    .fillMaxSize()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ package com.pixelized.rplexicon.ui.screens.search
 | 
			
		|||
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
 | 
			
		||||
import androidx.compose.foundation.ExperimentalFoundationApi
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +33,6 @@ import androidx.compose.runtime.mutableStateOf
 | 
			
		|||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +49,7 @@ import com.pixelized.rplexicon.ui.composable.form.TextFieldUio
 | 
			
		|||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
 | 
			
		||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterDetail
 | 
			
		||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
 | 
			
		||||
 | 
			
		||||
@Stable
 | 
			
		||||
| 
						 | 
				
			
			@ -81,8 +80,8 @@ fun SearchScreen(
 | 
			
		|||
                screen.navigateToCharacterDetail(
 | 
			
		||||
                    id = item.id,
 | 
			
		||||
                    highlight = form.search.value.value.takeIf { it.isNotEmpty() },
 | 
			
		||||
                    race = form.race.value.value.first,
 | 
			
		||||
                    gender = form.gender.value.value.first,
 | 
			
		||||
                    race = form.race.value.value,
 | 
			
		||||
                    gender = form.gender.value.value,
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            onBack = {
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +94,7 @@ fun SearchScreen(
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
 | 
			
		||||
@OptIn(ExperimentalMaterial3Api::class)
 | 
			
		||||
@Composable
 | 
			
		||||
private fun SearchScreenContent(
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
| 
						 | 
				
			
			@ -178,21 +177,20 @@ private fun SearchScreenContent(
 | 
			
		|||
@Preview(uiMode = UI_MODE_NIGHT_YES)
 | 
			
		||||
private fun SearchScreenContentPreview() {
 | 
			
		||||
    LexiconTheme {
 | 
			
		||||
        val context = LocalContext.current
 | 
			
		||||
        Surface {
 | 
			
		||||
            SearchScreenContent(
 | 
			
		||||
                modifier = Modifier.fillMaxSize(),
 | 
			
		||||
                form = SearchFormUio(
 | 
			
		||||
                    search = TextFieldUio.preview(R.string.search_field_search),
 | 
			
		||||
                    gender = DropDownFieldUio.preview(
 | 
			
		||||
                        label = R.string.search_field_gender,
 | 
			
		||||
                        id = Lexicon.Gender.FEMALE,
 | 
			
		||||
                        value = context.getString(R.string.gender_female),
 | 
			
		||||
                        label = R.string.search_field_gender,
 | 
			
		||||
                        valueLabel = { stringResource(id = Lexicon.Gender.FEMALE) },
 | 
			
		||||
                    ),
 | 
			
		||||
                    race = DropDownFieldUio.preview(
 | 
			
		||||
                        label = R.string.search_field_race,
 | 
			
		||||
                        id = null,
 | 
			
		||||
                        value = "",
 | 
			
		||||
                        label = R.string.search_field_race,
 | 
			
		||||
                        valueLabel = { stringResource(id = Lexicon.Race.HALF_ORC) },
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
                items = remember {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ import com.pixelized.rplexicon.model.Lexicon.Race
 | 
			
		|||
import com.pixelized.rplexicon.repository.LexiconRepository
 | 
			
		||||
import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
 | 
			
		||||
import com.pixelized.rplexicon.ui.composable.form.TextFieldUio
 | 
			
		||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
 | 
			
		||||
import dagger.hilt.android.lifecycle.HiltViewModel
 | 
			
		||||
import javax.inject.Inject
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,8 +19,8 @@ class SearchViewModel @Inject constructor(
 | 
			
		|||
    repository: LexiconRepository,
 | 
			
		||||
) : ViewModel() {
 | 
			
		||||
    private val _search = mutableStateOf("")
 | 
			
		||||
    private val _gender = mutableStateOf<Pair<Gender?, String>>(null to "")
 | 
			
		||||
    private val _race = mutableStateOf<Pair<Race?, String>>(null to "")
 | 
			
		||||
    private val _gender = mutableStateOf<Gender?>(null)
 | 
			
		||||
    private val _race = mutableStateOf<Race?>(null)
 | 
			
		||||
 | 
			
		||||
    val form = SearchFormUio(
 | 
			
		||||
        search = TextFieldUio(
 | 
			
		||||
| 
						 | 
				
			
			@ -31,15 +32,17 @@ class SearchViewModel @Inject constructor(
 | 
			
		|||
        ),
 | 
			
		||||
        gender = DropDownFieldUio(
 | 
			
		||||
            label = R.string.search_field_gender,
 | 
			
		||||
            values = genders(),
 | 
			
		||||
            values = Lexicon.Gender.values().toList(),
 | 
			
		||||
            value = _gender,
 | 
			
		||||
            onValueChange = { id, value -> _gender.value = id to value },
 | 
			
		||||
            valueLabel = { stringResource(id = it) },
 | 
			
		||||
            onValueChange = { id -> _gender.value = id },
 | 
			
		||||
        ),
 | 
			
		||||
        race = DropDownFieldUio(
 | 
			
		||||
            label = R.string.search_field_race,
 | 
			
		||||
            values = races(),
 | 
			
		||||
            values = Lexicon.Race.values().toList(),
 | 
			
		||||
            value = _race,
 | 
			
		||||
            onValueChange = { id, value -> _race.value = id to value },
 | 
			
		||||
            valueLabel = { stringResource(id = it) },
 | 
			
		||||
            onValueChange = { id -> _race.value = id },
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +50,8 @@ class SearchViewModel @Inject constructor(
 | 
			
		|||
 | 
			
		||||
    val filter = derivedStateOf {
 | 
			
		||||
        data.filter { item ->
 | 
			
		||||
            val gender = _gender.value.first?.let { it == item.gender }
 | 
			
		||||
            val race = _race.value.first?.let { it == item.race }
 | 
			
		||||
            val gender = _gender.value?.let { it == item.gender }
 | 
			
		||||
            val race = _race.value?.let { it == item.race }
 | 
			
		||||
            val search = _search.value.takeIf { it.isNotEmpty() }?.let {
 | 
			
		||||
                val name = item.name.contains(_search.value, true)
 | 
			
		||||
                val diminutive = item.diminutive?.contains(_search.value, true) == true
 | 
			
		||||
| 
						 | 
				
			
			@ -66,8 +69,8 @@ class SearchViewModel @Inject constructor(
 | 
			
		|||
 | 
			
		||||
    private fun Lexicon.toSearchUio(
 | 
			
		||||
        search: String = _search.value,
 | 
			
		||||
        highlightGender: Boolean = this.gender == _gender.value.first,
 | 
			
		||||
        highlightRace: Boolean = this.race == _race.value.first,
 | 
			
		||||
        highlightGender: Boolean = this.gender == _gender.value,
 | 
			
		||||
        highlightRace: Boolean = this.race == _race.value,
 | 
			
		||||
    ) = SearchItemUio(
 | 
			
		||||
        id = this.id,
 | 
			
		||||
        name = this.name,
 | 
			
		||||
| 
						 | 
				
			
			@ -80,29 +83,4 @@ class SearchViewModel @Inject constructor(
 | 
			
		|||
        highlightGender = highlightGender,
 | 
			
		||||
        highlightRace = highlightRace,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private fun genders() = listOf(
 | 
			
		||||
            Gender.MALE to R.string.gender_male,
 | 
			
		||||
            Gender.FEMALE to R.string.gender_female,
 | 
			
		||||
            Gender.UNDETERMINED to R.string.gender_undetermined,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        private fun races() = listOf(
 | 
			
		||||
            Race.ELF to R.string.race_elf,
 | 
			
		||||
            Race.HALFLING to R.string.race_halfling,
 | 
			
		||||
            Race.HUMAN to R.string.race_human,
 | 
			
		||||
            Race.DWARF to R.string.race_dwarf,
 | 
			
		||||
            Race.HALF_ELF to R.string.race_half_elf,
 | 
			
		||||
            Race.HALF_ORC to R.string.race_half_orc,
 | 
			
		||||
            Race.DRAGONBORN to R.string.race_dragonborn,
 | 
			
		||||
            Race.GNOME to R.string.race_gnome,
 | 
			
		||||
            Race.TIEFLING to R.string.race_tiefling,
 | 
			
		||||
            Race.AARAKOCRA to R.string.race_aarakocra,
 | 
			
		||||
            Race.GENASI to R.string.race_genasi,
 | 
			
		||||
            Race.DEEP_GNOME to R.string.race_deep_gnome,
 | 
			
		||||
            Race.GOLIATH to R.string.race_goliath,
 | 
			
		||||
            Race.UNDETERMINED to R.string.race_undetermined,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/drawable/im_brulkhai.jpeg
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 61 KiB  | 
| 
		 Before Width: | Height: | Size: 32 KiB  | 
| 
		 Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 20 KiB  | 
| 
		 Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 23 KiB  | 
| 
		 Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 28 KiB  |