Add search bar to drawer

pull/2919/head
Alex Baker 1 week ago
parent 28de989a05
commit 007c536312

@ -115,7 +115,7 @@ class MainActivity : AppCompatActivity() {
bottomPadding = WindowInsets.mandatorySystemGestures
.asPaddingValues()
.calculateBottomPadding(),
items = state.drawerItems,
items = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems,
begForMoney = state.begForMoney,
isTopAppBar = preferences.isTopAppBar,
setFilter = { viewModel.setFilter(it) },
@ -161,6 +161,8 @@ class MainActivity : AppCompatActivity() {
}
},
dismiss = { viewModel.setDrawerOpen(false) },
query = state.menuQuery,
onQueryChange = { viewModel.queryMenu(it) },
)
}
}

@ -62,6 +62,8 @@ class MainActivityViewModel @Inject constructor(
val task: Task? = null,
val drawerOpen: Boolean = false,
val drawerItems: ImmutableList<DrawerItem> = persistentListOf(),
val searchItems: ImmutableList<DrawerItem> = persistentListOf(),
val menuQuery: String = "",
)
private val _state = MutableStateFlow(
@ -107,7 +109,12 @@ class MainActivityViewModel @Inject constructor(
}
fun setDrawerOpen(open: Boolean) {
_state.update { it.copy(drawerOpen = open) }
_state.update {
it.copy(
drawerOpen = open,
menuQuery = if (!open) "" else it.menuQuery,
)
}
}
init {
@ -152,6 +159,27 @@ class MainActivityViewModel @Inject constructor(
}
}
.let { filters -> _state.update { it.copy(drawerItems = filters.toPersistentList()) } }
val query = _state.value.menuQuery
filterProvider
.allFilters()
.filter { it.title!!.contains(query, ignoreCase = true) }
.map { item ->
DrawerItem.Filter(
title = item.title ?: "",
icon = getIcon(item),
color = getColor(item),
count = item.count.takeIf { it != NO_COUNT } ?: try {
taskDao.count(item)
} catch (e: Exception) {
Timber.e(e)
0
},
selected = item.areItemsTheSame(selected),
shareCount = if (item is CaldavFilter) item.principals else 0,
type = { item },
)
}
.let { filters -> _state.update { it.copy(searchItems = filters.toPersistentList()) } }
}
private fun getColor(filter: Filter): Int {
@ -202,4 +230,9 @@ class MainActivityViewModel @Inject constructor(
fun setTask(task: Task?) {
_state.update { it.copy(task = task) }
}
fun queryMenu(query: String) {
_state.update { it.copy(menuQuery = query) }
updateFilters()
}
}

@ -16,17 +16,18 @@ import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.PeopleOutline
import androidx.compose.material.icons.outlined.PermIdentity
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.SyncProblem
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
@ -34,7 +35,9 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
@ -49,6 +52,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.compose.components.SearchBar
import org.tasks.extensions.formatNumber
import org.tasks.filters.FilterImpl
import org.tasks.filters.NavigationDrawerSubheader
@ -63,7 +67,14 @@ fun TaskListDrawer(
onDrawerAction: (DrawerAction) -> Unit,
onAddClick: (DrawerItem.Header) -> Unit,
onErrorClick: () -> Unit,
query: String,
onQueryChange: (String) -> Unit,
) {
val searching by remember (query) {
derivedStateOf {
query.isNotBlank()
}
}
LazyColumn(
modifier = Modifier
.padding(bottom = bottomPadding)
@ -73,7 +84,33 @@ fun TaskListDrawer(
stiffness = Spring.StiffnessMedium
)
)
.imePadding()
) {
item {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
SearchBar(
modifier = Modifier
.padding(start = 8.dp, bottom = 4.dp)
.weight(1f),
text = query,
onTextChange = { onQueryChange(it) },
placeHolder = stringResource(id = R.string.TLA_menu_search),
onCloseClicked = { onQueryChange("") },
onSearchClicked = {
// TODO: close keyboard
},
)
IconButton(onClick = { onDrawerAction(DrawerAction.SETTINGS) }) {
Icon(
imageVector = Icons.Outlined.Settings,
contentDescription = stringResource(id = R.string.TLA_menu_settings),
tint = MaterialTheme.colorScheme.onSurface,
)
}
}
}
items(items = filters) {
when (it) {
is DrawerItem.Filter -> FilterItem(
@ -89,48 +126,42 @@ fun TaskListDrawer(
)
}
}
item {
Divider(modifier = Modifier.fillMaxWidth())
}
if (begForMoney) {
if (!searching) {
item {
Divider(modifier = Modifier.fillMaxWidth())
}
if (begForMoney) {
item {
MenuAction(
icon = R.drawable.ic_outline_attach_money_24px,
title = if (IS_GENERIC) R.string.TLA_menu_donate else R.string.name_your_price
) {
onDrawerAction(DrawerAction.PURCHASE)
}
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_attach_money_24px,
title = if (IS_GENERIC) R.string.TLA_menu_donate else R.string.name_your_price
icon = R.drawable.ic_outline_edit_24px,
title = R.string.manage_drawer
) {
onDrawerAction(DrawerAction.PURCHASE)
onDrawerAction(DrawerAction.CUSTOMIZE_DRAWER)
}
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_edit_24px,
title = R.string.manage_drawer
) {
onDrawerAction(DrawerAction.CUSTOMIZE_DRAWER)
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_settings_24px,
title = R.string.TLA_menu_settings
) {
onDrawerAction(DrawerAction.SETTINGS)
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_help_outline_24px,
title = R.string.help_and_feedback
) {
onDrawerAction(DrawerAction.HELP_AND_FEEDBACK)
item {
MenuAction(
icon = R.drawable.ic_outline_help_outline_24px,
title = R.string.help_and_feedback
) {
onDrawerAction(DrawerAction.HELP_AND_FEEDBACK)
}
}
}
}
}
@Composable
private fun FilterItem(
internal fun FilterItem(
item: DrawerItem.Filter,
onClick: () -> Unit,
) {
@ -161,9 +192,7 @@ private fun FilterItem(
else -> Icons.Outlined.PeopleOutline
},
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface.copy(
alpha = ContentAlpha.medium
),
tint = MaterialTheme.colorScheme.onSurface,
)
}
Box(
@ -207,12 +236,12 @@ private fun DrawerIcon(icon: Int, color: Int = 0) {
tint = when (color) {
0 -> MaterialTheme.colorScheme.onSurface
else -> Color(color)
}.copy(alpha = ContentAlpha.medium)
}
)
}
@Composable
private fun HeaderItem(
internal fun HeaderItem(
item: DrawerItem.Header,
canAdd: Boolean,
toggleCollapsed: () -> Unit,
@ -289,6 +318,7 @@ private fun MenuRow(
fun MenuPreview() {
TasksTheme {
TaskListDrawer(
begForMoney = true,
filters = persistentListOf(
DrawerItem.Filter(
title = "My Tasks",
@ -313,9 +343,10 @@ fun MenuPreview() {
),
onClick = {},
onDrawerAction = {},
begForMoney = true,
onAddClick = {},
onErrorClick = {},
query = "",
onQueryChange = {},
)
}
}

@ -42,6 +42,8 @@ fun TasksMenu(
toggleCollapsed: (NavigationDrawerSubheader) -> Unit,
addFilter: (NavigationDrawerSubheader) -> Unit,
dismiss: () -> Unit,
query: String,
onQueryChange: (String) -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
val skipPartiallyExpanded = remember(expanded) {
@ -128,6 +130,8 @@ fun TasksMenu(
onErrorClick = {
context.startActivity(Intent(context, MainPreferences::class.java))
},
query = query,
onQueryChange = onQueryChange,
)
}
}

@ -14,7 +14,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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

@ -3,9 +3,7 @@ package org.tasks.widget
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.Toolbar
import androidx.core.content.IntentCompat
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat

@ -0,0 +1,37 @@
package org.tasks.previews.components
import android.content.res.Configuration
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import org.tasks.compose.components.SearchBar
import org.tasks.themes.TasksTheme
@Preview(widthDp = 320, showBackground = true)
@Preview(widthDp = 320, showBackground = false, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun SearchBarPreviewPlaceholder() {
TasksTheme {
SearchBar(
text = "",
onTextChange = {},
placeHolder = "Search",
onCloseClicked = {},
onSearchClicked = {},
)
}
}
@Preview(widthDp = 320, showBackground = true)
@Preview(widthDp = 320, showBackground = false, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun SearchBarPreviewSearching() {
TasksTheme {
SearchBar(
text = "Testing",
onTextChange = {},
placeHolder = "Search",
onCloseClicked = {},
onSearchClicked = {},
)
}
}

@ -1,16 +1,13 @@
package org.tasks.previews
package org.tasks.previews.pickers
import android.content.res.Configuration
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AllInbox
import androidx.compose.material.icons.outlined.QuestionMark
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import org.tasks.compose.pickers.SearchableFilterPicker
import org.tasks.themes.TasksTheme
import org.tasks.filters.FilterImpl
import org.tasks.filters.NavigationDrawerSubheader
@ -21,9 +18,7 @@ import org.tasks.filters.NavigationDrawerSubheader
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320)
@Composable
fun FilterPickerPreview() {
MaterialTheme(
colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()
) {
TasksTheme {
SearchableFilterPicker(
filters = listOf(
FilterImpl("My Tasks", icon = 4),

@ -0,0 +1,79 @@
package org.tasks.compose.components
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
@Composable
fun SearchBar(
modifier: Modifier = Modifier,
text: String,
onTextChange: (String) -> Unit,
placeHolder: String,
onCloseClicked: () -> Unit,
onSearchClicked: (String) -> Unit,
) {
OutlinedTextField(
shape = MaterialTheme.shapes.medium,
modifier = modifier.height(56.dp),
value = text,
onValueChange = {
onTextChange(it)
},
placeholder = {
Text(
text = placeHolder,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.fillMaxHeight(),
style = MaterialTheme.typography.bodyLarge,
)
},
leadingIcon = {
Icon(
imageVector = Icons.Outlined.Search,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
)
},
trailingIcon = {
IconButton(onClick = { onCloseClicked() }) {
if (text.isNotBlank()) {
Icon(
imageVector = Icons.Outlined.Clear,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
)
}
}
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Search
),
keyboardActions = KeyboardActions(
onSearch = {
onSearchClicked(text)
}
),
singleLine = true,
textStyle = MaterialTheme.typography.bodyLarge,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.onSurface,
unfocusedBorderColor = MaterialTheme.colorScheme.onSurface,
cursorColor = MaterialTheme.colorScheme.onSurface,
),
)
}
Loading…
Cancel
Save