Basic search implementation.

This commit is contained in:
Andres Gomez, Thomas (ITDV CC) - AF (ext) 2023-07-18 21:21:52 +02:00
parent dfc6c4e673
commit af5fb8f33c
21 changed files with 806 additions and 298 deletions

View file

@ -97,6 +97,9 @@ dependencies {
implementation("com.google.accompanist:accompanist-navigation-animation:0.30.1")
implementation("com.google.accompanist:accompanist-placeholder:0.30.1")
// Splash Screen support prior to Android 12
implementation("androidx.core:core-splashscreen:1.0.1")
// Google service
implementation("com.google.android.gms:play-services-auth:20.6.0")
implementation(

View file

@ -9,19 +9,23 @@
<application
android:name=".MainApplication"
android:allowBackup="true"
android:colorMode="wideColorGamut"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Lexique"
android:theme="@style/Theme.Lexicon.NoActionBar"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Lexique">
android:screenOrientation="portrait"
android:theme="@style/Theme.Lexicon.Starting"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -29,5 +33,4 @@
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -4,6 +4,7 @@ import android.app.Activity
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
@ -15,13 +16,19 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import com.pixelized.rplexicon.ui.navigation.ScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import dagger.hilt.android.AndroidEntryPoint
val LocalActivity = staticCompositionLocalOf<Activity> { error("Activity not available") }
val LocalSnack =
staticCompositionLocalOf<SnackbarHostState> { error("SnackbarHostState not available") }
val LocalActivity = staticCompositionLocalOf<Activity> {
error("Activity not available")
}
val LocalSnack = staticCompositionLocalOf<SnackbarHostState> {
error("SnackbarHostState not available")
}
val NO_WINDOW_INSETS = WindowInsets(0, 0, 0, 0)
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@ -29,6 +36,14 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Disable system inset consuming.
WindowCompat.setDecorFitsSystemWindows(window, false)
// splashscreen management
installSplashScreen().apply {
setKeepOnScreenCondition { false }
}
setContent {
LexiconTheme {
CompositionLocalProvider(
@ -36,6 +51,7 @@ class MainActivity : ComponentActivity() {
LocalSnack provides remember { SnackbarHostState() }
) {
Scaffold(
contentWindowInsets = NO_WINDOW_INSETS,
content = { padding ->
Surface(
modifier = Modifier

View file

@ -5,15 +5,14 @@ import androidx.compose.animation.core.*
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@ -25,24 +24,24 @@ import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Composable
fun FloatingActionButton(
modifier: Modifier = Modifier,
expended: Boolean,
enabled: Boolean = true,
innerSpacing: Dp = 16.dp,
contentPadding: PaddingValues = FlyingBlueFloatingActionButtonDefault.ContentPadding,
elevation: ButtonElevation? = ButtonDefaults.elevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp,
disabledElevation = 0.dp,
),
shape: Shape = CircleShape,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
colors: ButtonColors = ButtonDefaults.buttonColors(),
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = ButtonDefaults.outlinedShape,
colors: ButtonColors = ButtonDefaults.outlinedButtonColors(),
elevation: ButtonElevation? = null,
border: BorderStroke? = BorderStroke(
width = 2.dp,
color = MaterialTheme.colorScheme.primary,
),
contentPadding: PaddingValues = PaddingValues(all = 0.dp),
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
expended: Boolean,
innerSpacing: Dp = 8.dp,
icon: @Composable (RowScope.() -> Unit),
text: @Composable (RowScope.() -> Unit),
) {
LocalButton(
OutlinedButton(
modifier = modifier,
onClick = onClick,
enabled = enabled,
@ -51,13 +50,16 @@ fun FloatingActionButton(
shape = shape,
colors = colors,
contentPadding = contentPadding,
border = border,
content = {
FabContent(
expended = expended,
innerSpacing = innerSpacing,
icon = icon,
text = text,
)
BoxWithConstraints {
FabContent(
expended = expended,
innerSpacing = innerSpacing,
icon = icon,
text = text,
)
}
},
)
}
@ -69,9 +71,10 @@ private fun BoxWithConstraintsScope.FabContent(
icon: @Composable (RowScope.() -> Unit),
text: @Composable (RowScope.() -> Unit),
) {
val maxWidth = if (LocalView.current.isInEditMode) 300.dp else maxWidth
val width by animateDpAsState(
label = "FabContentWidth",
targetValue = if (expended) maxWidth else minWidth,
targetValue = if (expended) maxWidth else 56.dp,
animationSpec = when (expended) {
true -> tween(durationMillis = 300, easing = FastOutSlowInEasing)
else -> tween(durationMillis = 300, delayMillis = 100, easing = FastOutSlowInEasing)
@ -105,56 +108,6 @@ private fun BoxWithConstraintsScope.FabContent(
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun LocalButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
elevation: ButtonElevation? = ButtonDefaults.elevation(),
shape: Shape = MaterialTheme.shapes.small,
border: BorderStroke? = null,
colors: ButtonColors = ButtonDefaults.buttonColors(),
contentPadding: PaddingValues = FlyingBlueFloatingActionButtonDefault.ContentPadding,
content: @Composable BoxWithConstraintsScope.() -> Unit
) {
val contentColor by colors.contentColor(enabled)
Surface(
onClick = onClick,
modifier = modifier,
enabled = enabled,
shape = shape,
color = colors.backgroundColor(enabled).value,
contentColor = contentColor.copy(alpha = 1f),
border = border,
elevation = elevation?.elevation(enabled, interactionSource)?.value ?: 0.dp,
interactionSource = interactionSource,
) {
CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) {
ProvideTextStyle(
value = MaterialTheme.typography.button
) {
BoxWithConstraints(
Modifier
.defaultMinSize(
minWidth = FlyingBlueFloatingActionButtonDefault.MinWidth,
minHeight = FlyingBlueFloatingActionButtonDefault.MinHeight
)
.padding(contentPadding),
content = content
)
}
}
}
}
object FlyingBlueFloatingActionButtonDefault {
val ContentPadding = PaddingValues(all = 0.dp)
val MinWidth = 56.dp
val MinHeight = 56.dp
}
@Composable
@Preview
private fun FloatingActionButtonPreview(
@ -162,6 +115,7 @@ private fun FloatingActionButtonPreview(
) {
LexiconTheme {
FloatingActionButton(
modifier = Modifier.padding(all = 16.dp),
expended = expended,
icon = {
Icon(

View file

@ -0,0 +1,168 @@
package com.pixelized.rplexicon.ui.composable.form
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.with
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
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.ui.theme.LexiconTheme
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,
) {
companion object {
fun <T> preview(@StringRes label: Int, id: T?, value: String) = DropDownFieldUio(
label = label,
values = emptyList(),
value = mutableStateOf(id to value),
onValueChange = { _, _ -> },
)
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalAnimationApi::class)
@Composable
fun <T> DropDownField(
modifier: Modifier = Modifier,
field: DropDownFieldUio<T>,
) {
var expended by remember(field) { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = modifier,
expanded = expended,
onExpandedChange = { expended = !expended && field.value.value.first == null },
) {
OutlinedTextField(
modifier = Modifier.menuAnchor(),
shape = MaterialTheme.lexicon.shapes.textField,
readOnly = true,
singleLine = true,
label = {
Text(
text = stringResource(id = field.label)
)
},
trailingIcon = {
AnimatedContent(
modifier = Modifier.size(size = 48.dp),
targetState = field.value.value.first != null,
transitionSpec = { fadeIn() with fadeOut() },
label = "DropDownFieldTrailingIconAnimation",
) {
when (it) {
true -> IconButton(
onClick = { field.onValueChange(null, "") },
) {
Icon(
modifier = Modifier.size(size = 18.dp),
painter = painterResource(id = R.drawable.ic_clear_24),
contentDescription = null,
)
}
else -> Box(
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(size = 24.dp),
painter = painterResource(id = R.drawable.ic_arrow_down_24),
contentDescription = null,
)
}
}
}
},
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
),
value = field.value.value.second,
onValueChange = {},
)
DropdownMenu(
expanded = expended,
onDismissRequest = { expended = false },
) {
field.values.forEach {
val label = stringResource(id = it.second)
DropdownMenuItem(
onClick = {
expended = false
field.onValueChange(it.first, label)
},
text = {
Text(text = label)
},
)
}
}
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun DropDownFieldPreview(
@PreviewParameter(DropDownFieldPreviewProvider::class) preview: Pair<String?, String>,
) {
LexiconTheme {
Surface {
DropDownField(
modifier = Modifier
.fillMaxWidth()
.padding(all = 8.dp),
field = DropDownFieldUio(
label = R.string.lexicon_search,
values = emptyList(),
value = remember { mutableStateOf(preview) },
onValueChange = { _, _ -> },
)
)
}
}
}
private class DropDownFieldPreviewProvider : PreviewParameterProvider<Pair<String?, String>> {
override val values: Sequence<Pair<String?, String>> = sequenceOf(null to "", "" to "preview")
}

View file

@ -0,0 +1,109 @@
package com.pixelized.rplexicon.ui.composable.form
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.annotation.StringRes
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
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.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@Stable
data class TextFieldUio(
@StringRes val label: Int,
val value: State<String>,
val onValueChange: (String) -> Unit,
) {
companion object {
fun preview(@StringRes label: Int) = TextFieldUio(
label = label,
value = mutableStateOf(""),
onValueChange = {},
)
}
}
@Composable
fun TextField(
modifier: Modifier = Modifier,
field: TextFieldUio,
) {
OutlinedTextField(
modifier = modifier,
shape = MaterialTheme.lexicon.shapes.textField,
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
),
label = {
Text(text = stringResource(id = field.label))
},
trailingIcon = {
AnimatedVisibility(
visible = field.value.value.isNotEmpty(),
enter = fadeIn(),
exit = fadeOut(),
) {
IconButton(onClick = { field.onValueChange("") }) {
Icon(
modifier = Modifier.size(size = 18.dp),
painter = painterResource(id = R.drawable.ic_clear_24),
contentDescription = null,
)
}
}
},
value = field.value.value,
onValueChange = field.onValueChange,
)
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun TextFieldPreview(
@PreviewParameter(TextFieldPreviewProvider::class) preview: String,
) {
LexiconTheme {
Surface {
TextField(
modifier = Modifier
.fillMaxWidth()
.padding(all = 8.dp),
field = TextFieldUio(
label = R.string.lexicon_search,
value = remember { mutableStateOf(preview) },
onValueChange = {},
)
)
}
}
}
private class TextFieldPreviewProvider : PreviewParameterProvider<String> {
override val values: Sequence<String> = sequenceOf("", "preview")
}

View file

@ -5,9 +5,9 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -16,13 +16,13 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -71,9 +71,12 @@ fun AuthenticationScreen(
val state = authenticationVM.rememberAuthenticationState()
Surface {
PartyBackground()
AuthenticationScreenContent(
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.padding(all = 16.dp),
version = versionVM.version,
onGoogleSignIn = {
@ -108,22 +111,19 @@ private fun AuthenticationScreenContent(
) {
val typography = MaterialTheme.typography
PartyBackground()
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Bottom),
horizontalAlignment = Alignment.End,
) {
Button(
OutlinedButton(
modifier = Modifier
.fillMaxWidth()
.border(
width = 2.dp,
color = MaterialTheme.colorScheme.primary,
shape = CircleShape,
),
colors = ButtonDefaults.outlinedButtonColors(),
.height(56.dp),
border = BorderStroke(
width = 2.dp,
color = MaterialTheme.colorScheme.primary,
),
onClick = onGoogleSignIn,
) {
Text(text = rememeberGoogleStringResource())

View file

@ -6,7 +6,6 @@ import android.net.Uri
import androidx.annotation.StringRes
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -17,9 +16,9 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
@ -40,6 +39,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
@ -47,6 +47,7 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -58,8 +59,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.theme.LexiconTheme
import com.pixelized.rplexicon.utilitary.rememberLoadingTransition
import com.skydoves.landscapist.ImageOptions
import com.skydoves.landscapist.glide.GlideImage
import com.skydoves.landscapist.glide.GlideImageState
@Stable
data class CharacterDetailUio(
@ -83,7 +86,6 @@ fun CharacterDetailScreen(
modifier = Modifier.fillMaxSize(),
item = viewModel.character,
onBack = { screen.popBackStack() },
onImage = { },
)
}
}
@ -95,7 +97,6 @@ private fun CharacterDetailScreenContent(
state: ScrollState = rememberScrollState(),
item: State<CharacterDetailUio>,
onBack: () -> Unit,
onImage: (Uri) -> Unit,
) {
val colorScheme = MaterialTheme.colorScheme
val typography = MaterialTheme.typography
@ -129,9 +130,16 @@ private fun CharacterDetailScreenContent(
.aspectRatio(ratio = 1f)
.scrollOffset(scrollState = state) { -it / 2 },
) {
val transition = rememberLoadingTransition { uri }
GlideImage(
modifier = Modifier.matchParentSize(),
imageModel = { uri.toString() },
modifier = Modifier
.matchParentSize()
.alpha(alpha = transition.alpha),
onImageStateChanged = {
if (it is GlideImageState.Success) {
transition.target = 1f
}
},
imageOptions = ImageOptions(
alignment = Alignment.TopCenter,
contentScale = ContentScale.Crop,
@ -141,7 +149,8 @@ private fun CharacterDetailScreenContent(
)
},
),
previewPlaceholder = R.drawable.ic_empty,
imageModel = { uri.toString() },
previewPlaceholder = R.drawable.im_brulkhai,
)
Box(
modifier = Modifier
@ -220,6 +229,8 @@ private fun CharacterDetailScreenContent(
)
}
if (item.value.portrait.isNotEmpty()) {
val configuration = LocalConfiguration.current
val maxSize = remember { (configuration.screenWidthDp.dp - 16.dp * 2) }
Text(
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
style = typography.titleMedium,
@ -230,15 +241,21 @@ private fun CharacterDetailScreenContent(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(items = item.value.portrait) {
val transition = rememberLoadingTransition { it }
GlideImage(
modifier = Modifier
.clickable { onImage(it) }
.height(320.dp),
imageModel = { it },
.sizeIn(maxWidth = maxSize, maxHeight = maxSize)
.alpha(alpha = transition.alpha),
onImageStateChanged = {
if (it is GlideImageState.Success) {
transition.target = 1f
}
},
imageOptions = ImageOptions(
contentScale = ContentScale.FillHeight
),
previewPlaceholder = R.drawable.ic_empty,
imageModel = { it },
previewPlaceholder = R.drawable.im_brulkhai,
)
}
}
@ -285,15 +302,6 @@ private fun CharacterDetailScreenContentPreview() {
race = R.string.race_half_orc,
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/877/large/bayard-wu-0714.jpg?1468642665"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/887/large/bayard-wu-0715.jpg?1468642839"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/003/024/891/large/bayard-wu-0623-03.jpg?1468642872"),
Uri.parse("https://cdna.artstation.com/p/assets/images/images/002/869/868/large/bayard-wu-0622-03.jpg?1466664135"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/002/869/871/large/bayard-wu-0622-04.jpg?1466664153"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/004/347/181/large/bayard-wu-1217.jpg?1482770883"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/004/297/635/large/bayard-wu-1215.jpg?1482166826"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/004/297/631/large/bayard-wu-1209.jpg?1482166803"),
Uri.parse("https://cdnb.artstation.com/p/assets/images/images/004/297/641/large/bayard-wu-1212.jpg?1482166838"),
),
description = "Brulkhai, ou plus simplement Bru, est solidement bâti. Elle mesure 192 cm pour 110 kg de muscles lorsquelle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. Dun tempérament taciturne, elle parle peu et de façon concise. Elle est parfois brutale, aussi bien physiquement que verbalement, Elle ne prend cependant aucun plaisir à malmener ceux quelle considère plus faibles quelle. Dune nature simple et honnête, elle ne mâche pas ses mots et ne dissimule généralement pas ses pensées. Son intelligence modeste est plus le reflet dun manque déducation et dune capacité limitée à gérer ses émotions quà une débilité congénitale. Elle voue à la force un culte car cest par son expression quelle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux quelle nomme foshnu (bébé, chouineur en commun).",
history = null,
@ -304,7 +312,6 @@ private fun CharacterDetailScreenContentPreview() {
modifier = Modifier.fillMaxSize(),
item = character,
onBack = { },
onImage = { },
)
}
}

View file

@ -55,49 +55,53 @@ fun LexiconItem(
) {
val typography = MaterialTheme.typography
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
Surface(modifier = modifier) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
maxLines = 1,
text = item.name,
)
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = typography.labelMedium,
maxLines = 1,
text = item.diminutive ?: ""
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
maxLines = 1,
text = stringResource(id = item.gender)
)
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
maxLines = 1,
text = stringResource(id = item.race)
)
FlowRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
maxLines = 1,
text = item.name,
)
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = typography.labelMedium,
maxLines = 1,
text = item.diminutive ?: ""
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
maxLines = 1,
text = stringResource(id = item.gender)
)
Text(
modifier = Modifier
.alignByBaseline()
.placeholder { item.placeholder },
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
maxLines = 1,
text = stringResource(id = item.race)
)
}
}
}
}
@ -110,9 +114,6 @@ private fun LexiconItemContentPreview() {
LexiconTheme {
Surface {
LexiconItem(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
item = LexiconItemUio(
id = 0,
name = "Brulkhai",

View file

@ -7,6 +7,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -17,7 +18,9 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
@ -30,6 +33,7 @@ import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@ -46,12 +50,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.LocalSnack
import com.pixelized.rplexicon.NO_WINDOW_INSETS
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.screens.navigateToCharacterDetail
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch
@ -100,6 +107,7 @@ fun LexiconScreen(
Surface {
LexiconScreenContent(
modifier = Modifier.systemBarsPadding(),
items = viewModel.items,
lazyColumnState = lazyListState,
refreshState = refresh,
@ -139,35 +147,44 @@ private fun LexiconScreenContent(
) {
Scaffold(
modifier = modifier,
contentWindowInsets = NO_WINDOW_INSETS,
topBar = {
TopAppBar(
windowInsets = NO_WINDOW_INSETS,
title = {
Text(text = stringResource(id = R.string.app_name))
},
)
},
// floatingActionButton = {
// FloatingActionButton(
// modifier = Modifier.padding(start = 32.dp),
// expended = isFabExpended.value,
// onClick = onSearch,
// icon = {
// Icon(
// tint = MaterialTheme.colorScheme.onPrimary,
// painter = painterResource(id = R.drawable.ic_baseline_search_24),
// contentDescription = null,
// )
// },
// text = {
// val typography = MaterialTheme.typography
// Text(
// color = MaterialTheme.colorScheme.onPrimary,
// style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
// text = "Rechercher",
// )
// },
// )
// }
floatingActionButton = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(start = 32.dp), // `Fix` Scaffold content size for FAB.
contentAlignment = Alignment.CenterEnd,
) {
AnimatedVisibility(
visible = items.value.isNotEmpty(),
enter = fadeIn(),
exit = fadeOut(),
) {
FloatingActionButton(
expended = isFabExpended.value,
onClick = onSearch,
icon = {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_search_24),
contentDescription = null,
)
},
text = {
Text(text = stringResource(id = R.string.lexicon_search))
},
)
}
}
}
) { padding ->
Box(
modifier = Modifier.padding(paddingValues = padding),
@ -186,15 +203,12 @@ private fun LexiconScreenContent(
state = lazyColumnState,
contentPadding = PaddingValues(
top = 8.dp,
bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
),
) {
items(count = 6) {
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
modifier = Modifier.animateItemPlacement(),
item = LexiconItemUio.Brulkhai,
)
}
@ -207,7 +221,7 @@ private fun LexiconScreenContent(
state = lazyColumnState,
contentPadding = PaddingValues(
top = 8.dp,
bottom = 8.dp + 16.dp // + 56.dp + 16.dp,
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
),
) {
items(
@ -218,9 +232,7 @@ private fun LexiconScreenContent(
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.clickable { onItem(it) }
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 16.dp),
.clickable { onItem(it) },
item = it,
)
}

View file

@ -2,46 +2,79 @@ 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
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ExposedDropdownMenuBox
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
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
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.NO_WINDOW_INSETS
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Lexicon
import com.pixelized.rplexicon.ui.composable.form.DropDownField
import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
import com.pixelized.rplexicon.ui.composable.form.TextField
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.screens.lexicon.LexiconItem
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconItemUio
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@Stable
data class SearchFormUio(
val search: TextFieldUio,
val gender: DropDownFieldUio<Lexicon.Gender>,
val race: DropDownFieldUio<Lexicon.Race>,
)
@Composable
fun SearchScreen() {
fun SearchScreen(
viewModel: SearchViewModel = hiltViewModel(),
) {
val screen = LocalScreenNavHost.current
Surface {
SearchScreenContent(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding(),
items = viewModel.filter,
form = viewModel.form,
onItem = {
screen.navigateToCharacterDetail(id = it.id)
},
onBack = {
screen.popBackStack()
}
@ -49,17 +82,23 @@ fun SearchScreen() {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
private fun SearchScreenContent(
modifier: Modifier = Modifier,
lazyColumnState: LazyListState = rememberLazyListState(),
items: State<List<LexiconItemUio>>,
form: SearchFormUio,
onBack: () -> Unit,
onItem: (LexiconItemUio) -> Unit,
) {
Scaffold(
modifier = modifier,
contentWindowInsets = NO_WINDOW_INSETS,
containerColor = Color.Transparent,
topBar = {
TopAppBar(
windowInsets = NO_WINDOW_INSETS,
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
@ -69,99 +108,52 @@ private fun SearchScreenContent(
}
},
title = {
Text(text = "Rechercher")
Text(text = stringResource(id = R.string.search_field_title))
},
)
},
) {
Column(
) { paddingValues ->
LazyColumn(
modifier = Modifier
.padding(paddingValues = it)
.padding(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
.fillMaxSize()
.padding(paddingValues = paddingValues),
state = lazyColumnState,
contentPadding = PaddingValues(vertical = 8.dp),
) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = "",
label = {
Text("Nom")
},
onValueChange = { _ -> },
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
),
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
DropDownField(
modifier = Modifier.weight(1f),
subject = remember { mutableStateOf("1") },
subjects = listOf("1", "2"),
onChange = { },
expanded = remember { mutableStateOf(false) },
onExpandedChange = { }
)
DropDownField(
modifier = Modifier.weight(1f),
subject = remember { mutableStateOf("1") },
subjects = listOf("1", "2"),
onChange = { },
expanded = remember { mutableStateOf(false) },
onExpandedChange = { }
)
item {
Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
TextField(
modifier = Modifier.fillMaxWidth(),
field = form.search,
)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
DropDownField(
modifier = Modifier.weight(1f),
field = form.gender,
)
DropDownField(
modifier = Modifier.weight(1f),
field = form.race,
)
}
Divider(modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DropDownField(
modifier: Modifier = Modifier,
subjects: List<String>,
subject: State<String>,
onChange: (String) -> Unit,
expanded: State<Boolean>,
onExpandedChange: (Boolean) -> Unit
) {
ExposedDropdownMenuBox(
modifier = modifier,
expanded = expanded.value,
onExpandedChange = onExpandedChange,
) {
TextField(
modifier = Modifier.clickable { onExpandedChange(true) },
readOnly = true,
singleLine = true,
placeholder = {
Text(
text = "pouet"
)
},
trailingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_arrow_drop_down_24),
contentDescription = null,
)
},
colors = TextFieldDefaults.colors(
focusedContainerColor = MaterialTheme.colorScheme.surface,
unfocusedContainerColor = MaterialTheme.colorScheme.surface,
),
value = subject.value,
onValueChange = {},
)
ExposedDropdownMenu(
expanded = expanded.value,
onDismissRequest = { onExpandedChange(false) },
) {
subjects.forEach {
DropdownMenuItem(
onClick = { onChange(it) },
content = { Text(text = it) },
items(
items = items.value,
key = { it.id },
contentType = { "Lexicon" },
) {
LexiconItem(
modifier = Modifier
.animateItemPlacement()
.clickable { onItem(it) },
item = it,
)
}
}
@ -173,9 +165,37 @@ fun DropDownField(
@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),
),
race = DropDownFieldUio.preview(
label = R.string.search_field_race,
id = null,
value = "",
),
),
items = remember {
mutableStateOf(
listOf(
LexiconItemUio(
id = 0,
name = "Brulkhai",
diminutive = "Bru",
gender = R.string.gender_female_short,
race = R.string.race_half_orc,
)
)
)
},
onItem = { },
onBack = { },
)
}

View file

@ -0,0 +1,117 @@
package com.pixelized.rplexicon.ui.screens.search
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.model.Lexicon
import com.pixelized.rplexicon.model.Lexicon.Gender
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.ui.screens.lexicon.LexiconItemUio
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class SearchViewModel @Inject constructor(
private val 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 "")
val form = SearchFormUio(
search = TextFieldUio(
label = R.string.search_field_search,
value = search,
onValueChange = {
search.value = it
}
),
gender = DropDownFieldUio(
label = R.string.search_field_gender,
values = genders(),
value = gender,
onValueChange = { id, value -> gender.value = id to value },
),
race = DropDownFieldUio(
label = R.string.search_field_race,
values = races(),
value = race,
onValueChange = { id, value -> race.value = id to value },
),
)
private var data: List<Lexicon> = repository.data.value
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 search = search.value.takeIf { it.isNotEmpty() }?.let {
val name = item.name.contains(search.value, true)
val diminutive = item.diminutive?.contains(search.value, true) == true
val description = item.description?.contains(search.value, true) == true
val history = item.history?.contains(search.value, true) == true
name || diminutive || description || history
}
(gender == null || gender) && (race == null || race) && (search == null || search)
}.map {
LexiconItemUio(
id = it.id,
name = it.name,
diminutive = it.diminutive?.takeIf { it.isNotBlank() }?.let { "./ $it" },
gender = when (it.gender) {
Gender.MALE -> R.string.gender_male_short
Gender.FEMALE -> R.string.gender_female_short
Gender.UNDETERMINED -> R.string.gender_undetermined_short
},
race = when (it.race) {
Race.ELF -> R.string.race_elf
Race.HALFLING -> R.string.race_halfling
Race.HUMAN -> R.string.race_human
Race.DWARF -> R.string.race_dwarf
Race.HALF_ELF -> R.string.race_half_elf
Race.HALF_ORC -> R.string.race_half_orc
Race.DRAGONBORN -> R.string.race_dragonborn
Race.GNOME -> R.string.race_gnome
Race.TIEFLING -> R.string.race_tiefling
Race.AARAKOCRA -> R.string.race_aarakocra
Race.GENASI -> R.string.race_genasi
Race.DEEP_GNOME -> R.string.race_deep_gnome
Race.GOLIATH -> R.string.race_goliath
Race.UNDETERMINED -> R.string.race_undetermined
},
)
}
.sortedBy { it.name }
}
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,
)
}
}

View file

@ -12,6 +12,11 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import com.pixelized.rplexicon.ui.theme.colors.LexiconColors
import com.pixelized.rplexicon.ui.theme.colors.darkColorScheme
import com.pixelized.rplexicon.ui.theme.colors.lightColorScheme
import com.pixelized.rplexicon.ui.theme.shape.LexiconShapes
import com.pixelized.rplexicon.ui.theme.shape.lexiconShapes
val LocalLexiconTheme = compositionLocalOf<LexiconTheme> {
error("LocalLexiconTheme not ready yet.")
@ -20,6 +25,7 @@ val LocalLexiconTheme = compositionLocalOf<LexiconTheme> {
@Stable
data class LexiconTheme(
val colorScheme: LexiconColors,
val shapes: LexiconShapes,
)
@Composable
@ -32,7 +38,8 @@ fun LexiconTheme(
colorScheme = when (darkTheme) {
true -> darkColorScheme()
else -> lightColorScheme()
}
},
shapes = lexiconShapes()
)
}
@ -40,10 +47,8 @@ fun LexiconTheme(
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
lexiconTheme.colorScheme.base.background.toArgb().let {
window.statusBarColor = it
window.navigationBarColor = it
}
window.statusBarColor = lexiconTheme.colorScheme.status.toArgb()
window.navigationBarColor = lexiconTheme.colorScheme.navigation.toArgb()
WindowCompat.getInsetsController(window, view).let {
it.isAppearanceLightStatusBars = !darkTheme
it.isAppearanceLightNavigationBars = !darkTheme
@ -56,6 +61,7 @@ fun LexiconTheme(
) {
MaterialTheme(
colorScheme = lexiconTheme.colorScheme.base,
shapes = lexiconTheme.shapes.base,
typography = Typography,
content = content
)

View file

@ -1,4 +1,4 @@
package com.pixelized.rplexicon.ui.theme
package com.pixelized.rplexicon.ui.theme.colors
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
@ -6,13 +6,13 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Color
import com.pixelized.rplexicon.ui.theme.colors.BaseDark
import com.pixelized.rplexicon.ui.theme.colors.BaseLight
@Stable
@Immutable
class LexiconColors(
val base: ColorScheme,
val status: Color,
val navigation: Color,
val placeholder: Color,
)
@ -24,9 +24,13 @@ fun darkColorScheme(
tertiary = BaseDark.Pink80,
onPrimary = Color.White,
),
status: Color = Color.Transparent,
navigation: Color = Color.Transparent,
placeholder: Color = Color(red = 49, green = 48, blue = 51),
) = LexiconColors(
base = base,
status = status,
navigation = navigation,
placeholder = placeholder,
)
@ -38,8 +42,12 @@ fun lightColorScheme(
tertiary = BaseLight.Pink40,
onPrimary = Color.White,
),
status: Color = Color.Transparent,
navigation: Color = Color.Transparent,
placeholder: Color = Color(red = 230, green = 225, blue = 229),
) = LexiconColors(
base = base,
status = status,
navigation = navigation,
placeholder = placeholder,
)

View file

@ -0,0 +1,21 @@
package com.pixelized.rplexicon.ui.theme.shape
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Shapes
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.Shape
@Stable
class LexiconShapes(
val base: Shapes,
val textField: Shape
)
@Stable
fun lexiconShapes(
base: Shapes = Shapes(),
textField: Shape = CircleShape,
) = LexiconShapes(
base = base,
textField = textField,
)

View file

@ -0,0 +1,40 @@
package com.pixelized.rplexicon.utilitary
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalView
@Stable
class GlideLoadingTransition(
target: MutableState<Float>,
alpha: State<Float>
) {
var target by target
val alpha by alpha
}
@Composable
fun rememberLoadingTransition(model: () -> Any?): GlideLoadingTransition {
val isInEditMode = LocalView.current.isInEditMode
val key = model()
val target = remember(key) {
mutableStateOf(if (isInEditMode) 1f else 0f)
}
val alpha = animateFloatAsState(
targetValue = target.value,
label = "RememberLoadingTransition"
)
return remember(key) {
GlideLoadingTransition(
target = target,
alpha = alpha,
)
}
}

View file

@ -1,5 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -28,8 +28,15 @@
<string name="action_google_sign_in">Se connecter avec</string>
<string name="lexicon_search">Rechercher</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_history">Histoire</string>
<string name="detail_portrait">Portrait</string>
<string name="search_field_title">Rechercher</string>
<string name="search_field_search">Rechercher</string>
<string name="search_field_race">Race</string>
<string name="search_field_gender">Sexe</string>
</resources>

View file

@ -28,8 +28,15 @@
<string name="action_google_sign_in">Sign in with</string>
<string name="lexicon_search">Search</string>
<string name="detail_title">Character\'s details</string>
<string name="detail_description">Description</string>
<string name="detail_history">Histoire</string>
<string name="detail_history">History</string>
<string name="detail_portrait">Portrait</string>
<string name="search_field_title">Search</string>
<string name="search_field_search">Search</string>
<string name="search_field_race">Race</string>
<string name="search_field_gender">Gender</string>
</resources>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Lexique" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.Lexicon.Starting" parent="Theme.SplashScreen">
<item name="postSplashScreenTheme">@style/Theme.Lexicon.NoActionBar</item>
</style>
<style name="Theme.Lexicon.NoActionBar" parent="android:Theme.Material.Light.NoActionBar" />
</resources>