diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 07eb31b..fbcf239 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -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(
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 16743da..6f1b495 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,19 +9,23 @@
+
+ android:screenOrientation="portrait"
+ android:theme="@style/Theme.Lexicon.Starting"
+ android:windowSoftInputMode="adjustResize">
@@ -29,5 +33,4 @@
-
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt b/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt
index 8743622..2bcd803 100644
--- a/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/MainActivity.kt
@@ -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 { error("Activity not available") }
-val LocalSnack =
- staticCompositionLocalOf { error("SnackbarHostState not available") }
+val LocalActivity = staticCompositionLocalOf {
+ error("Activity not available")
+}
+val LocalSnack = staticCompositionLocalOf {
+ 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
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/FloatingActionButton.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/FloatingActionButton.kt
index b82235d..2e1fcbb 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/composable/FloatingActionButton.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/FloatingActionButton.kt
@@ -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(
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt
new file mode 100644
index 0000000..868b47f
--- /dev/null
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/DropDownField.kt
@@ -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(
+ @StringRes val label: Int,
+ val values: List>,
+ val value: State>,
+ val onValueChange: (T?, String) -> Unit,
+) {
+ companion object {
+ fun 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 DropDownField(
+ modifier: Modifier = Modifier,
+ field: DropDownFieldUio,
+) {
+ 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,
+) {
+ 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> {
+ override val values: Sequence> = sequenceOf(null to "", "" to "preview")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextField.kt b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextField.kt
new file mode 100644
index 0000000..7cce867
--- /dev/null
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/composable/form/TextField.kt
@@ -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,
+ 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 {
+ override val values: Sequence = sequenceOf("", "preview")
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt
index 42a8d9e..8c58235 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/authentication/AuthenticationScreen.kt
@@ -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())
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/detail/CharacterDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/detail/CharacterDetailScreen.kt
index 7609cef..8952d21 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/detail/CharacterDetailScreen.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/detail/CharacterDetailScreen.kt
@@ -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,
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 lorsqu’elle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. D’un 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 qu’elle considère plus faibles qu’elle. D’une 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 d’un manque d’éducation et d’une capacité limitée à gérer ses émotions qu’à une débilité congénitale. Elle voue à la force un culte car c’est par son expression qu’elle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux qu’elle nomme foshnu (bébé, chouineur en commun).",
history = null,
@@ -304,7 +312,6 @@ private fun CharacterDetailScreenContentPreview() {
modifier = Modifier.fillMaxSize(),
item = character,
onBack = { },
- onImage = { },
)
}
}
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconItem.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconItem.kt
index 32ec9d9..03f62d2 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconItem.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconItem.kt
@@ -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",
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconScreen.kt
index 449cf66..135fccd 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconScreen.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/LexiconScreen.kt
@@ -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,
)
}
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchScreen.kt
index 70bac91..4efdcb9 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchScreen.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchScreen.kt
@@ -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,
+ val race: DropDownFieldUio,
+)
+
@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>,
+ 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,
- subject: State,
- onChange: (String) -> Unit,
- expanded: State,
- 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 = { },
)
}
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchViewModel.kt
new file mode 100644
index 0000000..78f44dd
--- /dev/null
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/search/SearchViewModel.kt
@@ -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>(null to "")
+ private val race = mutableStateOf>(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 = 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,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt
index be7a62c..99cec13 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/Theme.kt
@@ -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 {
error("LocalLexiconTheme not ready yet.")
@@ -20,6 +25,7 @@ val LocalLexiconTheme = compositionLocalOf {
@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
)
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Color.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/colors/LexiconColors.kt
similarity index 75%
rename from app/src/main/java/com/pixelized/rplexicon/ui/theme/Color.kt
rename to app/src/main/java/com/pixelized/rplexicon/ui/theme/colors/LexiconColors.kt
index fbb9210..4f37718 100644
--- a/app/src/main/java/com/pixelized/rplexicon/ui/theme/Color.kt
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/colors/LexiconColors.kt
@@ -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,
)
diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/theme/shape/LexiconShapes.kt b/app/src/main/java/com/pixelized/rplexicon/ui/theme/shape/LexiconShapes.kt
new file mode 100644
index 0000000..a25ecc5
--- /dev/null
+++ b/app/src/main/java/com/pixelized/rplexicon/ui/theme/shape/LexiconShapes.kt
@@ -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,
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/GlideHelp.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/GlideHelp.kt
new file mode 100644
index 0000000..2ae29e1
--- /dev/null
+++ b/app/src/main/java/com/pixelized/rplexicon/utilitary/GlideHelp.kt
@@ -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,
+ alpha: State
+) {
+ 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,
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml b/app/src/main/res/drawable/ic_arrow_down_24.xml
similarity index 79%
rename from app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml
rename to app/src/main/res/drawable/ic_arrow_down_24.xml
index c1c897a..9e345b8 100644
--- a/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml
+++ b/app/src/main/res/drawable/ic_arrow_down_24.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/app/src/main/res/drawable/ic_clear_24.xml b/app/src/main/res/drawable/ic_clear_24.xml
new file mode 100644
index 0000000..844b6b6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_clear_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 60e24f5..8d94de1 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -28,8 +28,15 @@
Se connecter avec
+ Rechercher
+
Détails du personnage
Description
- History
+ Histoire
Portrait
+
+ Rechercher
+ Rechercher
+ Race
+ Sexe
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bd3828e..745a89a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -28,8 +28,15 @@
Sign in with
+ Search
+
Character\'s details
Description
- Histoire
+ History
Portrait
+
+ Search
+ Search
+ Race
+ Gender
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index bfa0207..53b5e00 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,9 @@
-
+
+
+
\ No newline at end of file