QueryEditor
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
package xyz.quaver.pupil.ui.composable
|
package xyz.quaver.pupil.ui.composable
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.AnimatedContent
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
@@ -31,10 +29,8 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.AddCircleOutline
|
|
||||||
import androidx.compose.material.icons.filled.Book
|
import androidx.compose.material.icons.filled.Book
|
||||||
import androidx.compose.material.icons.filled.Brush
|
import androidx.compose.material.icons.filled.Brush
|
||||||
import androidx.compose.material.icons.filled.Face
|
import androidx.compose.material.icons.filled.Face
|
||||||
@@ -43,12 +39,9 @@ import androidx.compose.material.icons.filled.Folder
|
|||||||
import androidx.compose.material.icons.filled.Group
|
import androidx.compose.material.icons.filled.Group
|
||||||
import androidx.compose.material.icons.filled.LocalOffer
|
import androidx.compose.material.icons.filled.LocalOffer
|
||||||
import androidx.compose.material.icons.filled.Male
|
import androidx.compose.material.icons.filled.Male
|
||||||
import androidx.compose.material.icons.filled.RemoveCircleOutline
|
|
||||||
import androidx.compose.material.icons.filled.Translate
|
import androidx.compose.material.icons.filled.Translate
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardColors
|
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
@@ -80,11 +73,8 @@ import xyz.quaver.pupil.R
|
|||||||
import xyz.quaver.pupil.networking.GalleryInfo
|
import xyz.quaver.pupil.networking.GalleryInfo
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.networking.SearchQueryPreviewParameterProvider
|
import xyz.quaver.pupil.networking.SearchQueryPreviewParameterProvider
|
||||||
import xyz.quaver.pupil.ui.theme.Blue300
|
|
||||||
import xyz.quaver.pupil.ui.theme.Blue600
|
import xyz.quaver.pupil.ui.theme.Blue600
|
||||||
import xyz.quaver.pupil.ui.theme.Gray300
|
|
||||||
import xyz.quaver.pupil.ui.theme.Pink600
|
import xyz.quaver.pupil.ui.theme.Pink600
|
||||||
import xyz.quaver.pupil.ui.theme.Red300
|
|
||||||
import xyz.quaver.pupil.ui.theme.Yellow400
|
import xyz.quaver.pupil.ui.theme.Yellow400
|
||||||
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
import xyz.quaver.pupil.ui.viewmodel.MainUIState
|
||||||
|
|
||||||
@@ -102,7 +92,7 @@ private val iconMap = mapOf(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TagChipIcon(tag: SearchQuery.Tag) {
|
fun TagChipIcon(tag: SearchQuery.Tag) {
|
||||||
val icon = iconMap[tag.namespace ?: "tag"]
|
val icon = iconMap[tag.namespace]
|
||||||
|
|
||||||
if (icon != null)
|
if (icon != null)
|
||||||
Icon(
|
Icon(
|
||||||
@@ -122,9 +112,9 @@ fun TagChip(
|
|||||||
isFavorite: Boolean = false,
|
isFavorite: Boolean = false,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
onClick: (SearchQuery.Tag) -> Unit = { },
|
onClick: (SearchQuery.Tag) -> Unit = { },
|
||||||
leftIcon: @Composable (SearchQuery.Tag) -> Unit = { tag -> TagChipIcon(tag) },
|
leftIcon: @Composable (SearchQuery.Tag) -> Unit = { TagChipIcon(it) },
|
||||||
rightIcon: @Composable (SearchQuery.Tag) -> Unit = { _ -> Spacer(Modifier.width(16.dp)) },
|
rightIcon: @Composable (SearchQuery.Tag) -> Unit = { Spacer(Modifier.width(16.dp)) },
|
||||||
content: @Composable (SearchQuery.Tag) -> Unit = { tag -> Text(tag.tag) },
|
content: @Composable (SearchQuery.Tag) -> Unit = { Text(it.tag) },
|
||||||
) {
|
) {
|
||||||
val surfaceColor = if (isFavorite) Yellow400 else when (tag.namespace) {
|
val surfaceColor = if (isFavorite) Yellow400 else when (tag.namespace) {
|
||||||
"male" -> Blue600
|
"male" -> Blue600
|
||||||
@@ -176,7 +166,7 @@ fun TagChip(
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun QueryView(
|
fun QueryView(
|
||||||
@PreviewParameter(SearchQueryPreviewParameterProvider::class) query: SearchQuery?,
|
@PreviewParameter(SearchQueryPreviewParameterProvider::class) query: SearchQuery,
|
||||||
topLevel: Boolean = true
|
topLevel: Boolean = true
|
||||||
) {
|
) {
|
||||||
val modifier = if (topLevel) {
|
val modifier = if (topLevel) {
|
||||||
@@ -301,6 +291,8 @@ fun SearchBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidx.compose.animation.AnimatedVisibility(focused, enter = fadeIn(), exit = fadeOut()) {
|
androidx.compose.animation.AnimatedVisibility(focused, enter = fadeIn(), exit = fadeOut()) {
|
||||||
|
val state = remember(query) { query.toEditableState() }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -315,10 +307,7 @@ fun SearchBar(
|
|||||||
contentDescription = "close search bar"
|
contentDescription = "close search bar"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
QueryEditor(
|
QueryEditor(state = state)
|
||||||
query = query,
|
|
||||||
onQueryChange = onQueryChange
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
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.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@@ -19,12 +21,14 @@ import androidx.compose.material3.Card
|
|||||||
import androidx.compose.material3.CardColors
|
import androidx.compose.material3.CardColors
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -38,21 +42,154 @@ import androidx.compose.ui.unit.dp
|
|||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.networking.SearchQuery
|
import xyz.quaver.pupil.networking.SearchQuery
|
||||||
import xyz.quaver.pupil.ui.theme.Blue300
|
import xyz.quaver.pupil.ui.theme.Blue300
|
||||||
|
import xyz.quaver.pupil.ui.theme.Blue600
|
||||||
import xyz.quaver.pupil.ui.theme.Gray300
|
import xyz.quaver.pupil.ui.theme.Gray300
|
||||||
|
import xyz.quaver.pupil.ui.theme.Pink600
|
||||||
import xyz.quaver.pupil.ui.theme.Red300
|
import xyz.quaver.pupil.ui.theme.Red300
|
||||||
|
import xyz.quaver.pupil.ui.theme.Yellow400
|
||||||
|
|
||||||
|
private fun SearchQuery.toEditableStateInternal(): EditableSearchQueryState = when (this) {
|
||||||
|
is SearchQuery.Tag -> EditableSearchQueryState.Tag(namespace, tag)
|
||||||
|
is SearchQuery.And -> EditableSearchQueryState.And(queries.map { it.toEditableStateInternal() })
|
||||||
|
is SearchQuery.Or -> EditableSearchQueryState.Or(queries.map { it.toEditableStateInternal() })
|
||||||
|
is SearchQuery.Not -> EditableSearchQueryState.Not(query.toEditableStateInternal())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SearchQuery?.toEditableState(): EditableSearchQueryState.Root
|
||||||
|
= EditableSearchQueryState.Root(this?.toEditableStateInternal())
|
||||||
|
|
||||||
|
private fun EditableSearchQueryState.toSearchQueryInternal(): SearchQuery? = when (this) {
|
||||||
|
is EditableSearchQueryState.Tag -> SearchQuery.Tag(namespace.value, tag.value)
|
||||||
|
is EditableSearchQueryState.And -> SearchQuery.And(queries.mapNotNull { it.toSearchQueryInternal() })
|
||||||
|
is EditableSearchQueryState.Or -> SearchQuery.Or(queries.mapNotNull { it.toSearchQueryInternal() })
|
||||||
|
is EditableSearchQueryState.Not -> query.value?.toSearchQueryInternal()?.let { SearchQuery.Not(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun EditableSearchQueryState.Root.toSearchQuery(): SearchQuery?
|
||||||
|
= query.value?.toSearchQueryInternal()
|
||||||
|
|
||||||
|
fun coalesceTags(oldTag: EditableSearchQueryState.Tag?, newTag: EditableSearchQueryState?): EditableSearchQueryState?
|
||||||
|
= if (oldTag != null) {
|
||||||
|
when (newTag) {
|
||||||
|
is EditableSearchQueryState.Tag,
|
||||||
|
is EditableSearchQueryState.Not -> EditableSearchQueryState.And(listOf(oldTag, newTag))
|
||||||
|
is EditableSearchQueryState.And -> newTag.apply { queries.add(oldTag) }
|
||||||
|
is EditableSearchQueryState.Or -> newTag.apply { queries.add(oldTag) }
|
||||||
|
null -> oldTag
|
||||||
|
}
|
||||||
|
} else newTag
|
||||||
|
|
||||||
|
sealed interface EditableSearchQueryState {
|
||||||
|
class Tag(
|
||||||
|
namespace: String? = null,
|
||||||
|
tag: String = ""
|
||||||
|
): EditableSearchQueryState {
|
||||||
|
val namespace = mutableStateOf(namespace)
|
||||||
|
val tag = mutableStateOf(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
class And(
|
||||||
|
queries: List<EditableSearchQueryState> = emptyList()
|
||||||
|
): EditableSearchQueryState {
|
||||||
|
val queries = queries.toMutableStateList()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Or(
|
||||||
|
queries: List<EditableSearchQueryState> = emptyList()
|
||||||
|
): EditableSearchQueryState {
|
||||||
|
val queries = queries.toMutableStateList()
|
||||||
|
}
|
||||||
|
|
||||||
|
class Not(
|
||||||
|
query: EditableSearchQueryState? = null
|
||||||
|
): EditableSearchQueryState {
|
||||||
|
val query = mutableStateOf(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Root(
|
||||||
|
query: EditableSearchQueryState? = null
|
||||||
|
) {
|
||||||
|
val query = mutableStateOf(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewQueryChip(currentQuery: SearchQuery) {
|
fun EditableTagChip(
|
||||||
|
state: EditableSearchQueryState.Tag,
|
||||||
|
isFavorite: Boolean = false,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
leftIcon: @Composable (SearchQuery.Tag) -> Unit = { tag -> TagChipIcon(tag) },
|
||||||
|
rightIcon: @Composable (SearchQuery.Tag) -> Unit = { _ -> Spacer(Modifier.width(16.dp)) },
|
||||||
|
content: @Composable (SearchQuery.Tag) -> Unit = { tag -> Text(tag.tag) },
|
||||||
|
) {
|
||||||
|
val namespace by state.namespace
|
||||||
|
val tag by state.tag
|
||||||
|
|
||||||
|
val surfaceColor = if (isFavorite) Yellow400 else when (namespace) {
|
||||||
|
"male" -> Blue600
|
||||||
|
"female" -> Pink600
|
||||||
|
else -> MaterialTheme.colorScheme.surface
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentColor =
|
||||||
|
if (surfaceColor == MaterialTheme.colorScheme.surface)
|
||||||
|
MaterialTheme.colorScheme.onSurface
|
||||||
|
else
|
||||||
|
Color.White
|
||||||
|
|
||||||
|
val inner = @Composable {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalContentColor provides contentColor,
|
||||||
|
LocalTextStyle provides MaterialTheme.typography.bodyMedium
|
||||||
|
) {
|
||||||
|
val queryTag = SearchQuery.Tag(namespace, tag)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
leftIcon(queryTag)
|
||||||
|
content(queryTag)
|
||||||
|
rightIcon(queryTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val modifier = Modifier.height(32.dp)
|
||||||
|
val shape = RoundedCornerShape(16.dp)
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = shape,
|
||||||
|
color = surfaceColor,
|
||||||
|
content = inner
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Surface(
|
||||||
|
modifier,
|
||||||
|
shape = shape,
|
||||||
|
color = surfaceColor,
|
||||||
|
content = inner
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewQueryChip(
|
||||||
|
currentQuery: EditableSearchQueryState?,
|
||||||
|
onNewQuery: (EditableSearchQueryState) -> Unit
|
||||||
|
) {
|
||||||
var opened by remember { mutableStateOf(false) }
|
var opened by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewQueryRow(
|
fun NewQueryRow(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
icon: ImageVector = Icons.Default.AddCircleOutline,
|
icon: ImageVector = Icons.Default.AddCircleOutline,
|
||||||
text: String,
|
text: String,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.height(32.dp)
|
.height(32.dp)
|
||||||
.clickable(onClick = onClick),
|
.clickable(onClick = onClick),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@@ -73,35 +210,42 @@ fun NewQueryChip(currentQuery: SearchQuery) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Surface(shape = RoundedCornerShape(16.dp)) {
|
Surface(shape = RoundedCornerShape(16.dp)) {
|
||||||
AnimatedContent(targetState = opened, label = "add new query") { targetOpened ->
|
AnimatedContent(targetState = opened, label = "add new query" ) { targetOpened ->
|
||||||
if (targetOpened) {
|
if (targetOpened) {
|
||||||
Column {
|
Column {
|
||||||
NewQueryRow(
|
NewQueryRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Default.RemoveCircleOutline,
|
icon = Icons.Default.RemoveCircleOutline,
|
||||||
text = stringResource(android.R.string.cancel)
|
text = stringResource(android.R.string.cancel)
|
||||||
) {
|
) {
|
||||||
opened = false
|
opened = false
|
||||||
}
|
}
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
NewQueryRow(text = stringResource(R.string.search_add_query_item_tag)) {
|
if (currentQuery !is EditableSearchQueryState.Tag) {
|
||||||
|
NewQueryRow(modifier = Modifier.fillMaxWidth(), text = stringResource(R.string.search_add_query_item_tag)) {
|
||||||
}
|
opened = false
|
||||||
if (currentQuery !is SearchQuery.And) {
|
onNewQuery(EditableSearchQueryState.Tag())
|
||||||
HorizontalDivider()
|
|
||||||
NewQueryRow(text = "AND") {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentQuery !is SearchQuery.Or) {
|
if (currentQuery !is EditableSearchQueryState.And) {
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
NewQueryRow(text = "OR") {
|
NewQueryRow(modifier = Modifier.fillMaxWidth(), text = "AND") {
|
||||||
|
opened = false
|
||||||
|
onNewQuery(EditableSearchQueryState.And())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentQuery !is SearchQuery.Not) {
|
if (currentQuery !is EditableSearchQueryState.Or) {
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
NewQueryRow(text = "NOT") {
|
NewQueryRow(modifier = Modifier.fillMaxWidth(), text = "OR") {
|
||||||
|
opened = false
|
||||||
|
onNewQuery(EditableSearchQueryState.Or())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (currentQuery !is EditableSearchQueryState.Not || currentQuery.query.value != null) {
|
||||||
|
HorizontalDivider()
|
||||||
|
NewQueryRow(modifier = Modifier.fillMaxWidth(), text = "NOT") {
|
||||||
|
opened = false
|
||||||
|
onNewQuery(EditableSearchQueryState.Not())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,26 +260,29 @@ fun NewQueryChip(currentQuery: SearchQuery) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QueryEditorQueryView(
|
fun QueryEditorQueryView(
|
||||||
query: SearchQuery?,
|
state: EditableSearchQueryState,
|
||||||
onQueryAdd: (SearchQuery) -> Unit
|
onQueryRemove: (EditableSearchQueryState) -> Unit,
|
||||||
) {
|
) {
|
||||||
when (query) {
|
when (state) {
|
||||||
is SearchQuery.Tag -> {
|
is EditableSearchQueryState.Tag -> {
|
||||||
TagChip(
|
EditableTagChip(
|
||||||
query,
|
state,
|
||||||
enabled = false,
|
enabled = false,
|
||||||
rightIcon = {
|
rightIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.size(16.dp),
|
.size(16.dp)
|
||||||
|
.clickable {
|
||||||
|
onQueryRemove(state)
|
||||||
|
},
|
||||||
imageVector = Icons.Default.RemoveCircleOutline,
|
imageVector = Icons.Default.RemoveCircleOutline,
|
||||||
contentDescription = stringResource(R.string.search_remove_query_item_description)
|
contentDescription = stringResource(R.string.search_remove_query_item_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SearchQuery.Or -> {
|
is EditableSearchQueryState.Or -> {
|
||||||
Card(
|
Card(
|
||||||
colors = CardColors(
|
colors = CardColors(
|
||||||
containerColor = Blue300,
|
containerColor = Blue300,
|
||||||
@@ -158,22 +305,27 @@ fun QueryEditorQueryView(
|
|||||||
) {
|
) {
|
||||||
Text("OR", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.labelMedium)
|
Text("OR", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.labelMedium)
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.clickable { onQueryRemove(state) },
|
||||||
imageVector = Icons.Default.RemoveCircleOutline,
|
imageVector = Icons.Default.RemoveCircleOutline,
|
||||||
contentDescription = stringResource(xyz.quaver.pupil.R.string.search_remove_query_item_description)
|
contentDescription = stringResource(R.string.search_remove_query_item_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
query.queries.forEachIndexed { index, subquery ->
|
state.queries.forEachIndexed { index, subQueryState ->
|
||||||
if (index != 0) { Text("+", modifier = Modifier.padding(horizontal = 8.dp)) }
|
if (index != 0) { Text("+", modifier = Modifier.padding(horizontal = 8.dp)) }
|
||||||
QueryEditorQueryView(subquery, onQueryAdd)
|
QueryEditorQueryView(
|
||||||
|
subQueryState,
|
||||||
|
onQueryRemove = { state.queries.remove(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
NewQueryChip(query) {
|
NewQueryChip(state) { newQueryState ->
|
||||||
|
state.queries.add(newQueryState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SearchQuery.And -> {
|
is EditableSearchQueryState.And -> {
|
||||||
Card(
|
Card(
|
||||||
colors = CardColors(
|
colors = CardColors(
|
||||||
containerColor = Gray300,
|
containerColor = Gray300,
|
||||||
@@ -196,21 +348,28 @@ fun QueryEditorQueryView(
|
|||||||
) {
|
) {
|
||||||
Text("AND", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.labelMedium)
|
Text("AND", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.labelMedium)
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.clickable { onQueryRemove(state) },
|
||||||
imageVector = Icons.Default.RemoveCircleOutline,
|
imageVector = Icons.Default.RemoveCircleOutline,
|
||||||
contentDescription = stringResource(xyz.quaver.pupil.R.string.search_remove_query_item_description)
|
contentDescription = stringResource(R.string.search_remove_query_item_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
query.queries.forEach { subquery ->
|
state.queries.forEach { subQuery ->
|
||||||
QueryEditorQueryView(subquery, onQueryAdd)
|
QueryEditorQueryView(
|
||||||
|
subQuery,
|
||||||
|
onQueryRemove = { state.queries.remove(it) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
NewQueryChip(query) {
|
NewQueryChip(state) { newQueryState ->
|
||||||
|
state.queries.add(newQueryState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is SearchQuery.Not -> {
|
is EditableSearchQueryState.Not -> {
|
||||||
|
var subQueryState by state.query
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
colors = CardColors(
|
colors = CardColors(
|
||||||
containerColor = Red300,
|
containerColor = Red300,
|
||||||
@@ -233,12 +392,32 @@ fun QueryEditorQueryView(
|
|||||||
) {
|
) {
|
||||||
Text("-", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.labelMedium)
|
Text("-", modifier = Modifier.padding(horizontal = 8.dp), style = MaterialTheme.typography.labelMedium)
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.clickable { onQueryRemove(state) },
|
||||||
imageVector = Icons.Default.RemoveCircleOutline,
|
imageVector = Icons.Default.RemoveCircleOutline,
|
||||||
contentDescription = stringResource(xyz.quaver.pupil.R.string.search_remove_query_item_description)
|
contentDescription = stringResource(R.string.search_remove_query_item_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
QueryEditorQueryView(query.query, onQueryAdd)
|
val subQueryStateSnapshot = subQueryState
|
||||||
|
if (subQueryStateSnapshot != null) {
|
||||||
|
QueryEditorQueryView(
|
||||||
|
subQueryStateSnapshot,
|
||||||
|
onQueryRemove = { subQueryState = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subQueryStateSnapshot == null) {
|
||||||
|
NewQueryChip(state) { newQueryState ->
|
||||||
|
subQueryState = newQueryState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subQueryStateSnapshot is EditableSearchQueryState.Tag) {
|
||||||
|
NewQueryChip(state) { newQueryState ->
|
||||||
|
subQueryState = coalesceTags(subQueryStateSnapshot, newQueryState)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,19 +426,27 @@ fun QueryEditorQueryView(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun QueryEditor(
|
fun QueryEditor(
|
||||||
query: SearchQuery?,
|
state: EditableSearchQueryState.Root
|
||||||
onQueryChange: (SearchQuery) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
|
var rootQuery by state.query
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.verticalScroll(rememberScrollState())
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
if (query != null) {
|
val rootQuerySnapshot = rootQuery
|
||||||
QueryEditorQueryView(query = query) {
|
if (rootQuerySnapshot != null) {
|
||||||
|
QueryEditorQueryView(
|
||||||
}
|
state = rootQuerySnapshot,
|
||||||
} else {
|
onQueryRemove = { rootQuery = null }
|
||||||
NewQueryChip(null) {
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootQuerySnapshot is EditableSearchQueryState.Tag?) {
|
||||||
|
NewQueryChip(rootQuerySnapshot) { newState ->
|
||||||
|
rootQuery = coalesceTags(rootQuerySnapshot, newState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user