diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchPage.kt index b79bcf4d..eaaba2dc 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchPage.kt @@ -6,6 +6,7 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.PressInteraction import androidx.compose.foundation.layout.Arrangement @@ -271,6 +272,7 @@ fun SearchPage( }, expanded = expanded, onToggleExpand = { expanded = !expanded }, + onDelete = { viewModel.send(SearchUiIntent.DeleteSearchHistory(it.id)) }, onClear = { viewModel.send(SearchUiIntent.ClearSearchHistory) } ) } @@ -389,13 +391,14 @@ private fun ColumnScope.SearchTabRow( } } -@OptIn(ExperimentalLayoutApi::class) +@OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class) @Composable private fun SearchHistoryList( searchHistories: ImmutableList, onSearchHistoryClick: (SearchHistory) -> Unit, expanded: Boolean = false, onToggleExpand: () -> Unit = {}, + onDelete: (SearchHistory) -> Unit = {}, onClear: () -> Unit = {}, ) { val hasMore = remember(searchHistories) { @@ -438,7 +441,10 @@ private fun SearchHistoryList( modifier = Modifier .padding(bottom = 8.dp) .clip(RoundedCornerShape(100)) - .clickable { onSearchHistoryClick(searchHistory) } + .combinedClickable( + onClick = { onSearchHistoryClick(searchHistory) }, + onLongClick = { onDelete(searchHistory) } + ) .background(ExtendedTheme.colors.chip) .padding(horizontal = 16.dp, vertical = 8.dp) ) { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchViewModel.kt index e7602cdd..4d6e50ac 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchViewModel.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/search/SearchViewModel.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import org.litepal.LitePal +import org.litepal.extension.delete import org.litepal.extension.deleteAll import org.litepal.extension.find @@ -47,6 +48,10 @@ class SearchViewModel : App.INSTANCE.getString(R.string.toast_clear_failure, partialChange.errorMessage) ) + is SearchPartialChange.DeleteSearchHistory.Failure -> CommonUiEvent.Toast( + App.INSTANCE.getString(R.string.toast_delete_failure, partialChange.errorMessage) + ) + is SearchPartialChange.SubmitKeyword -> SearchUiEvent.KeywordChanged(partialChange.keyword) else -> null @@ -61,6 +66,8 @@ class SearchViewModel : .flatMapConcat { produceInitPartialChange() }, intentFlow.filterIsInstance() .flatMapConcat { produceClearHistoryPartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, intentFlow.filterIsInstance() .flatMapConcat { it.producePartialChange() }, ) @@ -83,6 +90,14 @@ class SearchViewModel : emit(SearchPartialChange.ClearSearchHistory.Failure(it.getErrorMessage())) }.flowOn(Dispatchers.IO) + private fun SearchUiIntent.DeleteSearchHistory.producePartialChange() = + flow { + LitePal.delete(id) + emit(SearchPartialChange.DeleteSearchHistory.Success(id)) + }.catch { + emit(SearchPartialChange.DeleteSearchHistory.Failure(it.getErrorMessage())) + }.flowOn(Dispatchers.IO) + private fun SearchUiIntent.SubmitKeyword.producePartialChange() = flowOf(SearchPartialChange.SubmitKeyword(keyword.trim())) .onEach { @@ -102,6 +117,8 @@ sealed interface SearchUiIntent : UiIntent { data object ClearSearchHistory : SearchUiIntent + data class DeleteSearchHistory(val id: Long) : SearchUiIntent + data class SubmitKeyword(val keyword: String) : SearchUiIntent } @@ -130,6 +147,23 @@ sealed interface SearchPartialChange : PartialChange { ) : ClearSearchHistory() } + sealed class DeleteSearchHistory : SearchPartialChange { + override fun reduce(oldState: SearchUiState): SearchUiState = when (this) { + is Success -> oldState.copy( + searchHistories = oldState.searchHistories.filterNot { it.id == id } + .toImmutableList() + ) + + is Failure -> oldState + } + + data class Success(val id: Long) : DeleteSearchHistory() + + data class Failure( + val errorMessage: String, + ) : DeleteSearchHistory() + } + data class SubmitKeyword(val keyword: String) : SearchPartialChange { override fun reduce(oldState: SearchUiState): SearchUiState { if (keyword.isEmpty()) {