pref: 交换清除全部和展开更多历史按钮
This commit is contained in:
parent
5f7203228e
commit
4e6d9aab70
|
|
@ -6,28 +6,23 @@ import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
internal val DefaultTextStyle = TextStyle.Default.copy()
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
// Set of Material typography styles to start with
|
||||||
val Typography = Typography(
|
val Typography = Typography(
|
||||||
body1 = TextStyle(
|
body1 = DefaultTextStyle.copy(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
),
|
),
|
||||||
subtitle1 = TextStyle(
|
subtitle1 = DefaultTextStyle.copy(
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
letterSpacing = 0.15.sp
|
letterSpacing = 0.15.sp
|
||||||
),
|
),
|
||||||
/* Other default text styles to override
|
button = DefaultTextStyle.copy(
|
||||||
button = TextStyle(
|
fontWeight = FontWeight.Medium,
|
||||||
fontFamily = FontFamily.Default,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W500,
|
letterSpacing = 0.15.sp
|
||||||
fontSize = 14.sp
|
|
||||||
),
|
|
||||||
caption = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 12.sp
|
|
||||||
)
|
)
|
||||||
*/
|
|
||||||
)
|
)
|
||||||
|
|
@ -38,8 +38,9 @@ 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.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.rounded.ArrowDropDown
|
import androidx.compose.material.icons.rounded.ArrowDropDown
|
||||||
import androidx.compose.material.icons.rounded.CleaningServices
|
|
||||||
import androidx.compose.material.icons.rounded.Clear
|
import androidx.compose.material.icons.rounded.Clear
|
||||||
|
import androidx.compose.material.icons.rounded.ExpandLess
|
||||||
|
import androidx.compose.material.icons.rounded.ExpandMore
|
||||||
import androidx.compose.material.icons.rounded.Search
|
import androidx.compose.material.icons.rounded.Search
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -79,6 +80,7 @@ import com.huanchengfly.tieba.post.models.database.SearchHistory
|
||||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.TiebaLiteTheme
|
import com.huanchengfly.tieba.post.ui.common.theme.compose.TiebaLiteTheme
|
||||||
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
|
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
|
||||||
|
import com.huanchengfly.tieba.post.ui.page.destinations.SearchPageDestination
|
||||||
import com.huanchengfly.tieba.post.ui.page.search.forum.SearchForumPage
|
import com.huanchengfly.tieba.post.ui.page.search.forum.SearchForumPage
|
||||||
import com.huanchengfly.tieba.post.ui.page.search.thread.SearchThreadPage
|
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
|
||||||
|
|
@ -88,6 +90,7 @@ 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.ClickMenu
|
import com.huanchengfly.tieba.post.ui.widgets.compose.ClickMenu
|
||||||
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.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.TopAppBarContainer
|
import com.huanchengfly.tieba.post.ui.widgets.compose.TopAppBarContainer
|
||||||
|
|
@ -132,10 +135,25 @@ fun SearchPage(
|
||||||
prop1 = SearchUiState::keyword,
|
prop1 = SearchUiState::keyword,
|
||||||
initial = ""
|
initial = ""
|
||||||
)
|
)
|
||||||
val isKeywordEmpty by remember {
|
|
||||||
derivedStateOf { keyword.isEmpty() }
|
val isKeywordEmpty by viewModel.uiState.collectPartialAsState(
|
||||||
}
|
prop1 = SearchUiState::isKeywordEmpty,
|
||||||
|
initial = true
|
||||||
|
)
|
||||||
var inputKeyword by remember { mutableStateOf("") }
|
var inputKeyword by remember { mutableStateOf("") }
|
||||||
|
LaunchedEffect(keyword) {
|
||||||
|
if (keyword.isNotEmpty() && keyword != inputKeyword) {
|
||||||
|
inputKeyword = keyword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MyBackHandler(
|
||||||
|
enabled = !isKeywordEmpty,
|
||||||
|
currentScreen = SearchPageDestination
|
||||||
|
) {
|
||||||
|
viewModel.send(SearchUiIntent.SubmitKeyword(""))
|
||||||
|
}
|
||||||
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val initialSortType = remember { SearchThreadSortType.SORT_TYPE_NEWEST }
|
val initialSortType = remember { SearchThreadSortType.SORT_TYPE_NEWEST }
|
||||||
|
|
@ -145,7 +163,7 @@ fun SearchPage(
|
||||||
}
|
}
|
||||||
viewModel.onEvent<SearchUiEvent.KeywordChanged> {
|
viewModel.onEvent<SearchUiEvent.KeywordChanged> {
|
||||||
inputKeyword = it.keyword
|
inputKeyword = it.keyword
|
||||||
emitGlobalEventSuspend(it)
|
if (it.keyword.isNotBlank()) emitGlobalEventSuspend(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pages by remember {
|
val pages by remember {
|
||||||
|
|
@ -213,7 +231,13 @@ fun SearchPage(
|
||||||
onKeywordSubmit = {
|
onKeywordSubmit = {
|
||||||
viewModel.send(SearchUiIntent.SubmitKeyword(it))
|
viewModel.send(SearchUiIntent.SubmitKeyword(it))
|
||||||
},
|
},
|
||||||
onBack = { navigator.navigateUp() }
|
onBack = {
|
||||||
|
if (isKeywordEmpty) {
|
||||||
|
navigator.navigateUp()
|
||||||
|
} else {
|
||||||
|
viewModel.send(SearchUiIntent.SubmitKeyword(""))
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -224,17 +248,21 @@ fun SearchPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
if (!isKeywordEmpty) {
|
if (!isKeywordEmpty) {
|
||||||
ProvideNavigator(navigator = navigator) {
|
ProvideNavigator(navigator = navigator) {
|
||||||
LazyLoadHorizontalPager(
|
LazyLoadHorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
key = { pages[it].id }
|
key = { pages[it].id },
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
pages[it].content()
|
pages[it].content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Column {
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
SearchHistoryList(
|
SearchHistoryList(
|
||||||
searchHistories = searchHistories,
|
searchHistories = searchHistories,
|
||||||
onSearchHistoryClick = {
|
onSearchHistoryClick = {
|
||||||
|
|
@ -249,6 +277,7 @@ fun SearchPage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -390,12 +419,10 @@ private fun SearchHistoryList(
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
style = MaterialTheme.typography.subtitle1
|
style = MaterialTheme.typography.subtitle1
|
||||||
)
|
)
|
||||||
if (hasMore) {
|
if (hasItem) {
|
||||||
Text(
|
Text(
|
||||||
text = if (!expanded) stringResource(id = R.string.button_expand) else stringResource(
|
text = stringResource(id = R.string.button_clear_all),
|
||||||
id = R.string.button_collapse
|
modifier = Modifier.clickable(onClick = onClear),
|
||||||
),
|
|
||||||
modifier = Modifier.clickable(onClick = onToggleExpand),
|
|
||||||
style = MaterialTheme.typography.button
|
style = MaterialTheme.typography.button
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -421,9 +448,9 @@ private fun SearchHistoryList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasItem) {
|
if (hasMore) {
|
||||||
Button(
|
Button(
|
||||||
onClick = onClear,
|
onClick = onToggleExpand,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
colors = ButtonDefaults.textButtonColors(
|
colors = ButtonDefaults.textButtonColors(
|
||||||
backgroundColor = Color.Transparent,
|
backgroundColor = Color.Transparent,
|
||||||
|
|
@ -435,18 +462,21 @@ private fun SearchHistoryList(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.CleaningServices,
|
imageVector = if (expanded) Icons.Rounded.ExpandLess else Icons.Rounded.ExpandMore,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.title_clear_search_history),
|
text = stringResource(
|
||||||
|
id = if (expanded) R.string.button_expand_less_history else R.string.button_expand_more_history
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.button,
|
style = MaterialTheme.typography.button,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
if (!hasItem) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,14 @@ class SearchViewModel :
|
||||||
}.flowOn(Dispatchers.IO)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
private fun SearchUiIntent.SubmitKeyword.producePartialChange() =
|
private fun SearchUiIntent.SubmitKeyword.producePartialChange() =
|
||||||
flowOf(SearchPartialChange.SubmitKeyword(keyword))
|
flowOf(SearchPartialChange.SubmitKeyword(keyword.trim()))
|
||||||
.onEach {
|
.onEach {
|
||||||
runCatching {
|
runCatching {
|
||||||
SearchHistory(keyword).saveOrUpdate("content = ?", keyword)
|
val trimKeyword = keyword.trim()
|
||||||
|
if (trimKeyword.isNotBlank()) SearchHistory(trimKeyword).saveOrUpdate(
|
||||||
|
"content = ?",
|
||||||
|
trimKeyword
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +123,7 @@ sealed interface SearchPartialChange : PartialChange<SearchUiState> {
|
||||||
is Failure -> oldState
|
is Failure -> oldState
|
||||||
}
|
}
|
||||||
|
|
||||||
object Success : ClearSearchHistory()
|
data object Success : ClearSearchHistory()
|
||||||
|
|
||||||
data class Failure(
|
data class Failure(
|
||||||
val errorMessage: String,
|
val errorMessage: String,
|
||||||
|
|
@ -129,13 +133,14 @@ sealed interface SearchPartialChange : PartialChange<SearchUiState> {
|
||||||
data class SubmitKeyword(val keyword: String) : SearchPartialChange {
|
data class SubmitKeyword(val keyword: String) : SearchPartialChange {
|
||||||
override fun reduce(oldState: SearchUiState): SearchUiState {
|
override fun reduce(oldState: SearchUiState): SearchUiState {
|
||||||
if (keyword.isEmpty()) {
|
if (keyword.isEmpty()) {
|
||||||
return oldState.copy(keyword = keyword)
|
return oldState.copy(isKeywordEmpty = true)
|
||||||
}
|
}
|
||||||
val newSearchHistories = (oldState.searchHistories
|
val newSearchHistories = (oldState.searchHistories
|
||||||
.filterNot { it.content == keyword } + SearchHistory(content = keyword))
|
.filterNot { it.content == keyword } + SearchHistory(content = keyword))
|
||||||
.sortedByDescending { it.timestamp }
|
.sortedByDescending { it.timestamp }
|
||||||
return oldState.copy(
|
return oldState.copy(
|
||||||
keyword = keyword,
|
keyword = keyword,
|
||||||
|
isKeywordEmpty = false,
|
||||||
searchHistories = newSearchHistories.toImmutableList()
|
searchHistories = newSearchHistories.toImmutableList()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +149,7 @@ sealed interface SearchPartialChange : PartialChange<SearchUiState> {
|
||||||
|
|
||||||
data class SearchUiState(
|
data class SearchUiState(
|
||||||
val keyword: String = "",
|
val keyword: String = "",
|
||||||
|
val isKeywordEmpty: Boolean = true,
|
||||||
val searchHistories: ImmutableList<SearchHistory> = persistentListOf(),
|
val searchHistories: ImmutableList<SearchHistory> = persistentListOf(),
|
||||||
) : UiState
|
) : UiState
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -716,4 +716,7 @@
|
||||||
<string name="toast_clear_failure">清除失败 %s</string>
|
<string name="toast_clear_failure">清除失败 %s</string>
|
||||||
<string name="title_recommend">推荐</string>
|
<string name="title_recommend">推荐</string>
|
||||||
<string name="title_fuzzy_match_user">相关用户</string>
|
<string name="title_fuzzy_match_user">相关用户</string>
|
||||||
|
<string name="button_expand_more_history">展开历史</string>
|
||||||
|
<string name="button_expand_less_history">收起历史</string>
|
||||||
|
<string name="button_clear_all">清除全部</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue