Small UX fix.
Trim on search. keyboard action done focus. maxline on search. collapsing header on search. remove keyboard hide on search scroll.
This commit is contained in:
parent
99f7546621
commit
82738a8f03
9 changed files with 303 additions and 113 deletions
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.pixelized.rplexicon.ui.composable
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.pixelized.rplexicon.utilitary.rememberLoadingTransition
|
||||||
|
import com.skydoves.landscapist.ImageOptions
|
||||||
|
import com.skydoves.landscapist.InternalLandscapistApi
|
||||||
|
import com.skydoves.landscapist.components.ImageComponent
|
||||||
|
import com.skydoves.landscapist.components.rememberImageComponent
|
||||||
|
import com.skydoves.landscapist.glide.GlideImage
|
||||||
|
import com.skydoves.landscapist.glide.GlideImageState
|
||||||
|
import com.skydoves.landscapist.glide.GlideRequestType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AsyncImage(
|
||||||
|
imageModel: () -> Any?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
glideRequestType: GlideRequestType = GlideRequestType.DRAWABLE,
|
||||||
|
requestListener: (() -> RequestListener<Any>)? = null,
|
||||||
|
component: ImageComponent = rememberImageComponent {},
|
||||||
|
imageOptions: ImageOptions = ImageOptions(),
|
||||||
|
onImageStateChanged: (GlideImageState) -> Unit = {},
|
||||||
|
@DrawableRes previewPlaceholder: Int = 0,
|
||||||
|
loading: @Composable (BoxScope.(imageState: GlideImageState.Loading) -> Unit)? = null,
|
||||||
|
success: @Composable (BoxScope.(imageState: GlideImageState.Success, painter: Painter) -> Unit)? = null,
|
||||||
|
failure: @Composable (BoxScope.(imageState: GlideImageState.Failure) -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val transition = rememberLoadingTransition(imageModel)
|
||||||
|
|
||||||
|
GlideImage(
|
||||||
|
imageModel = imageModel,
|
||||||
|
modifier = modifier.alpha(alpha = transition.alpha),
|
||||||
|
glideRequestType = glideRequestType,
|
||||||
|
requestListener = requestListener,
|
||||||
|
component = component,
|
||||||
|
imageOptions = imageOptions,
|
||||||
|
onImageStateChanged = {
|
||||||
|
when (it) {
|
||||||
|
is GlideImageState.Success -> transition.target = 1f
|
||||||
|
is GlideImageState.Failure -> transition.target = 1f
|
||||||
|
is GlideImageState.Loading -> transition.target = 0f
|
||||||
|
is GlideImageState.None -> transition.target = 0f
|
||||||
|
}
|
||||||
|
onImageStateChanged(it)
|
||||||
|
},
|
||||||
|
previewPlaceholder = previewPlaceholder,
|
||||||
|
loading = loading,
|
||||||
|
success = success,
|
||||||
|
failure = failure,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.pixelized.rplexicon.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateIntAsState
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
|
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.layout.Layout
|
||||||
|
import androidx.compose.ui.layout.Measurable
|
||||||
|
import androidx.compose.ui.layout.layoutId
|
||||||
|
import androidx.compose.ui.unit.Velocity
|
||||||
|
|
||||||
|
private val List<Measurable>.grid: Measurable get() = first { it.layoutId == GRID_ID }
|
||||||
|
private val List<Measurable>.header: Measurable get() = first { it.layoutId == HEADER_ID }
|
||||||
|
|
||||||
|
private const val GRID_ID = "GRID_ID"
|
||||||
|
private const val HEADER_ID = "HEADER_ID"
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CollapsingHeader(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
header: @Composable () -> Unit,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val headerHeight = rememberSaveable { mutableStateOf(0) }
|
||||||
|
val headerScroll = rememberSaveable { mutableStateOf(0) }
|
||||||
|
|
||||||
|
val animatedHeaderScroll = animateIntAsState(
|
||||||
|
targetValue = headerScroll.value,
|
||||||
|
label = "HeaderSnapAnimation",
|
||||||
|
)
|
||||||
|
|
||||||
|
val nestedScrollConnection = remember {
|
||||||
|
object : NestedScrollConnection {
|
||||||
|
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||||
|
val scroll = (headerScroll.value + available.y.toInt()).coerceIn(
|
||||||
|
minimumValue = -headerHeight.value,
|
||||||
|
maximumValue = 0,
|
||||||
|
)
|
||||||
|
return if (headerScroll.value != scroll) {
|
||||||
|
headerScroll.value = scroll
|
||||||
|
available
|
||||||
|
} else {
|
||||||
|
Offset.Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
|
||||||
|
headerScroll.value = if (headerScroll.value < -headerHeight.value / 2) {
|
||||||
|
-headerHeight.value
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
return super.onPostFling(consumed, available)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout(
|
||||||
|
modifier = modifier.nestedScroll(nestedScrollConnection),
|
||||||
|
content = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.layoutId(HEADER_ID),
|
||||||
|
content = { header() },
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.layoutId(GRID_ID),
|
||||||
|
content = { content() },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
measurePolicy = { measurables, constraints ->
|
||||||
|
val headerPlaceable = measurables.header.measure(constraints)
|
||||||
|
val gridPlaceable = measurables.grid.measure(constraints)
|
||||||
|
|
||||||
|
if (headerHeight.value == 0) {
|
||||||
|
headerHeight.value = headerPlaceable.height
|
||||||
|
}
|
||||||
|
|
||||||
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
|
headerPlaceable.place(0, animatedHeaderScroll.value)
|
||||||
|
gridPlaceable.place(0, headerPlaceable.height + animatedHeaderScroll.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
package com.pixelized.rplexicon.ui.composable
|
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
|
||||||
@Composable
|
|
||||||
fun ScrollingKeyboardHandler(
|
|
||||||
lazyListState: LazyListState
|
|
||||||
) {
|
|
||||||
val keyboard = LocalSoftwareKeyboardController.current
|
|
||||||
|
|
||||||
if (lazyListState.isScrollInProgress) {
|
|
||||||
keyboard?.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,6 +9,8 @@ import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|
@ -22,8 +24,11 @@ import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
|
@ -52,6 +57,8 @@ fun TextField(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
field: TextFieldUio,
|
field: TextFieldUio,
|
||||||
) {
|
) {
|
||||||
|
val focus = LocalFocusManager.current
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = MaterialTheme.lexicon.shapes.textField,
|
shape = MaterialTheme.lexicon.shapes.textField,
|
||||||
|
|
@ -77,6 +84,14 @@ fun TextField(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Text,
|
||||||
|
imeAction = ImeAction.Done,
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onDone = { focus.clearFocus(true) },
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
value = field.value.value,
|
value = field.value.value,
|
||||||
onValueChange = field.onValueChange,
|
onValueChange = field.onValueChange,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
@ -48,6 +47,7 @@ import androidx.compose.ui.graphics.Shadow
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
|
@ -59,16 +59,14 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.model.Lexicon
|
import com.pixelized.rplexicon.model.Lexicon
|
||||||
|
import com.pixelized.rplexicon.ui.composable.AsyncImage
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
|
import com.pixelized.rplexicon.utilitary.composable.stringResource
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
|
import com.pixelized.rplexicon.utilitary.extentions.annotatedSpan
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.annotatedString
|
import com.pixelized.rplexicon.utilitary.extentions.annotatedString
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
|
import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
|
||||||
import com.pixelized.rplexicon.utilitary.rememberLoadingTransition
|
|
||||||
import com.skydoves.landscapist.ImageOptions
|
import com.skydoves.landscapist.ImageOptions
|
||||||
import com.skydoves.landscapist.glide.GlideImage
|
|
||||||
import com.skydoves.landscapist.glide.GlideImageState
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class CharacterDetailUio(
|
data class CharacterDetailUio(
|
||||||
|
|
@ -200,16 +198,8 @@ private fun CharacterDetailScreenContent(
|
||||||
.aspectRatio(ratio = 1f)
|
.aspectRatio(ratio = 1f)
|
||||||
.scrollOffset(scrollState = state) { -it / 2 },
|
.scrollOffset(scrollState = state) { -it / 2 },
|
||||||
) {
|
) {
|
||||||
val transition = rememberLoadingTransition { uri }
|
AsyncImage(
|
||||||
GlideImage(
|
modifier = Modifier.matchParentSize(),
|
||||||
modifier = Modifier
|
|
||||||
.matchParentSize()
|
|
||||||
.alpha(alpha = transition.alpha),
|
|
||||||
onImageStateChanged = {
|
|
||||||
if (it is GlideImageState.Success) {
|
|
||||||
transition.target = 1f
|
|
||||||
}
|
|
||||||
},
|
|
||||||
imageOptions = ImageOptions(
|
imageOptions = ImageOptions(
|
||||||
alignment = Alignment.TopCenter,
|
alignment = Alignment.TopCenter,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
|
|
@ -299,8 +289,7 @@ private fun CharacterDetailScreenContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (annotatedItem.portrait.isNotEmpty()) {
|
if (annotatedItem.portrait.isNotEmpty()) {
|
||||||
val configuration = LocalConfiguration.current
|
val maxSize = rememberPortraitWidth()
|
||||||
val maxSize = remember { (configuration.screenWidthDp.dp - 16.dp * 2) }
|
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
|
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
|
||||||
style = typography.titleMedium,
|
style = typography.titleMedium,
|
||||||
|
|
@ -308,19 +297,16 @@ private fun CharacterDetailScreenContent(
|
||||||
)
|
)
|
||||||
LazyRow(
|
LazyRow(
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
items(items = annotatedItem.portrait) {
|
items(items = annotatedItem.portrait) {
|
||||||
val transition = rememberLoadingTransition { it }
|
AsyncImage(
|
||||||
GlideImage(
|
modifier = Modifier.sizeIn(
|
||||||
modifier = Modifier
|
minWidth = maxSize / 2,
|
||||||
.sizeIn(maxWidth = maxSize, maxHeight = maxSize)
|
maxWidth = maxSize,
|
||||||
.alpha(alpha = transition.alpha),
|
minHeight = maxSize,
|
||||||
onImageStateChanged = {
|
maxHeight = maxSize,
|
||||||
if (it is GlideImageState.Success) {
|
),
|
||||||
transition.target = 1f
|
|
||||||
}
|
|
||||||
},
|
|
||||||
imageOptions = ImageOptions(
|
imageOptions = ImageOptions(
|
||||||
contentScale = ContentScale.FillHeight
|
contentScale = ContentScale.FillHeight
|
||||||
),
|
),
|
||||||
|
|
@ -348,6 +334,19 @@ private fun rememberBackgroundGradient(): Brush {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun rememberPortraitWidth(): Dp {
|
||||||
|
val configuration = LocalConfiguration.current
|
||||||
|
val view = LocalView.current
|
||||||
|
return remember(configuration, view) {
|
||||||
|
if (view.isInEditMode) {
|
||||||
|
300.dp
|
||||||
|
} else {
|
||||||
|
(configuration.screenWidthDp.dp - 16.dp * 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private fun Modifier.scrollOffset(
|
private fun Modifier.scrollOffset(
|
||||||
scrollState: ScrollState,
|
scrollState: ScrollState,
|
||||||
|
|
|
||||||
|
|
@ -99,12 +99,13 @@ class AnnotatedSearchItemUio(
|
||||||
private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
|
private fun SearchItemUio.annotate(): AnnotatedSearchItemUio {
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
val highlight = remember { SpanStyle(color = colorScheme.primary) }
|
val highlight = remember { SpanStyle(color = colorScheme.primary) }
|
||||||
val highlightRegex = remember(search) { search.highlightRegex }
|
val trimmedSearch = remember(search) { search.trim() }
|
||||||
val finderRegex = remember(search) { search.finderRegex }
|
val highlightRegex = remember(search) { trimmedSearch.highlightRegex }
|
||||||
|
val finderRegex = remember(search) { trimmedSearch.finderRegex }
|
||||||
val gender = stringResource(id = gender, short = true)
|
val gender = stringResource(id = gender, short = true)
|
||||||
val race = stringResource(id = race)
|
val race = stringResource(id = race)
|
||||||
|
|
||||||
return remember(search, race, highlightRace, gender, highlightGender) {
|
return remember(trimmedSearch, race, highlightRace, gender, highlightGender) {
|
||||||
AnnotatedSearchItemUio(
|
AnnotatedSearchItemUio(
|
||||||
id = id,
|
id = id,
|
||||||
name = AnnotatedString(
|
name = AnnotatedString(
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.model.Lexicon
|
import com.pixelized.rplexicon.model.Lexicon
|
||||||
import com.pixelized.rplexicon.ui.composable.ScrollingKeyboardHandler
|
import com.pixelized.rplexicon.ui.composable.CollapsingHeader
|
||||||
import com.pixelized.rplexicon.ui.composable.form.DropDownField
|
import com.pixelized.rplexicon.ui.composable.form.DropDownField
|
||||||
import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
|
import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
|
||||||
import com.pixelized.rplexicon.ui.composable.form.TextField
|
import com.pixelized.rplexicon.ui.composable.form.TextField
|
||||||
|
|
@ -88,9 +88,6 @@ fun SearchScreen(
|
||||||
screen.popBackStack()
|
screen.popBackStack()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ScrollingKeyboardHandler(
|
|
||||||
lazyListState = lazyState,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,16 +122,45 @@ private fun SearchScreenContent(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
CollapsingHeader(
|
||||||
|
modifier = Modifier.padding(paddingValues = paddingValues),
|
||||||
|
header = {
|
||||||
|
SearchBox(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
form = form,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues = paddingValues),
|
|
||||||
state = lazyColumnState,
|
state = lazyColumnState,
|
||||||
contentPadding = PaddingValues(vertical = 8.dp),
|
contentPadding = PaddingValues(vertical = 8.dp),
|
||||||
) {
|
) {
|
||||||
item {
|
items(
|
||||||
|
items = items.value,
|
||||||
|
key = { it.id },
|
||||||
|
contentType = { "Search" },
|
||||||
|
) {
|
||||||
|
SearchItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { onItem(it) }
|
||||||
|
.heightIn(min = MaterialTheme.lexicon.dimens.item),
|
||||||
|
item = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SearchBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
form: SearchFormUio,
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = modifier,
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
TextField(
|
TextField(
|
||||||
|
|
@ -155,21 +181,6 @@ private fun SearchScreenContent(
|
||||||
}
|
}
|
||||||
Divider(modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
|
Divider(modifier = Modifier.padding(top = 16.dp, bottom = 8.dp))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
items(
|
|
||||||
items = items.value,
|
|
||||||
key = { it.id },
|
|
||||||
contentType = { "Search" },
|
|
||||||
) {
|
|
||||||
SearchItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable { onItem(it) }
|
|
||||||
.heightIn(min = MaterialTheme.lexicon.dimens.item),
|
|
||||||
item = it,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -196,18 +207,41 @@ private fun SearchScreenContentPreview() {
|
||||||
items = remember {
|
items = remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
listOf(
|
listOf(
|
||||||
SearchItemUio(
|
SearchItemUio.preview(
|
||||||
id = 0,
|
id = 0,
|
||||||
name = "Brulkhai",
|
name = "Brulkhai",
|
||||||
diminutive = "Bru",
|
diminutive = "Bru",
|
||||||
gender = Lexicon.Gender.FEMALE,
|
gender = Lexicon.Gender.FEMALE,
|
||||||
race = Lexicon.Race.HALF_ORC,
|
race = Lexicon.Race.HALF_ORC,
|
||||||
description = null,
|
),
|
||||||
history = null,
|
SearchItemUio.preview(
|
||||||
search = "",
|
id = 0,
|
||||||
highlightGender = false,
|
name = "Léandre",
|
||||||
highlightRace = false,
|
diminutive = null,
|
||||||
)
|
gender = Lexicon.Gender.MALE,
|
||||||
|
race = Lexicon.Race.HUMAN,
|
||||||
|
),
|
||||||
|
SearchItemUio.preview(
|
||||||
|
id = 0,
|
||||||
|
name = "Nélia",
|
||||||
|
diminutive = null,
|
||||||
|
gender = Lexicon.Gender.FEMALE,
|
||||||
|
race = Lexicon.Race.ELF,
|
||||||
|
),
|
||||||
|
SearchItemUio.preview(
|
||||||
|
id = 0,
|
||||||
|
name = "Tigrane",
|
||||||
|
diminutive = null,
|
||||||
|
gender = Lexicon.Gender.MALE,
|
||||||
|
race = Lexicon.Race.TIEFLING,
|
||||||
|
),
|
||||||
|
SearchItemUio.preview(
|
||||||
|
id = 0,
|
||||||
|
name = "Unathana",
|
||||||
|
diminutive = "Una",
|
||||||
|
gender = Lexicon.Gender.FEMALE,
|
||||||
|
race = Lexicon.Race.HALF_ELF,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,11 @@ class SearchViewModel @Inject constructor(
|
||||||
data.filter { item ->
|
data.filter { item ->
|
||||||
val gender = _gender.value?.let { it == item.gender }
|
val gender = _gender.value?.let { it == item.gender }
|
||||||
val race = _race.value?.let { it == item.race }
|
val race = _race.value?.let { it == item.race }
|
||||||
val search = _search.value.takeIf { it.isNotEmpty() }?.let {
|
val search = _search.value.takeIf { it.isNotEmpty() }?.trim()?.let { search ->
|
||||||
val name = item.name.contains(_search.value, true)
|
val name = item.name.contains(search, true)
|
||||||
val diminutive = item.diminutive?.contains(_search.value, true) == true
|
val diminutive = item.diminutive?.contains(search, true) == true
|
||||||
val description = item.description?.contains(_search.value, true) == true
|
val description = item.description?.contains(search, true) == true
|
||||||
val history = item.history?.contains(_search.value, true) == true
|
val history = item.history?.contains(search, true) == true
|
||||||
name || diminutive || description || history
|
name || diminutive || description || history
|
||||||
}
|
}
|
||||||
(gender == null || gender) && (race == null || race) && (search == null || search)
|
(gender == null || gender) && (race == null || race) && (search == null || search)
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,28 @@ class GlideLoadingTransition(
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberLoadingTransition(model: () -> Any?): GlideLoadingTransition {
|
fun rememberLoadingTransition(model: () -> Any?): GlideLoadingTransition {
|
||||||
val isInEditMode = LocalView.current.isInEditMode
|
val isInEditMode = LocalView.current.isInEditMode
|
||||||
|
|
||||||
|
return if (isInEditMode) {
|
||||||
|
remember {
|
||||||
|
GlideLoadingTransition(
|
||||||
|
target = mutableStateOf(1f),
|
||||||
|
alpha = mutableStateOf(1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
val key = model()
|
val key = model()
|
||||||
val target = remember(key) {
|
val target = remember(key) {
|
||||||
mutableStateOf(if (isInEditMode) 1f else 0f)
|
mutableStateOf(0f)
|
||||||
}
|
}
|
||||||
val alpha = animateFloatAsState(
|
val alpha = animateFloatAsState(
|
||||||
targetValue = target.value,
|
targetValue = target.value,
|
||||||
label = "RememberLoadingTransition"
|
label = "LoadingAlphaTransition"
|
||||||
)
|
)
|
||||||
return remember(key) {
|
remember(key) {
|
||||||
GlideLoadingTransition(
|
GlideLoadingTransition(
|
||||||
target = target,
|
target = target,
|
||||||
alpha = alpha,
|
alpha = alpha,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue