refactor(Search): 封装搜索框、结果列表等组件
This commit is contained in:
parent
d99f723cbc
commit
0207f830f7
|
|
@ -1,6 +1,5 @@
|
||||||
package com.huanchengfly.tieba.post.ui.page.search
|
package com.huanchengfly.tieba.post.ui.page.search
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
|
@ -14,7 +13,6 @@ import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
|
@ -27,20 +25,16 @@ import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
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.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.ButtonDefaults
|
import androidx.compose.material.ButtonDefaults
|
||||||
import androidx.compose.material.ContentAlpha
|
import androidx.compose.material.ContentAlpha
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.ProvideTextStyle
|
import androidx.compose.material.ProvideTextStyle
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Tab
|
import androidx.compose.material.Tab
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.ArrowBack
|
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.rounded.Clear
|
|
||||||
import androidx.compose.material.icons.rounded.ExpandLess
|
import androidx.compose.material.icons.rounded.ExpandLess
|
||||||
import androidx.compose.material.icons.rounded.ExpandMore
|
import androidx.compose.material.icons.rounded.ExpandMore
|
||||||
import androidx.compose.material.icons.rounded.Search
|
import androidx.compose.material.icons.rounded.Search
|
||||||
|
|
@ -59,13 +53,11 @@ import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.onFocusEvent
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
@ -87,13 +79,13 @@ import com.huanchengfly.tieba.post.ui.page.search.thread.SearchThreadPage
|
||||||
import com.huanchengfly.tieba.post.ui.page.search.thread.SearchThreadSortType
|
import com.huanchengfly.tieba.post.ui.page.search.thread.SearchThreadSortType
|
||||||
import com.huanchengfly.tieba.post.ui.page.search.thread.SearchThreadUiEvent
|
import com.huanchengfly.tieba.post.ui.page.search.thread.SearchThreadUiEvent
|
||||||
import com.huanchengfly.tieba.post.ui.page.search.user.SearchUserPage
|
import com.huanchengfly.tieba.post.ui.page.search.user.SearchUserPage
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BaseTextField
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Button
|
import com.huanchengfly.tieba.post.ui.widgets.compose.Button
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Container
|
import com.huanchengfly.tieba.post.ui.widgets.compose.Container
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoadHorizontalPager
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoadHorizontalPager
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyBackHandler
|
import com.huanchengfly.tieba.post.ui.widgets.compose.MyBackHandler
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.PagerTabIndicator
|
import com.huanchengfly.tieba.post.ui.widgets.compose.PagerTabIndicator
|
||||||
|
import com.huanchengfly.tieba.post.ui.widgets.compose.SearchBox
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TabClickMenu
|
import com.huanchengfly.tieba.post.ui.widgets.compose.TabClickMenu
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TabRow
|
import com.huanchengfly.tieba.post.ui.widgets.compose.TabRow
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TopAppBarContainer
|
import com.huanchengfly.tieba.post.ui.widgets.compose.TopAppBarContainer
|
||||||
|
|
@ -269,8 +261,10 @@ fun SearchPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) { paddingValues ->
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
Box(modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)) {
|
||||||
if (!isKeywordEmpty) {
|
if (!isKeywordEmpty) {
|
||||||
ProvideNavigator(navigator = navigator) {
|
ProvideNavigator(navigator = navigator) {
|
||||||
LazyLoadHorizontalPager(
|
LazyLoadHorizontalPager(
|
||||||
|
|
@ -562,20 +556,18 @@ private fun SearchTopBar(
|
||||||
onKeywordSubmit: (String) -> Unit = {},
|
onKeywordSubmit: (String) -> Unit = {},
|
||||||
onBack: () -> Unit = {},
|
onBack: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val isKeywordNotEmpty = remember(keyword) { keyword.isNotEmpty() }
|
SearchBox(
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
keyword = keyword,
|
||||||
Surface(
|
onKeywordChange = onKeywordChange,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
shape = RoundedCornerShape(6.dp),
|
onKeywordSubmit = onKeywordSubmit,
|
||||||
color = ExtendedTheme.colors.topBarSurface,
|
placeholder = {
|
||||||
contentColor = ExtendedTheme.colors.onTopBarSurface,
|
Text(
|
||||||
elevation = 0.dp
|
text = stringResource(id = R.string.hint_search),
|
||||||
) {
|
color = ExtendedTheme.colors.onTopBarSurface.copy(alpha = ContentAlpha.medium)
|
||||||
Row(
|
)
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
prependIcon = {
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(100))
|
.clip(RoundedCornerShape(100))
|
||||||
|
|
@ -588,76 +580,13 @@ private fun SearchTopBar(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.ArrowBack,
|
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||||
contentDescription = stringResource(id = R.string.button_back)
|
contentDescription = stringResource(id = R.string.button_back)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
BaseTextField(
|
},
|
||||||
value = keyword,
|
shape = RoundedCornerShape(6.dp)
|
||||||
onValueChange = {
|
)
|
||||||
onKeywordChange(it)
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
imeAction = ImeAction.Search,
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
onKeywordSubmit(keyword)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(id = R.string.hint_search),
|
|
||||||
color = ExtendedTheme.colors.onTopBarSurface.copy(alpha = ContentAlpha.medium)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.weight(1f)
|
|
||||||
.onFocusEvent { isFocused = it.isFocused }
|
|
||||||
)
|
|
||||||
AnimatedVisibility(visible = isKeywordNotEmpty && isFocused) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(100))
|
|
||||||
.clickable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = rememberRipple(bounded = false, 24.dp),
|
|
||||||
role = Role.Button
|
|
||||||
) { onKeywordChange("") },
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Clear,
|
|
||||||
contentDescription = stringResource(id = R.string.button_clear)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AnimatedVisibility(visible = isKeywordNotEmpty) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(100))
|
|
||||||
.clickable(
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = rememberRipple(bounded = false, 24.dp),
|
|
||||||
role = Role.Button
|
|
||||||
) { onKeywordSubmit(keyword) },
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Search,
|
|
||||||
contentDescription = stringResource(id = R.string.button_search)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview("SearchBox")
|
@Preview("SearchBox")
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
package com.huanchengfly.tieba.post.ui.page.search.thread
|
package com.huanchengfly.tieba.post.ui.page.search.thread
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.LocalContentColor
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
|
@ -20,8 +14,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
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.platform.LocalContext
|
|
||||||
import com.huanchengfly.tieba.post.api.models.SearchThreadBean
|
|
||||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||||
import com.huanchengfly.tieba.post.arch.onGlobalEvent
|
import com.huanchengfly.tieba.post.arch.onGlobalEvent
|
||||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||||
|
|
@ -32,24 +24,12 @@ import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
|
||||||
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
|
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
|
||||||
import com.huanchengfly.tieba.post.ui.page.destinations.UserProfilePageDestination
|
import com.huanchengfly.tieba.post.ui.page.destinations.UserProfilePageDestination
|
||||||
import com.huanchengfly.tieba.post.ui.page.search.SearchUiEvent
|
import com.huanchengfly.tieba.post.ui.page.search.SearchUiEvent
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Card
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ForumInfoChip
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LocalShouldLoad
|
import com.huanchengfly.tieba.post.ui.widgets.compose.LocalShouldLoad
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn
|
import com.huanchengfly.tieba.post.ui.widgets.compose.SearchThreadList
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ThreadAgreeBtn
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ThreadContent
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ThreadReplyBtn
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ThreadShareBtn
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader
|
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
||||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
|
||||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
|
@ -59,7 +39,6 @@ fun SearchThreadPage(
|
||||||
initialSortType: Int = SearchThreadSortType.SORT_TYPE_NEWEST,
|
initialSortType: Int = SearchThreadSortType.SORT_TYPE_NEWEST,
|
||||||
viewModel: SearchThreadViewModel = pageViewModel(),
|
viewModel: SearchThreadViewModel = pageViewModel(),
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
val navigator = LocalNavigator.current
|
val navigator = LocalNavigator.current
|
||||||
LazyLoad(loaded = viewModel.initialized) {
|
LazyLoad(loaded = viewModel.initialized) {
|
||||||
viewModel.send(SearchThreadUiIntent.Refresh(keyword, initialSortType))
|
viewModel.send(SearchThreadUiIntent.Refresh(keyword, initialSortType))
|
||||||
|
|
@ -187,121 +166,3 @@ fun SearchThreadPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SearchThreadList(
|
|
||||||
data: ImmutableList<SearchThreadBean.ThreadInfoBean>,
|
|
||||||
lazyListState: LazyListState,
|
|
||||||
onItemClick: (SearchThreadBean.ThreadInfoBean) -> Unit,
|
|
||||||
onItemUserClick: (SearchThreadBean.UserInfoBean) -> Unit,
|
|
||||||
onItemForumClick: (SearchThreadBean.ForumInfo) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
MyLazyColumn(
|
|
||||||
state = lazyListState,
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
items(data) {
|
|
||||||
SearchThreadItem(
|
|
||||||
item = it,
|
|
||||||
onClick = onItemClick,
|
|
||||||
onUserClick = onItemUserClick,
|
|
||||||
onForumClick = onItemForumClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SearchThreadUserHeader(
|
|
||||||
user: SearchThreadBean.UserInfoBean,
|
|
||||||
time: String,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
UserHeader(
|
|
||||||
avatar = {
|
|
||||||
Avatar(
|
|
||||||
data = StringUtil.getAvatarUrl(user.portrait),
|
|
||||||
size = Sizes.Small,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
name = {
|
|
||||||
Text(
|
|
||||||
text = StringUtil.getUsernameAnnotatedString(
|
|
||||||
LocalContext.current,
|
|
||||||
user.userName.orEmpty(),
|
|
||||||
user.showNickname,
|
|
||||||
color = LocalContentColor.current
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
desc = {
|
|
||||||
Text(
|
|
||||||
text = DateTimeUtils.getRelativeTimeString(LocalContext.current, time)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = onClick,
|
|
||||||
modifier = modifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SearchThreadItem(
|
|
||||||
item: SearchThreadBean.ThreadInfoBean,
|
|
||||||
onClick: (SearchThreadBean.ThreadInfoBean) -> Unit,
|
|
||||||
onUserClick: (SearchThreadBean.UserInfoBean) -> Unit,
|
|
||||||
onForumClick: (SearchThreadBean.ForumInfo) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Card(
|
|
||||||
modifier = modifier,
|
|
||||||
header = {
|
|
||||||
SearchThreadUserHeader(
|
|
||||||
user = item.user,
|
|
||||||
time = item.time,
|
|
||||||
onClick = { onUserClick(item.user) }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
content = {
|
|
||||||
ThreadContent(
|
|
||||||
title = item.title,
|
|
||||||
abstractText = item.content,
|
|
||||||
showTitle = item.title.isNotBlank(),
|
|
||||||
showAbstract = item.content.isNotBlank(),
|
|
||||||
)
|
|
||||||
if (item.forumName.isNotEmpty()) {
|
|
||||||
ForumInfoChip(
|
|
||||||
imageUriProvider = { item.forumInfo.avatar },
|
|
||||||
nameProvider = { item.forumName }
|
|
||||||
) {
|
|
||||||
onForumClick(item.forumInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
action = {
|
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
ThreadReplyBtn(
|
|
||||||
replyNum = item.postNum.toInt(),
|
|
||||||
onClick = {},
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
ThreadAgreeBtn(
|
|
||||||
hasAgree = false,
|
|
||||||
agreeNum = item.likeNum.toInt(),
|
|
||||||
onClick = {},
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
ThreadShareBtn(
|
|
||||||
shareNum = item.shareNum.toLong(),
|
|
||||||
onClick = {},
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick = { onClick(item) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
package com.huanchengfly.tieba.post.ui.widgets.compose
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.LocalContentColor
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Clear
|
||||||
|
import androidx.compose.material.icons.rounded.Search
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusEvent
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.huanchengfly.tieba.post.R
|
||||||
|
import com.huanchengfly.tieba.post.api.models.SearchThreadBean
|
||||||
|
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||||
|
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
||||||
|
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchThreadList(
|
||||||
|
data: ImmutableList<SearchThreadBean.ThreadInfoBean>,
|
||||||
|
lazyListState: LazyListState,
|
||||||
|
onItemClick: (SearchThreadBean.ThreadInfoBean) -> Unit,
|
||||||
|
onItemUserClick: (SearchThreadBean.UserInfoBean) -> Unit,
|
||||||
|
onItemForumClick: (SearchThreadBean.ForumInfo) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
hideForum: Boolean = false,
|
||||||
|
header: LazyListScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
MyLazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
header()
|
||||||
|
itemsIndexed(data) { index, item ->
|
||||||
|
if (index > 0) {
|
||||||
|
VerticalDivider(modifier = Modifier.padding(horizontal = 16.dp))
|
||||||
|
}
|
||||||
|
SearchThreadItem(
|
||||||
|
item = item,
|
||||||
|
onClick = onItemClick,
|
||||||
|
onUserClick = onItemUserClick,
|
||||||
|
onForumClick = onItemForumClick,
|
||||||
|
hideForum = hideForum,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchThreadUserHeader(
|
||||||
|
user: SearchThreadBean.UserInfoBean,
|
||||||
|
time: String,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
UserHeader(
|
||||||
|
avatar = {
|
||||||
|
Avatar(
|
||||||
|
data = StringUtil.getAvatarUrl(user.portrait),
|
||||||
|
size = Sizes.Small,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
name = {
|
||||||
|
Text(
|
||||||
|
text = StringUtil.getUsernameAnnotatedString(
|
||||||
|
LocalContext.current,
|
||||||
|
user.userName.orEmpty(),
|
||||||
|
user.showNickname,
|
||||||
|
color = LocalContentColor.current
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
desc = {
|
||||||
|
Text(
|
||||||
|
text = DateTimeUtils.getRelativeTimeString(LocalContext.current, time)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchThreadItem(
|
||||||
|
item: SearchThreadBean.ThreadInfoBean,
|
||||||
|
onClick: (SearchThreadBean.ThreadInfoBean) -> Unit,
|
||||||
|
onUserClick: (SearchThreadBean.UserInfoBean) -> Unit,
|
||||||
|
onForumClick: (SearchThreadBean.ForumInfo) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
hideForum: Boolean = false,
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = modifier,
|
||||||
|
header = {
|
||||||
|
SearchThreadUserHeader(
|
||||||
|
user = item.user,
|
||||||
|
time = item.time,
|
||||||
|
onClick = { onUserClick(item.user) }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = {
|
||||||
|
ThreadContent(
|
||||||
|
title = item.title,
|
||||||
|
abstractText = item.content,
|
||||||
|
showTitle = item.title.isNotBlank(),
|
||||||
|
showAbstract = item.content.isNotBlank(),
|
||||||
|
)
|
||||||
|
if (!hideForum && item.forumName.isNotEmpty()) {
|
||||||
|
ForumInfoChip(
|
||||||
|
imageUriProvider = { item.forumInfo.avatar },
|
||||||
|
nameProvider = { item.forumName }
|
||||||
|
) {
|
||||||
|
onForumClick(item.forumInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
action = {
|
||||||
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
ThreadReplyBtn(
|
||||||
|
replyNum = item.postNum.toInt(),
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
ThreadAgreeBtn(
|
||||||
|
hasAgree = false,
|
||||||
|
agreeNum = item.likeNum.toInt(),
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
ThreadShareBtn(
|
||||||
|
shareNum = item.shareNum.toLong(),
|
||||||
|
onClick = {},
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = { onClick(item) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SearchBox(
|
||||||
|
keyword: String,
|
||||||
|
onKeywordChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onKeywordSubmit: (String) -> Unit = {},
|
||||||
|
placeholder: @Composable () -> Unit = {},
|
||||||
|
prependIcon: @Composable () -> Unit = {},
|
||||||
|
appendIcon: @Composable () -> Unit = {},
|
||||||
|
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
|
shape: Shape = RectangleShape,
|
||||||
|
color: Color = ExtendedTheme.colors.topBarSurface,
|
||||||
|
contentColor: Color = ExtendedTheme.colors.onTopBarSurface,
|
||||||
|
elevation: Dp = 0.dp,
|
||||||
|
) {
|
||||||
|
val isKeywordNotEmpty = remember(keyword) { keyword.isNotEmpty() }
|
||||||
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
|
Surface(
|
||||||
|
modifier = modifier,
|
||||||
|
shape = shape,
|
||||||
|
color = color,
|
||||||
|
contentColor = contentColor,
|
||||||
|
elevation = 0.dp
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
prependIcon()
|
||||||
|
BaseTextField(
|
||||||
|
value = keyword,
|
||||||
|
onValueChange = {
|
||||||
|
onKeywordChange(it)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Search,
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
onKeywordSubmit(keyword)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
placeholder = placeholder,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(1f)
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.onFocusEvent {
|
||||||
|
isFocused = it.isFocused
|
||||||
|
}
|
||||||
|
)
|
||||||
|
appendIcon()
|
||||||
|
AnimatedVisibility(visible = isKeywordNotEmpty && isFocused) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(100))
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = rememberRipple(bounded = false, 24.dp),
|
||||||
|
role = Role.Button
|
||||||
|
) { onKeywordChange("") },
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Clear,
|
||||||
|
contentDescription = stringResource(id = R.string.button_clear)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = isKeywordNotEmpty) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(100))
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = rememberRipple(bounded = false, 24.dp),
|
||||||
|
role = Role.Button
|
||||||
|
) { onKeywordSubmit(keyword) },
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Search,
|
||||||
|
contentDescription = stringResource(id = R.string.button_search)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue