Add article search feature
This commit is contained in:
parent
aaf032332b
commit
25009e9036
|
@ -10,6 +10,198 @@ import java.util.*
|
|||
|
||||
@Dao
|
||||
interface ArticleDao {
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND feedId IN (
|
||||
SELECT id FROM feed WHERE groupId = :groupId
|
||||
)
|
||||
AND isUnread = :isUnread
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleByGroupIdWhenIsUnread(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
groupId: String,
|
||||
isUnread: Boolean,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND feedId IN (
|
||||
SELECT id FROM feed WHERE groupId = :groupId
|
||||
)
|
||||
AND isStarred = :isStarred
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleByGroupIdWhenIsStarred(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
groupId: String,
|
||||
isStarred: Boolean,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND feedId IN (
|
||||
SELECT id FROM feed WHERE groupId = :groupId
|
||||
)
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleByGroupIdWhenAll(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
groupId: String,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND feedId = :feedId
|
||||
AND isUnread = :isUnread
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleByFeedIdWhenIsUnread(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
feedId: String,
|
||||
isUnread: Boolean,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND feedId = :feedId
|
||||
AND isStarred = :isStarred
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleByFeedIdWhenIsStarred(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
feedId: String,
|
||||
isStarred: Boolean,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND feedId = :feedId
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleByFeedIdWhenAll(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
feedId: String,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND isUnread = :isUnread
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleWhenIsUnread(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
isUnread: Boolean,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND isStarred = :isStarred
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleWhenIsStarred(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
isStarred: Boolean,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND (
|
||||
title LIKE '%' || :text || '%'
|
||||
OR shortDescription LIKE '%' || :text || '%'
|
||||
OR fullContent LIKE '%' || :text || '%'
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleWhenAll(
|
||||
accountId: Int,
|
||||
text: String,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
UPDATE article SET isUnread = :isUnread
|
||||
|
@ -92,64 +284,6 @@ interface ArticleDao {
|
|||
)
|
||||
suspend fun deleteByGroupId(accountId: Int, groupId: String)
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE accountId = :accountId
|
||||
AND (
|
||||
title LIKE :keyword
|
||||
OR rawDescription LIKE :keyword
|
||||
OR fullContent LIKE :keyword
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleWithFeedWhenIsAll(
|
||||
accountId: Int,
|
||||
keyword: String,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE isUnread = :isUnread
|
||||
AND accountId = :accountId
|
||||
AND (
|
||||
title LIKE :keyword
|
||||
OR rawDescription LIKE :keyword
|
||||
OR fullContent LIKE :keyword
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleWithFeedWhenIsUnread(
|
||||
accountId: Int,
|
||||
isUnread: Boolean,
|
||||
keyword: String,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM article
|
||||
WHERE isStarred = :isStarred
|
||||
AND accountId = :accountId
|
||||
AND (
|
||||
title LIKE :keyword
|
||||
OR rawDescription LIKE :keyword
|
||||
OR fullContent LIKE :keyword
|
||||
)
|
||||
ORDER BY date DESC
|
||||
"""
|
||||
)
|
||||
fun searchArticleWithFeedWhenIsStarred(
|
||||
accountId: Int,
|
||||
isStarred: Boolean,
|
||||
keyword: String,
|
||||
): PagingSource<Int, ArticleWithFeed>
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
|
|
|
@ -166,6 +166,41 @@ abstract class AbstractRssRepository constructor(
|
|||
suspend fun groupMoveToTargetGroup(group: Group, targetGroup: Group) {
|
||||
feedDao.updateTargetGroupIdByGroupId(context.currentAccountId, group.id, targetGroup.id)
|
||||
}
|
||||
|
||||
fun searchArticles(
|
||||
content: String,
|
||||
groupId: String? = null,
|
||||
feedId: String? = null,
|
||||
isStarred: Boolean = false,
|
||||
isUnread: Boolean = false,
|
||||
): PagingSource<Int, ArticleWithFeed> {
|
||||
val accountId = context.currentAccountId
|
||||
Log.i(
|
||||
"RLog",
|
||||
"searchArticles: content: ${content}, accountId: ${accountId}, groupId: ${groupId}, feedId: ${feedId}, isStarred: ${isStarred}, isUnread: ${isUnread}"
|
||||
)
|
||||
return when {
|
||||
groupId != null -> when {
|
||||
isStarred -> articleDao
|
||||
.searchArticleByGroupIdWhenIsStarred(accountId, content, groupId, isStarred)
|
||||
isUnread -> articleDao
|
||||
.searchArticleByGroupIdWhenIsUnread(accountId, content, groupId, isUnread)
|
||||
else -> articleDao.searchArticleByGroupIdWhenAll(accountId, content, groupId)
|
||||
}
|
||||
feedId != null -> when {
|
||||
isStarred -> articleDao
|
||||
.searchArticleByFeedIdWhenIsStarred(accountId, content, feedId, isStarred)
|
||||
isUnread -> articleDao
|
||||
.searchArticleByFeedIdWhenIsUnread(accountId, content, feedId, isUnread)
|
||||
else -> articleDao.searchArticleByFeedIdWhenAll(accountId, content, feedId)
|
||||
}
|
||||
else -> when {
|
||||
isStarred -> articleDao.searchArticleWhenIsStarred(accountId, content, isStarred)
|
||||
isUnread -> articleDao.searchArticleWhenIsUnread(accountId, content, isUnread)
|
||||
else -> articleDao.searchArticleWhenAll(accountId, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HiltWorker
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
|
@ -18,8 +19,10 @@ import androidx.compose.material3.Scaffold
|
|||
import androidx.compose.material3.SmallTopAppBar
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
@ -28,6 +31,8 @@ import androidx.navigation.NavHostController
|
|||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import androidx.work.WorkInfo
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.data.entity.ArticleWithFeed
|
||||
|
@ -42,6 +47,7 @@ import me.ash.reader.ui.page.home.FilterState
|
|||
@OptIn(
|
||||
ExperimentalMaterial3Api::class,
|
||||
ExperimentalFoundationApi::class, com.google.accompanist.pager.ExperimentalPagerApi::class,
|
||||
androidx.compose.ui.ExperimentalComposeUiApi::class,
|
||||
)
|
||||
@Composable
|
||||
fun FlowPage(
|
||||
|
@ -55,10 +61,13 @@ fun FlowPage(
|
|||
onItemClick: (item: ArticleWithFeed) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val scope = rememberCoroutineScope()
|
||||
var markAsRead by remember { mutableStateOf(false) }
|
||||
var onSearch by remember { mutableStateOf(false) }
|
||||
val viewState = flowViewModel.viewState.collectAsStateValue()
|
||||
val pagingItems = viewState.pagingData.collectAsLazyPagingItems()
|
||||
var markAsRead by remember { mutableStateOf(false) }
|
||||
|
||||
val owner = LocalLifecycleOwner.current
|
||||
var isSyncing by remember { mutableStateOf(false) }
|
||||
|
@ -67,10 +76,38 @@ fun FlowPage(
|
|||
}
|
||||
|
||||
LaunchedEffect(filterState) {
|
||||
snapshotFlow { filterState }.collect {
|
||||
flowViewModel.dispatch(
|
||||
FlowViewAction.FetchData(filterState)
|
||||
FlowViewAction.FetchData(it)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(onSearch) {
|
||||
snapshotFlow { onSearch }.collect {
|
||||
if (it) {
|
||||
delay(100) // ???
|
||||
focusRequester.requestFocus()
|
||||
} else {
|
||||
keyboardController?.hide()
|
||||
if (viewState.searchContent.isNotBlank()) {
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(viewState.listState) {
|
||||
snapshotFlow { viewState.listState.firstVisibleItemIndex }.collect {
|
||||
if (it > 0) {
|
||||
keyboardController?.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(onSearch) {
|
||||
onSearch = false
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.surface),
|
||||
|
@ -83,12 +120,13 @@ fun FlowPage(
|
|||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
) {
|
||||
onSearch = false
|
||||
onScrollToPage(0)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
AnimatedVisibility(
|
||||
visible = !filterState.filter.isStarred(),// && pagingItems.loadState.refresh is LoadState.NotLoading && pagingItems.itemCount != 0,
|
||||
visible = !filterState.filter.isStarred(),
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
|
@ -104,20 +142,28 @@ fun FlowPage(
|
|||
scope.launch {
|
||||
viewState.listState.scrollToItem(0)
|
||||
markAsRead = !markAsRead
|
||||
onSearch = false
|
||||
}
|
||||
}
|
||||
}
|
||||
FeedbackIconButton(
|
||||
imageVector = Icons.Rounded.Search,
|
||||
contentDescription = stringResource(R.string.search),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
tint = if (onSearch) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurface
|
||||
},
|
||||
) {
|
||||
scope.launch {
|
||||
viewState.listState.scrollToItem(0)
|
||||
onSearch = !onSearch
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = {
|
||||
Crossfade(targetState = pagingItems) { pagingItems ->
|
||||
// if (pagingItems.loadState.source.refresh is LoadState.NotLoading && pagingItems.itemCount == 0) {
|
||||
// LottieAnimation(
|
||||
// modifier = Modifier
|
||||
|
@ -166,10 +212,47 @@ fun FlowPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
AnimatedVisibility(
|
||||
visible = onSearch,
|
||||
enter = fadeIn() + expandVertically(),
|
||||
exit = fadeOut() + shrinkVertically(),
|
||||
) {
|
||||
SearchBar(
|
||||
value = viewState.searchContent,
|
||||
placeholder = when {
|
||||
filterState.group != null -> stringResource(
|
||||
R.string.search_for_in,
|
||||
filterState.filter.getName(),
|
||||
filterState.group.name
|
||||
)
|
||||
filterState.feed != null -> stringResource(
|
||||
R.string.search_for_in,
|
||||
filterState.filter.getName(),
|
||||
filterState.feed.name
|
||||
)
|
||||
else -> stringResource(
|
||||
R.string.search_for,
|
||||
filterState.filter.getName()
|
||||
)
|
||||
},
|
||||
focusRequester = focusRequester,
|
||||
onValueChange = {
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(it))
|
||||
},
|
||||
onClose = {
|
||||
onSearch = false
|
||||
flowViewModel.dispatch(FlowViewAction.InputSearchContent(""))
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.height((56 + 24 + 10).dp))
|
||||
}
|
||||
}
|
||||
generateArticleList(
|
||||
context = context,
|
||||
pagingItems = pagingItems,
|
||||
) {
|
||||
onSearch = false
|
||||
onItemClick(it)
|
||||
}
|
||||
item {
|
||||
|
@ -179,7 +262,6 @@ fun FlowPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
FilterBar(
|
||||
|
@ -187,7 +269,9 @@ fun FlowPage(
|
|||
.height(60.dp)
|
||||
.fillMaxWidth(),
|
||||
filter = filterState.filter,
|
||||
filterOnClick = { onFilterChange(filterState.copy(filter = it)) },
|
||||
filterOnClick = {
|
||||
onFilterChange(filterState.copy(filter = it))
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -35,22 +35,40 @@ class FlowViewModel @Inject constructor(
|
|||
action.articleId,
|
||||
action.markAsReadBefore,
|
||||
)
|
||||
is FlowViewAction.InputSearchContent -> inputSearchContent(action.content)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchData(filterState: FilterState) {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
rssRepository.get().pullImportant(filterState.filter.isStarred(), true)
|
||||
.collect { importantList ->
|
||||
private fun fetchData(filterState: FilterState? = null) {
|
||||
// viewModelScope.launch(Dispatchers.Default) {
|
||||
// rssRepository.get().pullImportant(filterState.filter.isStarred(), true)
|
||||
// .collect { importantList ->
|
||||
// _viewState.update {
|
||||
// it.copy(
|
||||
// filterImportant = importantList.sumOf { it.important },
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
if (_viewState.value.searchContent.isNotBlank()) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
filterImportant = importantList.sumOf { it.important },
|
||||
filterState = filterState,
|
||||
pagingData = Pager(PagingConfig(pageSize = 10)) {
|
||||
rssRepository.get().searchArticles(
|
||||
content = _viewState.value.searchContent.trim(),
|
||||
groupId = _viewState.value.filterState?.group?.id,
|
||||
feedId = _viewState.value.filterState?.feed?.id,
|
||||
isStarred = _viewState.value.filterState?.filter?.isStarred() ?: false,
|
||||
isUnread = _viewState.value.filterState?.filter?.isUnread() ?: false,
|
||||
)
|
||||
}.flow.flowOn(Dispatchers.IO).cachedIn(viewModelScope)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (filterState != null) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
filterState = filterState,
|
||||
pagingData = Pager(PagingConfig(pageSize = 10)) {
|
||||
rssRepository.get().pullArticles(
|
||||
groupId = filterState.group?.id,
|
||||
|
@ -62,6 +80,7 @@ class FlowViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToItem(index: Int) {
|
||||
viewModelScope.launch {
|
||||
|
@ -105,14 +124,25 @@ class FlowViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputSearchContent(content: String) {
|
||||
_viewState.update {
|
||||
it.copy(
|
||||
searchContent = content,
|
||||
)
|
||||
}
|
||||
fetchData(_viewState.value.filterState)
|
||||
}
|
||||
}
|
||||
|
||||
data class ArticleViewState(
|
||||
val filterState: FilterState? = null,
|
||||
val filterImportant: Int = 0,
|
||||
val listState: LazyListState = LazyListState(),
|
||||
val isRefreshing: Boolean = false,
|
||||
val pagingData: Flow<PagingData<ArticleWithFeed>> = emptyFlow(),
|
||||
val syncWorkInfo: String = "",
|
||||
val searchContent: String = "",
|
||||
)
|
||||
|
||||
sealed class FlowViewAction {
|
||||
|
@ -134,6 +164,10 @@ sealed class FlowViewAction {
|
|||
val articleId: String?,
|
||||
val markAsReadBefore: MarkAsReadBefore
|
||||
) : FlowViewAction()
|
||||
|
||||
data class InputSearchContent(
|
||||
val content: String,
|
||||
) : FlowViewAction()
|
||||
}
|
||||
|
||||
enum class MarkAsReadBefore {
|
||||
|
|
|
@ -14,6 +14,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
|
@ -34,7 +35,9 @@ fun MarkAsReadBar(
|
|||
val animated = remember { Animatable(absoluteY.value) }
|
||||
|
||||
LaunchedEffect(absoluteY) {
|
||||
animated.animateTo(absoluteY.value, spring(stiffness = Spring.StiffnessMediumLow))
|
||||
snapshotFlow { absoluteY }.collect {
|
||||
animated.animateTo(it.value, spring(stiffness = Spring.StiffnessMediumLow))
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedPopup(
|
||||
|
|
107
app/src/main/java/me/ash/reader/ui/page/home/flow/SearchBar.kt
Normal file
107
app/src/main/java/me/ash/reader/ui/page/home/flow/SearchBar.kt
Normal file
|
@ -0,0 +1,107 @@
|
|||
package me.ash.reader.ui.page.home.flow
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.style.BaselineShift
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.R
|
||||
|
||||
@Composable
|
||||
fun SearchBar(
|
||||
modifier: Modifier = Modifier,
|
||||
value: String,
|
||||
placeholder: String = "",
|
||||
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||
onValueChange: (String) -> Unit = {},
|
||||
onClose: () -> Unit = {},
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.height(56.dp)
|
||||
.padding(horizontal = 24.dp)
|
||||
.fillMaxWidth(),
|
||||
shape = CircleShape,
|
||||
tonalElevation = 3.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
imageVector = Icons.Rounded.Search,
|
||||
contentDescription = stringResource(R.string.search),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
androidx.compose.material.TextField(
|
||||
modifier = Modifier
|
||||
.height(56.dp)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
backgroundColor = Color.Transparent,
|
||||
cursorColor = MaterialTheme.colorScheme.onSurface,
|
||||
textColor = MaterialTheme.colorScheme.onSurface,
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
value = value,
|
||||
onValueChange = { onValueChange(it) },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = placeholder,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
|
||||
alpha = 0.7f
|
||||
),
|
||||
)
|
||||
},
|
||||
textStyle = MaterialTheme.typography.bodyLarge.copy(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
baselineShift = BaselineShift(0.1f)
|
||||
),
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
focusManager.clearFocus()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { onClose() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Close,
|
||||
contentDescription = stringResource(R.string.clear),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,8 @@
|
|||
<string name="today">今天</string>
|
||||
<string name="yesterday">昨天</string>
|
||||
<string name="date_at_time">%1$s %2$s</string>
|
||||
<string name="search_for_in">在%1$s的 \"%2$s\" 中搜索</string>
|
||||
<string name="search_for">在%1$s中搜索</string>
|
||||
<string name="mark_as_read">标记为已读</string>
|
||||
<string name="mark_all_as_read">全部标记为已读</string>
|
||||
<string name="mark_as_unread">标记为未读</string>
|
||||
|
|
|
@ -58,6 +58,8 @@
|
|||
<string name="today">Today</string>
|
||||
<string name="yesterday">Yesterday</string>
|
||||
<string name="date_at_time">%1$s At %2$s</string>
|
||||
<string name="search_for_in">Search for %1$s Items in \"%2$s\"</string>
|
||||
<string name="search_for">Search for %1$s Items</string>
|
||||
<string name="mark_as_read">Mark as Read</string>
|
||||
<string name="mark_all_as_read">Mark All as Read</string>
|
||||
<string name="mark_as_unread">Mark as Unread</string>
|
||||
|
|
Loading…
Reference in New Issue
Block a user