feat: 新版搜索状态屏
This commit is contained in:
parent
23f73f2e1a
commit
5f7203228e
|
|
@ -72,6 +72,8 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.emitGlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.emitGlobalEventSuspend
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.models.database.SearchHistory
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
|
|
@ -134,12 +136,17 @@ fun SearchPage(
|
|||
derivedStateOf { keyword.isEmpty() }
|
||||
}
|
||||
var inputKeyword by remember { mutableStateOf("") }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
val initialSortType = remember { SearchThreadSortType.SORT_TYPE_NEWEST }
|
||||
var searchThreadSortType by remember { mutableIntStateOf(initialSortType) }
|
||||
LaunchedEffect(searchThreadSortType) {
|
||||
emitGlobalEvent(SearchThreadUiEvent.SwitchSortType(searchThreadSortType))
|
||||
}
|
||||
viewModel.onEvent<SearchUiEvent.KeywordChanged> {
|
||||
inputKeyword = it.keyword
|
||||
emitGlobalEventSuspend(it)
|
||||
}
|
||||
|
||||
val pages by remember {
|
||||
derivedStateOf {
|
||||
|
|
@ -234,6 +241,8 @@ fun SearchPage(
|
|||
inputKeyword = it.content
|
||||
viewModel.send(SearchUiIntent.SubmitKeyword(it.content))
|
||||
},
|
||||
expanded = expanded,
|
||||
onToggleExpand = { expanded = !expanded },
|
||||
onClear = { viewModel.send(SearchUiIntent.ClearSearchHistory) }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ class SearchViewModel :
|
|||
App.INSTANCE.getString(R.string.toast_clear_failure, partialChange.errorMessage)
|
||||
)
|
||||
|
||||
is SearchPartialChange.SubmitKeyword -> SearchUiEvent.KeywordChanged(partialChange.keyword)
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
@ -145,4 +147,6 @@ data class SearchUiState(
|
|||
val searchHistories: ImmutableList<SearchHistory> = persistentListOf(),
|
||||
) : UiState
|
||||
|
||||
sealed interface SearchUiEvent : UiEvent
|
||||
sealed interface SearchUiEvent : UiEvent {
|
||||
data class KeywordChanged(val keyword: String) : SearchUiEvent
|
||||
}
|
||||
|
|
@ -30,16 +30,20 @@ import androidx.compose.ui.unit.dp
|
|||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.models.SearchForumBean
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onGlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.pullRefreshIndicator
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.search.SearchUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LocalShouldLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||
|
|
@ -53,20 +57,17 @@ fun SearchForumPage(
|
|||
viewModel.send(SearchForumUiIntent.Refresh(keyword))
|
||||
viewModel.initialized = true
|
||||
}
|
||||
|
||||
val shouldLoad = LocalShouldLoad.current
|
||||
LaunchedEffect(keyword) {
|
||||
if (viewModel.initialized) {
|
||||
if (shouldLoad) {
|
||||
viewModel.send(SearchForumUiIntent.Refresh(keyword))
|
||||
} else {
|
||||
viewModel.initialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
val currentKeyword by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchForumUiState::keyword,
|
||||
initial = ""
|
||||
)
|
||||
val isRefreshing by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchForumUiState::isRefreshing,
|
||||
initial = false
|
||||
initial = true
|
||||
)
|
||||
val error by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchForumUiState::error,
|
||||
initial = null
|
||||
)
|
||||
val exactMatchForum by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchForumUiState::exactMatchForum,
|
||||
|
|
@ -89,6 +90,36 @@ fun SearchForumPage(
|
|||
onRefresh = { viewModel.send(SearchForumUiIntent.Refresh(keyword)) }
|
||||
)
|
||||
|
||||
val isEmpty by remember {
|
||||
derivedStateOf { !showExactMatchResult && !showFuzzyMatchResult }
|
||||
}
|
||||
|
||||
onGlobalEvent<SearchUiEvent.KeywordChanged> {
|
||||
viewModel.send(SearchForumUiIntent.Refresh(it.keyword))
|
||||
}
|
||||
val shouldLoad = LocalShouldLoad.current
|
||||
LaunchedEffect(currentKeyword) {
|
||||
if (currentKeyword.isNotEmpty() && keyword != currentKeyword) {
|
||||
if (shouldLoad) {
|
||||
viewModel.send(SearchForumUiIntent.Refresh(keyword))
|
||||
} else {
|
||||
viewModel.initialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateScreen(
|
||||
isEmpty = isEmpty,
|
||||
isError = error != null,
|
||||
isLoading = isRefreshing,
|
||||
onReload = { viewModel.send(SearchForumUiIntent.Refresh(keyword)) },
|
||||
errorScreen = {
|
||||
error?.let {
|
||||
val (e) = it
|
||||
ErrorScreen(error = e)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -153,6 +184,8 @@ fun SearchForumPage(
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchForumItem(
|
||||
item: SearchForumBean.ForumInfoBean,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ data class SearchForumUiState(
|
|||
val keyword: String = "",
|
||||
val exactMatchForum: SearchForumBean.ForumInfoBean? = null,
|
||||
val fuzzyMatchForumList: List<SearchForumBean.ForumInfoBean> = persistentListOf(),
|
||||
val isRefreshing: Boolean = false,
|
||||
val isRefreshing: Boolean = true,
|
||||
val error: ImmutableHolder<Throwable>? = null,
|
||||
) : UiState
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ import androidx.compose.material.pullrefresh.pullRefresh
|
|||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.platform.LocalContext
|
||||
|
|
@ -30,8 +32,10 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.pullRefreshIndicator
|
|||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
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.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.ForumInfoChip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
|
||||
|
|
@ -42,6 +46,7 @@ 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.utils.DateTimeUtils
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -60,9 +65,13 @@ fun SearchThreadPage(
|
|||
viewModel.send(SearchThreadUiIntent.Refresh(keyword, initialSortType))
|
||||
viewModel.initialized = true
|
||||
}
|
||||
val currentKeyword by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchThreadUiState::keyword,
|
||||
initial = ""
|
||||
)
|
||||
val isRefreshing by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchThreadUiState::isRefreshing,
|
||||
initial = false
|
||||
initial = true
|
||||
)
|
||||
val isLoadingMore by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchThreadUiState::isLoadingMore,
|
||||
|
|
@ -88,9 +97,13 @@ fun SearchThreadPage(
|
|||
prop1 = SearchThreadUiState::sortType,
|
||||
initial = initialSortType
|
||||
)
|
||||
|
||||
onGlobalEvent<SearchThreadUiEvent.SwitchSortType> {
|
||||
viewModel.send(SearchThreadUiIntent.Refresh(keyword, it.sortType))
|
||||
}
|
||||
val shouldLoad = LocalShouldLoad.current
|
||||
LaunchedEffect(keyword) {
|
||||
if (viewModel.initialized) {
|
||||
LaunchedEffect(currentKeyword) {
|
||||
if (currentKeyword.isNotEmpty() && keyword != currentKeyword) {
|
||||
if (shouldLoad) {
|
||||
viewModel.send(SearchThreadUiIntent.Refresh(keyword, sortType))
|
||||
} else {
|
||||
|
|
@ -99,16 +112,32 @@ fun SearchThreadPage(
|
|||
}
|
||||
}
|
||||
|
||||
onGlobalEvent<SearchThreadUiEvent.SwitchSortType> {
|
||||
viewModel.send(SearchThreadUiIntent.Refresh(keyword, it.sortType))
|
||||
}
|
||||
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing,
|
||||
onRefresh = { viewModel.send(SearchThreadUiIntent.Refresh(keyword, sortType)) }
|
||||
)
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
val isEmpty by remember {
|
||||
derivedStateOf { data.isEmpty() }
|
||||
}
|
||||
|
||||
onGlobalEvent<SearchUiEvent.KeywordChanged> {
|
||||
viewModel.send(SearchThreadUiIntent.Refresh(it.keyword, sortType))
|
||||
}
|
||||
|
||||
StateScreen(
|
||||
isEmpty = isEmpty,
|
||||
isError = error != null,
|
||||
isLoading = isRefreshing,
|
||||
onReload = { viewModel.send(SearchThreadUiIntent.Refresh(keyword, sortType)) },
|
||||
errorScreen = {
|
||||
error?.let {
|
||||
val (e) = it
|
||||
ErrorScreen(error = e)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -156,6 +185,7 @@ fun SearchThreadPage(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchThreadList(
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ sealed interface SearchThreadPartialChange : PartialChange<SearchThreadUiState>
|
|||
}
|
||||
|
||||
data class SearchThreadUiState(
|
||||
val isRefreshing: Boolean = false,
|
||||
val isRefreshing: Boolean = true,
|
||||
val isLoadingMore: Boolean = false,
|
||||
val error: ImmutableHolder<Throwable>? = null,
|
||||
val currentPage: Int = 1,
|
||||
|
|
|
|||
|
|
@ -31,15 +31,19 @@ import androidx.compose.ui.unit.dp
|
|||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.models.SearchUserBean
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onGlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.pullRefreshIndicator
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.search.SearchUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LocalShouldLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -55,19 +59,17 @@ fun SearchUserPage(
|
|||
viewModel.send(SearchUserUiIntent.Refresh(keyword))
|
||||
viewModel.initialized = true
|
||||
}
|
||||
val shouldLoad = LocalShouldLoad.current
|
||||
LaunchedEffect(keyword) {
|
||||
if (viewModel.initialized) {
|
||||
if (shouldLoad) {
|
||||
viewModel.send(SearchUserUiIntent.Refresh(keyword))
|
||||
} else {
|
||||
viewModel.initialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
val currentKeyword by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchUserUiState::keyword,
|
||||
initial = ""
|
||||
)
|
||||
val isRefreshing by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchUserUiState::isRefreshing,
|
||||
initial = false
|
||||
initial = true
|
||||
)
|
||||
val error by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = SearchUserUiState::error,
|
||||
initial = null
|
||||
)
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isRefreshing,
|
||||
|
|
@ -89,6 +91,36 @@ fun SearchUserPage(
|
|||
derivedStateOf { fuzzyMatch.isNotEmpty() }
|
||||
}
|
||||
|
||||
onGlobalEvent<SearchUiEvent.KeywordChanged> {
|
||||
viewModel.send(SearchUserUiIntent.Refresh(it.keyword))
|
||||
}
|
||||
val shouldLoad = LocalShouldLoad.current
|
||||
LaunchedEffect(currentKeyword) {
|
||||
if (currentKeyword.isNotEmpty() && keyword != currentKeyword) {
|
||||
if (shouldLoad) {
|
||||
viewModel.send(SearchUserUiIntent.Refresh(keyword))
|
||||
} else {
|
||||
viewModel.initialized = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isEmpty by remember {
|
||||
derivedStateOf { !showExactMatchResult && !showFuzzyMatchResult }
|
||||
}
|
||||
|
||||
StateScreen(
|
||||
isEmpty = isEmpty,
|
||||
isError = error != null,
|
||||
isLoading = isRefreshing,
|
||||
onReload = { viewModel.send(SearchUserUiIntent.Refresh(keyword)) },
|
||||
errorScreen = {
|
||||
error?.let {
|
||||
val (e) = it
|
||||
ErrorScreen(error = e)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -150,6 +182,7 @@ fun SearchUserPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SearchUserItem(
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ sealed interface SearchUserPartialChange : PartialChange<SearchUserUiState> {
|
|||
}
|
||||
|
||||
data class SearchUserUiState(
|
||||
val isRefreshing: Boolean = false,
|
||||
val isRefreshing: Boolean = true,
|
||||
val error: ImmutableHolder<Throwable>? = null,
|
||||
val keyword: String = "",
|
||||
val exactMatch: SearchUserBean.UserBean? = null,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
|
|
@ -27,8 +29,11 @@ fun LazyLoad(
|
|||
onLoad: () -> Unit,
|
||||
) {
|
||||
val shouldLoad = LocalShouldLoad.current
|
||||
LaunchedEffect(loaded, shouldLoad, onLoad) {
|
||||
if (!loaded && shouldLoad) onLoad()
|
||||
val curOnLoad by rememberUpdatedState(newValue = onLoad)
|
||||
LaunchedEffect(loaded, shouldLoad) {
|
||||
if (!loaded && shouldLoad) {
|
||||
curOnLoad()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue