Skip to content

Commit

Permalink
feat: 보낸 봉투 최근 검색어 기능 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
yangsooplus committed Jan 31, 2024
1 parent 4d2133b commit 9294c06
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.susu.feature.sent.navigation.navigateSentEnvelope
import com.susu.feature.sent.navigation.navigateSentEnvelopeAdd
import com.susu.feature.sent.navigation.navigateSentEnvelopeDetail
import com.susu.feature.sent.navigation.navigateSentEnvelopeEdit
import com.susu.feature.sent.navigation.navigateSentEnvelopeSearch
import com.susu.feature.statistics.navigation.navigateStatistics

internal class MainNavigator(
Expand All @@ -64,6 +65,7 @@ internal class MainNavigator(
SentRoute.sentEnvelopeRoute,
SentRoute.sentEnvelopeDetailRoute,
SentRoute.sentEnvelopeEditRoute,
SentRoute.sentEnvelopeSearchRoute,
CommunityRoute.route,
CommunityRoute.voteAddRoute,
CommunityRoute.voteSearchRoute,
Expand Down Expand Up @@ -116,6 +118,10 @@ internal class MainNavigator(
navController.navigateSentEnvelopeAdd()
}

fun navigateSentEnvelopeSearch() {
navController.navigateSentEnvelopeSearch()
}

fun navigateLogin() {
navController.navigate(LoginSignupRoute.Parent.Login.route) {
popUpTo(id = navController.graph.id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ internal fun MainScreen(
navigateSentEnvelopeDetail = navigator::navigateSentEnvelopeDetail,
navigateSentEnvelopeEdit = navigator::navigateSentEnvelopeEdit,
navigateSentEnvelopeAdd = navigator::navigateSentEnvelopeAdd,
navigateSentEnvelopeSearch = navigator::navigateSentEnvelopeSearch,
handleException = viewModel::handleException,
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.susu.feature.envelopesearch

import com.susu.core.model.Envelope
import com.susu.core.ui.base.SideEffect
import com.susu.core.ui.base.UiState
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf

data class EnvelopeSearchState(
val searchKeyword: String = "",
val recentSearchKeywordList: PersistentList<String> = persistentListOf(),
val envelopeList: PersistentList<Envelope> = persistentListOf(),
) : UiState

sealed interface EnvelopeSearchEffect : SideEffect {
data object PopBackStack : EnvelopeSearchEffect
data class NavigateEnvelopDetail(val envelope: Envelope) : EnvelopeSearchEffect
data object FocusClear : EnvelopeSearchEffect
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.susu.core.designsystem.component.appbar.SusuDefaultAppBar
import com.susu.core.designsystem.component.appbar.icon.BackIcon
import com.susu.core.designsystem.component.container.SusuRecentSearchContainer
Expand All @@ -23,30 +27,68 @@ import com.susu.core.designsystem.theme.Gray60
import com.susu.core.designsystem.theme.Gray80
import com.susu.core.designsystem.theme.SusuTheme
import com.susu.core.model.Envelope
import com.susu.core.model.Friend
import com.susu.core.ui.extension.collectWithLifecycle
import com.susu.feature.sent.R
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.debounce

@OptIn(FlowPreview::class)
@Composable
fun SentEnvelopeSearchRoute() {
fun SentEnvelopeSearchRoute(
viewModel: EnvelopeSearchViewModel = hiltViewModel(),
popBackStack: () -> Unit,
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value

viewModel.sideEffect.collectWithLifecycle { sideEffect ->
when (sideEffect) {
EnvelopeSearchEffect.FocusClear -> {}
is EnvelopeSearchEffect.NavigateEnvelopDetail -> {}
EnvelopeSearchEffect.PopBackStack -> popBackStack()
}
}

LaunchedEffect(key1 = Unit) {
viewModel.getEnvelopeRecentSearchList()
}

LaunchedEffect(key1 = uiState.searchKeyword) {
snapshotFlow { uiState.searchKeyword }
.debounce(100L)
.collect(viewModel::getEnvelopeList)
}

SentEnvelopeSearchScreen(
uiState = uiState,
onSearchKeywordUpdated = viewModel::updateSearchKeyword,
onClickClearIcon = { viewModel.updateSearchKeyword("") },
onSelectRecentSearch = {
viewModel.upsertEnvelopeRecentSearch(it)
viewModel.updateSearchKeyword(it)
},
onDeleteRecentSearch = viewModel::deleteEnvelopeRecentSearch,
popBackStack = popBackStack,
)
}

@Composable
fun SentEnvelopeSearchScreen(
searchText: String = "",
recentSearch: PersistentList<String> = persistentListOf(),
searchResult: PersistentList<Envelope> = persistentListOf(),
onSelectRecentSearch: (Int) -> Unit = {},
onDeleteRecentSearch: (Int) -> Unit = {},
uiState: EnvelopeSearchState = EnvelopeSearchState(),
onSearchKeywordUpdated: (String) -> Unit = {},
onClickClearIcon: () -> Unit = {},
onSelectRecentSearch: (String) -> Unit = {},
onDeleteRecentSearch: (String) -> Unit = {},
onClickEnvelope: (Envelope) -> Unit = {},
popBackStack: () -> Unit = {},
) {
Column(
modifier = Modifier.fillMaxSize(),
) {
SusuDefaultAppBar(
leftIcon = {
BackIcon()
BackIcon(onClick = popBackStack)
},
)
Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxs))
Expand All @@ -55,11 +97,13 @@ fun SentEnvelopeSearchScreen(
horizontal = SusuTheme.spacing.spacing_m,
vertical = SusuTheme.spacing.spacing_xxs,
),
value = searchText,
value = uiState.searchKeyword,
placeholder = stringResource(R.string.sent_envelope_search_search_title),
onValueChange = onSearchKeywordUpdated,
onClickClearIcon = onClickClearIcon,
)
if (searchText.isEmpty()) {
if (recentSearch.isEmpty()) {
if (uiState.searchKeyword.isEmpty()) {
if (uiState.recentSearchKeywordList.isEmpty()) {
EmptyRecentSearch(modifier = Modifier.fillMaxWidth())
} else {
Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_xxl))
Expand All @@ -71,8 +115,10 @@ fun SentEnvelopeSearchScreen(
)
Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_m))
RecentSearchColumn(
modifier = Modifier.fillMaxWidth().padding(horizontal = SusuTheme.spacing.spacing_m),
recentSearchList = recentSearch,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = SusuTheme.spacing.spacing_m),
recentSearchList = uiState.recentSearchKeywordList,
onClickItem = onSelectRecentSearch,
onClickClearIcon = onDeleteRecentSearch,
)
Expand All @@ -85,13 +131,15 @@ fun SentEnvelopeSearchScreen(
style = SusuTheme.typography.title_xxs,
color = Gray60,
)
if (searchResult.isEmpty()) {
if (uiState.envelopeList.isEmpty()) {
EmptySearchEnvelope(modifier = Modifier.fillMaxWidth())
} else {
Spacer(modifier = Modifier.height(SusuTheme.spacing.spacing_m))
SearchEnvelopeColumn(
modifier = Modifier.fillMaxWidth().padding(horizontal = SusuTheme.spacing.spacing_m),
searchResult = searchResult,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = SusuTheme.spacing.spacing_m),
searchResult = uiState.envelopeList,
onClickItem = onClickEnvelope,
)
}
Expand Down Expand Up @@ -128,18 +176,18 @@ fun EmptyRecentSearch(
fun RecentSearchColumn(
modifier: Modifier = Modifier,
recentSearchList: PersistentList<String> = persistentListOf(),
onClickItem: (Int) -> Unit = {},
onClickClearIcon: (Int) -> Unit = {},
onClickItem: (String) -> Unit = {},
onClickClearIcon: (String) -> Unit = {},
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(SusuTheme.spacing.spacing_m),
) {
recentSearchList.forEachIndexed { index, keyword ->
recentSearchList.forEach { keyword ->
SusuRecentSearchContainer(
text = keyword,
onClick = { onClickItem(index) },
onClickCloseIcon = { onClickClearIcon(index) },
onClick = { onClickItem(keyword) },
onClickCloseIcon = { onClickClearIcon(keyword) },
)
}
}
Expand Down Expand Up @@ -194,9 +242,6 @@ fun SearchEnvelopeColumn(
@Composable
fun SentEnvelopeSearchScreenPreview() {
SusuTheme {
SentEnvelopeSearchScreen(
searchText = "d",
searchResult = persistentListOf(Envelope(friend = Friend(name = "김수수"))),
)
SentEnvelopeSearchScreen()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.susu.feature.envelopesearch

import androidx.lifecycle.viewModelScope
import com.susu.core.ui.base.BaseViewModel
import com.susu.domain.usecase.enveloperecentsearch.DeleteEnvelopeRecentSearchUseCase
import com.susu.domain.usecase.enveloperecentsearch.GetEnvelopeRecentSearchListUseCase
import com.susu.domain.usecase.enveloperecentsearch.UpsertEnvelopeRecentSearchUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class EnvelopeSearchViewModel @Inject constructor(
private val getEnvelopeRecentSearchUserCase: GetEnvelopeRecentSearchListUseCase,
private val deleteEnvelopeRecentSearchUseCase: DeleteEnvelopeRecentSearchUseCase,
private val upsertEnvelopeRecentSearchUseCase: UpsertEnvelopeRecentSearchUseCase,
) : BaseViewModel<EnvelopeSearchState, EnvelopeSearchEffect>(EnvelopeSearchState()) {

fun getEnvelopeRecentSearchList() {
viewModelScope.launch {
getEnvelopeRecentSearchUserCase().onSuccess(::updateRecentSearchList)
}
}

fun deleteEnvelopeRecentSearch(search: String) {
viewModelScope.launch {
deleteEnvelopeRecentSearchUseCase(search).onSuccess(::updateRecentSearchList)
}
}

fun upsertEnvelopeRecentSearch(search: String) {
viewModelScope.launch {
upsertEnvelopeRecentSearchUseCase(search).onSuccess(::updateRecentSearchList)
}
}

fun updateSearchKeyword(search: String) = intent {
copy(
searchKeyword = search,
envelopeList = if (search.isBlank()) persistentListOf() else envelopeList,
)
}

fun getEnvelopeList(search: String) = viewModelScope.launch {
// TODO: 친구 검색 -> 결과가 있으면 봉투 검색
// TODO: 카테고리 검색 -> 결과가 있으면 봉투 검색
}

private fun updateRecentSearchList(searchList: List<String>) {
intent {
copy(recentSearchKeywordList = searchList.toPersistentList())
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ fun SentRoute(
padding: PaddingValues,
navigateSentEnvelope: () -> Unit,
navigateSentEnvelopeAdd: () -> Unit,
navigateSentEnvelopeSearch: () -> Unit,
) {
SentScreen(
padding = padding,
onClickHistoryShowAll = navigateSentEnvelope,
onClickAddEnvelope = navigateSentEnvelopeAdd,
onClickSearchIcon = navigateSentEnvelopeSearch,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.susu.feature.envelope.SentEnvelopeRoute
import com.susu.feature.envelopeadd.SentEnvelopeAddRoute
import com.susu.feature.envelopedetail.SentEnvelopeDetailRoute
import com.susu.feature.envelopeedit.SentEnvelopeEditRoute
import com.susu.feature.envelopesearch.SentEnvelopeSearchRoute
import com.susu.feature.sent.SentRoute

fun NavController.navigateSent(navOptions: NavOptions) {
Expand All @@ -31,20 +32,26 @@ fun NavController.navigateSentEnvelopeAdd() {
navigate(SentRoute.sentEnvelopeAddRoute)
}

fun NavController.navigateSentEnvelopeSearch() {
navigate(SentRoute.sentEnvelopeSearchRoute)
}

fun NavGraphBuilder.sentNavGraph(
padding: PaddingValues,
popBackStack: () -> Unit,
navigateSentEnvelope: () -> Unit,
navigateSentEnvelopeDetail: () -> Unit,
navigateSentEnvelopeEdit: () -> Unit,
navigateSentEnvelopeAdd: () -> Unit,
navigateSentEnvelopeSearch: () -> Unit,
handleException: (Throwable, () -> Unit) -> Unit,
) {
composable(route = SentRoute.route) {
SentRoute(
padding = padding,
navigateSentEnvelope = navigateSentEnvelope,
navigateSentEnvelopeAdd = navigateSentEnvelopeAdd,
navigateSentEnvelopeSearch = navigateSentEnvelopeSearch,
)
}

Expand Down Expand Up @@ -75,6 +82,12 @@ fun NavGraphBuilder.sentNavGraph(
handleException = handleException,
)
}

composable(route = SentRoute.sentEnvelopeSearchRoute) {
SentEnvelopeSearchRoute(
popBackStack = popBackStack,
)
}
}

object SentRoute {
Expand All @@ -83,4 +96,5 @@ object SentRoute {
const val sentEnvelopeDetailRoute = "sent-envelope-detail"
const val sentEnvelopeEditRoute = "sent-envelope-edit"
const val sentEnvelopeAddRoute = "sent-envelope-add"
const val sentEnvelopeSearchRoute = "sent-envelope-search"
}

0 comments on commit 9294c06

Please sign in to comment.