diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/SearchFilterScreen.kt b/feature/search/src/main/java/com/chan/search/ui/composables/SearchFilterScreen.kt index 0e4e192..3e3188a 100644 --- a/feature/search/src/main/java/com/chan/search/ui/composables/SearchFilterScreen.kt +++ b/feature/search/src/main/java/com/chan/search/ui/composables/SearchFilterScreen.kt @@ -20,38 +20,33 @@ import androidx.compose.ui.unit.dp import com.chan.android.ui.theme.Spacing import com.chan.android.ui.theme.White import com.chan.search.R +import com.chan.search.ui.contract.SearchContract import com.chan.search.ui.model.filter.DeliveryOption import com.chan.search.ui.model.filter.FilterCategoriesModel @OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchFilterScreen( - selectedDeliveryOption: DeliveryOption?, - categoryFilters: List, - expandedCategoryName: String?, - selectedSubCategories: Set, - isCategorySectionExpanded: Boolean, - filteredProductCount: Int, - onClose: () -> Unit, - onDeliveryOptionClick: (DeliveryOption) -> Unit, - onCategoryHeaderClick: (String) -> Unit, - onSubCategoryClick: (String) -> Unit, - onFilterCategoryClick: () -> Unit, - onFilterClear: () -> Unit, + state: SearchContract.State, + onEvent: (SearchContract.Event) -> Unit, modifier: Modifier = Modifier ) { Scaffold( modifier = modifier, containerColor = White, - topBar = { FilterHeader( - onClose = onClose, - onFilterClear = onFilterClear - ) }, - bottomBar = { FilterBottomButton( - itemCount = filteredProductCount, - onClick = onClose - ) } + topBar = { + FilterHeader( + onClose = { onEvent(SearchContract.Event.Filter.OnFilterClick) }, + onFilterClear = { onEvent(SearchContract.Event.Filter.OnClear) } + ) + }, + bottomBar = { + FilterBottomButton( + itemCount = state.filter.filteredProductCount, + onApplyFilters = { onEvent(SearchContract.Event.Filter.OnFilterClick) } + ) + } ) { paddingValues -> LazyColumn( modifier = Modifier @@ -60,8 +55,8 @@ fun SearchFilterScreen( // "오늘드림", "픽업" 체크박스 섹션 item { FilterToggleSection( - selectedOption = selectedDeliveryOption, - onOptionClick = onDeliveryOptionClick + selectedOption = state.filter.selectedDeliveryOption, + onOptionClick = { onEvent(SearchContract.Event.Filter.OnDeliveryOptionChanged(it)) } ) HorizontalDivider( color = Color.LightGray.copy(alpha = 0.2f), @@ -75,21 +70,27 @@ fun SearchFilterScreen( //카테고리 ExpandableFilterSection( title = stringResource(R.string.category_label), - isExpanded = isCategorySectionExpanded, - onClick = onFilterCategoryClick + isExpanded = state.filter.isCategorySectionExpanded, + onClick = { onEvent(SearchContract.Event.Filter.OnCategoryClick) } ) HorizontalDivider( color = Color.LightGray.copy(alpha = 0.5f), thickness = 1.dp ) //서브 카테고리 - if (isCategorySectionExpanded) { + if (state.filter.isCategorySectionExpanded) { SubFilterCategory( - categoryFilters = categoryFilters, - expandedCategoryName = expandedCategoryName, - selectedSubCategories = selectedSubCategories, - onCategoryHeaderClick = onCategoryHeaderClick, - onSubCategoryClick = onSubCategoryClick + categoryFilters = state.filter.categoryFilters, + expandedCategoryName = state.filter.expandedCategoryName, + selectedSubCategories = state.filter.selectedSubCategories, + onCategoryHeaderClick = { + onEvent( + SearchContract.Event.Filter.OnCategoryHeaderClick( + it + ) + ) + }, + onSubCategoryClick = { onEvent(SearchContract.Event.Filter.OnSubCategoryClick(it)) } ) } } @@ -103,7 +104,10 @@ fun SearchFilterScreen( } item { - ExpandableFilterSection(title = stringResource(R.string.product_view_mode_label), details = "2단") + ExpandableFilterSection( + title = stringResource(R.string.product_view_mode_label), + details = "2단" + ) HorizontalDivider( color = Color.LightGray.copy(alpha = 0.5f), thickness = 1.dp @@ -118,8 +122,8 @@ private fun SubFilterCategory( categoryFilters: List, expandedCategoryName: String?, selectedSubCategories: Set, - onCategoryHeaderClick: (String) -> Unit, - onSubCategoryClick: (String) -> Unit, + onCategoryHeaderClick: (categoryName: String) -> Unit, + onSubCategoryClick: (subCategoryName: String) -> Unit, ) { Column { categoryFilters.forEach { category -> @@ -258,10 +262,10 @@ fun ExpandableFilterSection( @Composable fun FilterBottomButton( itemCount: Int, - onClick: () -> Unit + onApplyFilters: () -> Unit ) { Button( - onClick = onClick, + onClick = onApplyFilters, modifier = Modifier .fillMaxWidth() .padding(Spacing.spacing4), @@ -278,4 +282,8 @@ fun FilterBottomButton( fontWeight = FontWeight.Bold ) } -} \ No newline at end of file +} + + + + diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreen.kt b/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreen.kt index 140748e..c91377e 100644 --- a/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreen.kt +++ b/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController -import com.chan.search.ui.contract.SearchContract import com.chan.search.ui.viewmodel.SearchViewModel @Composable @@ -17,39 +16,7 @@ fun SearchScreen( SearchScreenContent( navController = navController, - search = state.search, - recentSearches = state.recentSearches, - recommendedKeywords = state.recommendedKeywords, - trendingSearches = state.trendingSearches, - searchResults = state.searchResults, - currentTime = state.currentTime, - showSearchResult = state.showSearchResult, - searchResultProducts = state.searchResultProducts, - showFilter = state.showFilter, - selectedDeliveryOption = state.selectedDeliveryOption, - categoryFilters = state.categoryFilters, - expandedCategoryName = state.expandedCategoryName, - selectedSubCategories = state.selectedSubCategories, - isCategorySectionExpanded = state.isCategorySectionExpanded, - filteredProductCount = state.filteredProductCount, - onSearchChanged = { viewModel.setEvent(SearchContract.Event.OnSearchChanged(it)) }, - onClearSearch = { viewModel.setEvent(SearchContract.Event.OnClickClearSearch) }, - onSearchClick = { viewModel.setEvent(SearchContract.Event.OnAddSearchKeyword(it)) }, - onSearchTextFocus = { viewModel.setEvent(SearchContract.Event.OnSearchTextFocus) }, - onRemoveSearchKeyword = { viewModel.setEvent(SearchContract.Event.OnRemoveSearchKeyword(it)) }, - onClearAllRecentSearches = { viewModel.setEvent(SearchContract.Event.OnClearAllRecentSearches) }, - onSearchResultItemClick = { viewModel.setEvent(SearchContract.Event.OnClickSearchProduct(it)) }, - onFilterClear = { viewModel.setEvent(SearchContract.Event.OnFilterClear) }, - onUpdateFilterClick = { viewModel.setEvent(SearchContract.Event.OnUpdateFilterClick) }, - onDeliveryOptionClick = { viewModel.setEvent(SearchContract.Event.OnDeliveryOptionChanged(it)) }, - onCategoryHeaderClick = { viewModel.setEvent(SearchContract.Event.OnCategoryHeaderClick(it)) }, - onSubCategoryClick = { viewModel.setEvent(SearchContract.Event.OnSubCategoryClick(it)) }, - onFilterCategoryClick = { viewModel.setEvent(SearchContract.Event.OnFilterCategoryClick) }, - onClickBack = { - navController.popBackStack() - }, - onClickCart = { - // TODO : Cart 로 이동 - } + state = state, + onEvent = viewModel::setEvent ) } \ No newline at end of file diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreenContent.kt b/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreenContent.kt index 75d1839..31aa811 100644 --- a/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreenContent.kt +++ b/feature/search/src/main/java/com/chan/search/ui/composables/SearchScreenContent.kt @@ -32,7 +32,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.navigation.NavHostController -import com.chan.android.model.ProductModel import com.chan.android.ui.theme.Black import com.chan.android.ui.theme.Spacing import com.chan.android.ui.theme.White @@ -41,53 +40,21 @@ import com.chan.android.ui.theme.dividerColor import com.chan.navigation.Routes import com.chan.search.R import com.chan.search.ui.composables.result.SearchResultScreen -import com.chan.search.ui.model.SearchHistoryModel -import com.chan.search.ui.model.SearchResultModel -import com.chan.search.ui.model.TrendingSearchModel -import com.chan.search.ui.model.filter.DeliveryOption -import com.chan.search.ui.model.filter.FilterCategoriesModel +import com.chan.search.ui.contract.SearchContract @Composable fun SearchScreenContent( navController: NavHostController, - search: String, - recentSearches: List, - recommendedKeywords: List, - trendingSearches: List, - searchResults: List, - currentTime: String, - showSearchResult: Boolean, - searchResultProducts: List, - showFilter: Boolean, - selectedDeliveryOption: DeliveryOption?, - categoryFilters: List, - expandedCategoryName: String?, - selectedSubCategories: Set, - isCategorySectionExpanded: Boolean, - filteredProductCount: Int, - onSearchChanged: (String) -> Unit, - onClearSearch: () -> Unit, - onSearchClick: (String) -> Unit, - onSearchTextFocus: () -> Unit, - onRemoveSearchKeyword: (String) -> Unit, - onClearAllRecentSearches: () -> Unit, - onSearchResultItemClick: (String) -> Unit, - onClickBack: () -> Unit, - onClickCart: () -> Unit, - onFilterClear: () -> Unit, - onUpdateFilterClick: () -> Unit, - onDeliveryOptionClick: (DeliveryOption) -> Unit, - onCategoryHeaderClick: (String) -> Unit, - onSubCategoryClick: (String) -> Unit, - onFilterCategoryClick: () -> Unit, + state: SearchContract.State, + onEvent: (SearchContract.Event) -> Unit ) { Box(modifier = Modifier.fillMaxSize()) { Scaffold( containerColor = White, topBar = { SearchTopAppBar( - onClickBack = onClickBack, - onClickCart = onClickCart + onClickBack = { navController.popBackStack() }, + onClickCart = { } ) }, ) { paddingValues -> @@ -98,65 +65,75 @@ fun SearchScreenContent( ) { SearchTextField( - search = search, - onSearchChanged = onSearchChanged, - onClearSearch = onClearSearch, - onSearchClick = { - onSearchClick(it) - }, - onSearchTextFocus = { - onSearchTextFocus() - }, + search = state.search, + onSearchChanged = { onEvent(SearchContract.Event.OnSearchChanged(it)) }, + onClearSearch = { onEvent(SearchContract.Event.OnClickClearSearch) }, + onSearchClick = { onEvent(SearchContract.Event.OnAddSearchKeyword(it)) }, + onSearchTextFocus = { onEvent(SearchContract.Event.OnSearchTextFocus) }, modifier = Modifier.padding(Spacing.spacing4) ) HorizontalDivider(color = dividerColor, thickness = 1.dp) - if (!showSearchResult) { - if (search.isBlank()) { - if (recentSearches.isNotEmpty()) { + when { + state.showSearchResult -> { + if (state.searchResultProducts.isEmpty()) { + Text(text = stringResource(R.string.search_empty_product)) + } else { + Box(modifier = Modifier.weight(1f)) { + SearchResultScreen( + state = state, + onEvent = onEvent, + onProductClick = { productId -> + navController.navigate( + Routes.PRODUCT_DETAIL.productDetailRoute(productId) + ) + } + ) + } + } + } + + state.search.isBlank() -> { + if (state.recentSearches.isNotEmpty()) { RecentSearchList( - recentSearches = recentSearches, - onRemoveSearch = onRemoveSearchKeyword, - onClearAllRecentSearches = onClearAllRecentSearches, - onSearchClick = { - onSearchClick(it) + recentSearches = state.recentSearches, + onRemoveSearch = { + onEvent( + SearchContract.Event.OnRemoveSearchKeyword( + it + ) + ) }, + onClearAllRecentSearches = { onEvent(SearchContract.Event.OnClearAllRecentSearches) }, + onSearchClick = { onEvent(SearchContract.Event.OnAddSearchKeyword(it)) }, ) } - RecommendedKeywordList(recommendedKeywords = recommendedKeywords) + RecommendedKeywordList(recommendedKeywords = state.recommendedKeywords) TrendingSearchList( - trendingSearches = trendingSearches, - currentTime = currentTime - ) - } else { - SearchResultList( - results = searchResults, - searchQuery = search, - onSearchResultItemClick = onSearchResultItemClick + trendingSearches = state.trendingSearches, + currentTime = state.currentTime ) } - } else { - if (searchResultProducts.isEmpty()) { - Text(text = stringResource(R.string.search_empty_product)) - } else { - Box(modifier = Modifier.weight(1f)) { - SearchResultScreen( - products = searchResultProducts, - onNavigateToFilter = onUpdateFilterClick, - onProductClick = { productId -> - navController.navigate( - Routes.PRODUCT_DETAIL.productDetailRoute(productId) + + else -> { + SearchResultList( + results = state.searchResults, + searchQuery = state.search, + onSearchResultItemClick = { + onEvent( + SearchContract.Event.OnClickSearchProduct( + it ) - } - ) - } + ) + } + ) } } } } AnimatedVisibility( - visible = showFilter, + visible = state.filter.showFilter, enter = fadeIn(animationSpec = tween(300)), exit = fadeOut(animationSpec = tween(300)) ) { @@ -167,13 +144,13 @@ fun SearchScreenContent( .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null, - onClick = onUpdateFilterClick + onClick = { onEvent(SearchContract.Event.Filter.OnFilterClick) } ) ) } AnimatedVisibility( - visible = showFilter, + visible = state.filter.showFilter, enter = slideInHorizontally( initialOffsetX = { it }, animationSpec = tween(300) @@ -188,18 +165,8 @@ fun SearchScreenContent( contentAlignment = Alignment.CenterEnd ) { SearchFilterScreen( - selectedDeliveryOption = selectedDeliveryOption, - categoryFilters = categoryFilters, - expandedCategoryName = expandedCategoryName, - selectedSubCategories = selectedSubCategories, - isCategorySectionExpanded = isCategorySectionExpanded, - filteredProductCount = filteredProductCount, - onClose = onUpdateFilterClick, - onDeliveryOptionClick = onDeliveryOptionClick, - onCategoryHeaderClick = onCategoryHeaderClick, - onSubCategoryClick = onSubCategoryClick, - onFilterCategoryClick = onFilterCategoryClick, - onFilterClear = onFilterClear, + state = state, + onEvent = onEvent, modifier = Modifier .fillMaxWidth(0.8f) .fillMaxHeight() diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreen.kt b/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreen.kt index 59a10a9..e373abe 100644 --- a/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreen.kt +++ b/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreen.kt @@ -1,32 +1,18 @@ package com.chan.search.ui.composables.result import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel -import com.chan.android.model.ProductModel import com.chan.search.ui.contract.SearchContract -import com.chan.search.ui.viewmodel.SearchViewModel @Composable fun SearchResultScreen( - viewModel: SearchViewModel = hiltViewModel(), - products: List, - onNavigateToFilter: () -> Unit, + state: SearchContract.State, + onEvent: (SearchContract.Event) -> Unit, onProductClick: (String) -> Unit ) { - val state by viewModel.viewState.collectAsState() - SearchResultScreenContent( - products = products, - filters = state.filterChips, - onToggleFilter = { - viewModel.setEvent(SearchContract.Event.OnFilterChipClicked(it)) - }, - onNavigateToFilter = { - onNavigateToFilter() - }, + state = state, + onEvent = onEvent, onProductClick = { productId -> onProductClick(productId) } diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreenContent.kt b/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreenContent.kt index b808171..8e8e8b8 100644 --- a/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreenContent.kt +++ b/feature/search/src/main/java/com/chan/search/ui/composables/result/SearchResultScreenContent.kt @@ -1,75 +1,40 @@ package com.chan.search.ui.composables.result import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -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.graphics.Color -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import coil.compose.AsyncImage -import com.chan.android.ProductCard import com.chan.android.model.ProductModel -import com.chan.android.ui.theme.LightGray -import com.chan.android.ui.theme.Radius import com.chan.android.ui.theme.Spacing import com.chan.android.ui.theme.White -import com.chan.android.ui.theme.appTypography import com.chan.android.ui.theme.dividerColor -import com.chan.search.ui.model.FilterChipType -import com.chan.search.ui.model.SearchResultFilterChipModel +import com.chan.search.ui.composables.result.composables.FilterChipRow +import com.chan.search.ui.composables.result.composables.TabRow +import com.chan.search.ui.composables.result.composables.productGrid +import com.chan.search.ui.contract.SearchContract @OptIn(ExperimentalFoundationApi::class) @Composable fun SearchResultScreenContent( - products: List, - filters: List, - onToggleFilter: (SearchResultFilterChipModel) -> Unit, - onNavigateToFilter: () -> Unit, + state: SearchContract.State, + onEvent: (SearchContract.Event) -> Unit, onProductClick: (String) -> Unit ) { - var selectedTabIndex by remember { mutableStateOf(0) } - val tabs = listOf("상품", "후기", "콘텐츠") val lazyListState = rememberLazyListState() - LaunchedEffect(products) { - if (products.isNotEmpty()) { + LaunchedEffect(state.searchResultProducts) { + if (state.searchResultProducts.isNotEmpty()) { lazyListState.scrollToItem(0) } } @@ -79,10 +44,12 @@ fun SearchResultScreenContent( state = lazyListState ) { item { - SearchResultTabRow( - tabs = tabs, - selectedTabIndex = selectedTabIndex, - onTabSelected = { selectedTabIndex = it } + TabRow( + tabs = state.resultTabRow.tabs, + selectedTabIndex = state.resultTabRow.resultSelectedTabIndex, + onTabSelected = { selectedTabIndex -> + onEvent(SearchContract.Event.TabRow.OnResultTabSelected(selectedTabIndex)) + } ) } @@ -92,216 +59,55 @@ fun SearchResultScreenContent( color = White ) { Column { - FilterChipsRow( - filters = filters, - onToggleFilter = onToggleFilter, - onNavigateToFilter = onNavigateToFilter, + FilterChipRow( + filters = state.filter.filterChips, + onToggleFilter = { + onEvent( + SearchContract.Event.Filter.OnFilterChipClicked( + it + ) + ) + }, + onFilterClick = { onEvent(SearchContract.Event.Filter.OnFilterClick) }, ) HorizontalDivider(color = dividerColor, thickness = 1.dp) } } } - when (selectedTabIndex) { + when (state.resultTabRow.resultSelectedTabIndex) { 0 -> { // 상품 - item { - SearchResultListHeader(products) - } - productGrid( - products = products, + productTabContent( + products = state.searchResultProducts, onProductClick = onProductClick ) } - 1 -> { // 후기 - item { Text("후기 탭 내용") } - } - - 2 -> { // 콘텐츠 - item { Text("콘텐츠 탭 내용") } - } + 1 -> reviewTabContent() + 2 -> contentTabContent() } } } -private fun LazyListScope.productGrid( +private fun LazyListScope.productTabContent( products: List, onProductClick: (String) -> Unit ) { - val chunkedProducts = products.chunked(2) - items( - items = chunkedProducts, - key = { row -> row.joinToString { it.productId } } - ) { productRow -> - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(Spacing.spacing2) - ) { - productRow.forEach { product -> - Box(modifier = Modifier.weight(1f)) { - ProductCard( - product = product, - onClick = { onProductClick(product.productId) }, - onLikeClick = {}, - onCartClick = {} - ) - } - } - if (productRow.size == 1) { - Spacer(modifier = Modifier.weight(1f)) - } - } + item { + SearchResultListHeader(products) } + productGrid( + products = products, + onProductClick = onProductClick + ) } -@Composable -fun CustomTab( - title: String, - isSelected: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier -) { - var textWidth by remember { mutableStateOf(0.dp) } - val density = LocalDensity.current - - Box( - modifier = modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = onClick - ) - .padding(horizontal = Spacing.spacing4) - ) { - Text( - text = title, - color = if (isSelected) Color.Black else Color.Gray, - style = MaterialTheme.appTypography.tab.copy( - fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal - ), - onTextLayout = { textLayoutResult -> - textWidth = with(density) { textLayoutResult.size.width.toDp() } - }, - modifier = Modifier - .padding(vertical = Spacing.spacing4) - .align(Alignment.Center) - ) - - Box( - modifier = Modifier - .width(textWidth) - .height(2.dp) - .background(if (isSelected) Color.Black else Color.Transparent) - .align(Alignment.BottomCenter) - ) - } +private fun LazyListScope.reviewTabContent() { + item { Text("후기 탭 내용") } } -@Composable -private fun SearchResultTabRow( - tabs: List, - selectedTabIndex: Int, - onTabSelected: (Int) -> Unit -) { - Column(modifier = Modifier.background(Color.White)) { - Row( - modifier = Modifier.fillMaxWidth() - ) { - tabs.forEachIndexed { index, title -> - CustomTab( - title = title, - isSelected = index == selectedTabIndex, - onClick = { onTabSelected(index) }, - modifier = Modifier.weight(1f) - ) - } - } - HorizontalDivider(color = dividerColor, thickness = 1.dp) - } -} - -@Composable -private fun FilterChipsRow( - filters: List, - onToggleFilter: (SearchResultFilterChipModel) -> Unit, - onNavigateToFilter: () -> Unit, - modifier: Modifier = Modifier, -) { - Box(modifier = modifier.fillMaxWidth()) { - Row( - modifier = Modifier - .horizontalScroll(rememberScrollState()) - .padding(horizontal = Spacing.spacing4, vertical = Spacing.spacing2), - horizontalArrangement = Arrangement.spacedBy(Spacing.spacing2), - verticalAlignment = Alignment.CenterVertically - ) { - filters.forEach { filter -> - FilterChip( - filter = filter, - onToggleFilter = onToggleFilter, - onNavigateToFilter = onNavigateToFilter, - ) - } - } - } -} - -@Composable -private fun FilterChip( - filter: SearchResultFilterChipModel, - onToggleFilter: (SearchResultFilterChipModel) -> Unit, - onNavigateToFilter: () -> Unit, -) { - val interactionSource = remember { MutableInteractionSource() } - val containerColor = if (filter.isSelected) Color.Black else Color.White - val textColor = if (filter.isSelected) Color.White else Color.DarkGray - - Box( - modifier = Modifier - .background(color = containerColor, shape = RoundedCornerShape(Radius.radius6)) - .clip(RoundedCornerShape(Radius.radius6)) - .border(width = 1.dp, color = LightGray, shape = RoundedCornerShape(Radius.radius6)) - .clickable( - interactionSource = interactionSource, - indication = null, - onClick = { - when (filter.chipType) { - FilterChipType.TOGGLE -> onToggleFilter(filter) - FilterChipType.DROP_DOWN -> onNavigateToFilter() - } - } - ) - .padding(horizontal = Spacing.spacing3, vertical = Spacing.spacing2) - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - when (filter.chipType) { - FilterChipType.TOGGLE -> { - AsyncImage( - model = filter.image, - contentDescription = filter.text, - modifier = Modifier - .size(Spacing.spacing5) - .padding(end = Spacing.spacing1) - ) - Text(text = filter.text, color = textColor) - } - - FilterChipType.DROP_DOWN -> { - Text( - text = filter.text, - color = textColor, - modifier = Modifier.padding(end = Spacing.spacing1) - ) - Icon( - imageVector = Icons.Default.ArrowDropDown, - contentDescription = "Dropdown", - tint = textColor, - modifier = Modifier.size(Spacing.spacing4) - ) - } - } - } - } +private fun LazyListScope.contentTabContent() { + item { Text("콘텐츠 탭 내용") } } @Composable diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/FilterChipRow.kt b/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/FilterChipRow.kt new file mode 100644 index 0000000..a06cf07 --- /dev/null +++ b/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/FilterChipRow.kt @@ -0,0 +1,129 @@ +package com.chan.search.ui.composables.result.composables + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll +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.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import com.chan.android.ui.theme.Black +import com.chan.android.ui.theme.DarkGray +import com.chan.android.ui.theme.LightGray +import com.chan.android.ui.theme.Radius +import com.chan.android.ui.theme.Spacing +import com.chan.android.ui.theme.White +import com.chan.search.ui.model.FilterChipType +import com.chan.search.ui.model.SearchResultFilterChipModel +import kotlin.collections.forEach + +@Composable +fun FilterChipRow( + filters: List, + onToggleFilter: (SearchResultFilterChipModel) -> Unit, + onFilterClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Box(modifier = modifier.fillMaxWidth()) { + Row( + modifier = Modifier + .horizontalScroll(rememberScrollState()) + .padding(horizontal = Spacing.spacing4, vertical = Spacing.spacing2), + horizontalArrangement = Arrangement.spacedBy(Spacing.spacing2), + verticalAlignment = Alignment.CenterVertically + ) { + filters.forEach { filter -> + FilterChip( + filter = filter, + onToggleFilter = onToggleFilter, + onFilterClick = onFilterClick, + ) + } + } + } +} + +@Composable +private fun FilterChip( + filter: SearchResultFilterChipModel, + onToggleFilter: (SearchResultFilterChipModel) -> Unit, + onFilterClick: () -> Unit, +) { + val interactionSource = remember { MutableInteractionSource() } + val chipCornerShape = RoundedCornerShape(Radius.radius6) + val (containerColor, textColor) = if (filter.isSelected) { + Black to Color.White + } else { + White to DarkGray + } + + Box( + modifier = Modifier + .background(color = containerColor, shape = chipCornerShape) + .clip(chipCornerShape) + .border(width = 1.dp, color = LightGray, shape = chipCornerShape) + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = { + when (filter.chipType) { + FilterChipType.TOGGLE -> onToggleFilter(filter) + FilterChipType.DROP_DOWN -> onFilterClick() + } + } + ) + .padding(horizontal = Spacing.spacing3, vertical = Spacing.spacing2) + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + when (filter.chipType) { + FilterChipType.TOGGLE -> ToggleChipContent(filter, textColor) + FilterChipType.DROP_DOWN -> DropDownChipContent(filter, textColor) + } + } + } +} + +@Composable +private fun ToggleChipContent(filter: SearchResultFilterChipModel, textColor: Color) { + AsyncImage( + model = filter.image, + contentDescription = filter.text, + modifier = Modifier + .size(Spacing.spacing5) + .padding(end = Spacing.spacing1) + ) + Text(text = filter.text, color = textColor) +} + +@Composable +private fun DropDownChipContent(filter: SearchResultFilterChipModel, textColor: Color) { + Text( + text = filter.text, + color = textColor, + modifier = Modifier.padding(end = Spacing.spacing1) + ) + Icon( + imageVector = Icons.Default.ArrowDropDown, + contentDescription = "Dropdown", + tint = textColor, + modifier = Modifier.size(Spacing.spacing4) + ) +} \ No newline at end of file diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/ProductGridView.kt b/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/ProductGridView.kt new file mode 100644 index 0000000..5bb91bc --- /dev/null +++ b/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/ProductGridView.kt @@ -0,0 +1,43 @@ +package com.chan.search.ui.composables.result.composables + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.ui.Modifier +import com.chan.android.ProductCard +import com.chan.android.model.ProductModel +import com.chan.android.ui.theme.Spacing + +fun LazyListScope.productGrid( + products: List, + onProductClick: (String) -> Unit +) { + val chunkedProducts = products.chunked(2) + items( + items = chunkedProducts, + key = { row -> row.joinToString { it.productId } } + ) { productRow -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(Spacing.spacing2) + ) { + productRow.forEach { product -> + Box(modifier = Modifier.weight(1f)) { + ProductCard( + product = product, + onClick = { onProductClick(product.productId) }, + onLikeClick = {}, + onCartClick = {} + ) + } + } + if (productRow.size == 1) { + Spacer(modifier = Modifier.weight(1f)) + } + } + } +} \ No newline at end of file diff --git a/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/TabRow.kt b/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/TabRow.kt new file mode 100644 index 0000000..3f3a670 --- /dev/null +++ b/feature/search/src/main/java/com/chan/search/ui/composables/result/composables/TabRow.kt @@ -0,0 +1,97 @@ +package com.chan.search.ui.composables.result.composables + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +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.graphics.Color +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.chan.android.ui.theme.Spacing +import com.chan.android.ui.theme.appTypography +import com.chan.android.ui.theme.dividerColor +import com.chan.search.ui.model.result.SearchResultTab + +@Composable +fun TabRow( + tabs: List, + selectedTabIndex: Int, + onTabSelected: (Int) -> Unit +) { + Column(modifier = Modifier.background(Color.White)) { + Row( + modifier = Modifier.fillMaxWidth() + ) { + tabs.forEachIndexed { index, tab -> + CustomTab( + title = tab.title, + isSelected = index == selectedTabIndex, + onClick = { onTabSelected(index) }, + modifier = Modifier.weight(1f) + ) + } + } + HorizontalDivider(color = dividerColor, thickness = 1.dp) + } +} + + +@Composable +private fun CustomTab( + title: String, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + var textWidth by remember { mutableStateOf(0.dp) } + val density = LocalDensity.current + + Box( + modifier = modifier + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = onClick + ) + .padding(horizontal = Spacing.spacing4) + ) { + Text( + text = title, + color = if (isSelected) Color.Black else Color.Gray, + style = MaterialTheme.appTypography.tab.copy( + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal + ), + onTextLayout = { textLayoutResult -> + textWidth = with(density) { textLayoutResult.size.width.toDp() } + }, + modifier = Modifier + .padding(vertical = Spacing.spacing4) + .align(Alignment.Center) + ) + + Box( + modifier = Modifier + .width(textWidth) + .height(2.dp) + .background(if (isSelected) Color.Black else Color.Transparent) + .align(Alignment.BottomCenter) + ) + } +} \ No newline at end of file diff --git a/feature/search/src/main/java/com/chan/search/ui/contract/SearchContract.kt b/feature/search/src/main/java/com/chan/search/ui/contract/SearchContract.kt index eb0b432..e9b97b7 100644 --- a/feature/search/src/main/java/com/chan/search/ui/contract/SearchContract.kt +++ b/feature/search/src/main/java/com/chan/search/ui/contract/SearchContract.kt @@ -11,6 +11,7 @@ import com.chan.search.ui.model.SearchResultModel import com.chan.search.ui.model.TrendingSearchModel import com.chan.search.ui.model.filter.DeliveryOption import com.chan.search.ui.model.filter.FilterCategoriesModel +import com.chan.search.ui.model.result.SearchResultTab class SearchContract { @@ -24,14 +25,19 @@ class SearchContract { data class OnRemoveSearchKeyword(val search: String) : Event() object OnClearAllRecentSearches : Event() - object OnUpdateFilterClick : Event() - object OnFilterClear : Event() + sealed class Filter : Event() { + data class OnDeliveryOptionChanged(val option: DeliveryOption) : Filter() + data class OnCategoryHeaderClick(val categoryName: String) : Filter() + data class OnSubCategoryClick(val subCategoryName: String) : Filter() + data class OnFilterChipClicked(val chip: SearchResultFilterChipModel) : Filter() + object OnCategoryClick : Filter() + object OnFilterClick : Filter() + object OnClear : Filter() + } - data class OnFilterChipClicked(val chip: SearchResultFilterChipModel) : Event() - data class OnDeliveryOptionChanged(val option: DeliveryOption) : Event() - data class OnCategoryHeaderClick(val categoryName: String) : Event() - data class OnSubCategoryClick(val subCategoryName: String) : Event() - object OnFilterCategoryClick : Event() + sealed class TabRow: Event() { + data class OnResultTabSelected(val index: Int): Event() + } } data class State( @@ -47,17 +53,28 @@ class SearchContract { val searchResultProducts: List = emptyList(), val currentTime: String = "", val showSearchResult: Boolean = false, - val showFilter: Boolean = false, + val filter: FilterState = FilterState(), + val resultTabRow: TabRow = TabRow() + + + ) : ViewState + + data class TabRow( + val resultSelectedTabIndex : Int = 0, + val tabs: List = SearchResultTab.allTabs + ) + + data class FilterState ( + val showFilter: Boolean = false, val selectedDeliveryOption: DeliveryOption? = null, val filterChips: List = emptyList(), - val categoryFilters: List = emptyList(), val expandedCategoryName: String? = null, val selectedSubCategories: Set = emptySet(), val isCategorySectionExpanded: Boolean = false, val filteredProductCount: Int = 0 - ) : ViewState + ) sealed class Effect : ViewEffect { data class ShowError(val message: String) : Effect() diff --git a/feature/search/src/main/java/com/chan/search/ui/mappers/CategoryFilterUiMapper.kt b/feature/search/src/main/java/com/chan/search/ui/mappers/CategoryFilterUiMapper.kt index 9773f26..05b32ef 100644 --- a/feature/search/src/main/java/com/chan/search/ui/mappers/CategoryFilterUiMapper.kt +++ b/feature/search/src/main/java/com/chan/search/ui/mappers/CategoryFilterUiMapper.kt @@ -16,3 +16,4 @@ fun FilterCategoriesVO.toUiModel(): FilterCategoriesModel = } ) + diff --git a/feature/search/src/main/java/com/chan/search/ui/model/result/SearchResultTab.kt b/feature/search/src/main/java/com/chan/search/ui/model/result/SearchResultTab.kt new file mode 100644 index 0000000..a820cfd --- /dev/null +++ b/feature/search/src/main/java/com/chan/search/ui/model/result/SearchResultTab.kt @@ -0,0 +1,11 @@ +package com.chan.search.ui.model.result + +sealed class SearchResultTab(val title: String, val index: Int) { + object Product: SearchResultTab("상품", 0) + object Review: SearchResultTab("후기", 1) + object Content: SearchResultTab("콘텐츠", 2) + + companion object { + val allTabs: List = listOf(Product, Review, Content) + } +} \ No newline at end of file diff --git a/feature/search/src/main/java/com/chan/search/ui/viewmodel/SearchViewModel.kt b/feature/search/src/main/java/com/chan/search/ui/viewmodel/SearchViewModel.kt index b09607e..61424aa 100644 --- a/feature/search/src/main/java/com/chan/search/ui/viewmodel/SearchViewModel.kt +++ b/feature/search/src/main/java/com/chan/search/ui/viewmodel/SearchViewModel.kt @@ -104,25 +104,47 @@ class SearchViewModel @Inject constructor( SearchContract.Event.OnClearAllRecentSearches -> clearAllSearchKeyword() SearchContract.Event.OnSearchTextFocus -> setState { copy(showSearchResult = false) } - SearchContract.Event.OnUpdateFilterClick -> setState { copy(showFilter = !showFilter) } - SearchContract.Event.OnFilterClear -> handleFilterClear() - - is SearchContract.Event.OnFilterChipClicked -> handleFilterChipClick(event.chip) - is SearchContract.Event.OnDeliveryOptionChanged -> handleDeliveryOptionChange(event.option) - is SearchContract.Event.OnCategoryHeaderClick -> handleCategoryHeaderClick(event.categoryName) - is SearchContract.Event.OnSubCategoryClick -> handleSubCategoryClick(event.subCategoryName) - SearchContract.Event.OnFilterCategoryClick -> setState { copy(isCategorySectionExpanded = !isCategorySectionExpanded) } + SearchContract.Event.Filter.OnFilterClick -> setState { + copy( + filter = filter.copy( + showFilter = !filter.showFilter + ) + ) + } + + SearchContract.Event.Filter.OnClear -> handleFilterClear() + + is SearchContract.Event.Filter.OnFilterChipClicked -> handleFilterChipClick(event.chip) + is SearchContract.Event.Filter.OnDeliveryOptionChanged -> handleDeliveryOptionChange( + event.option + ) + + is SearchContract.Event.Filter.OnCategoryHeaderClick -> handleCategoryHeaderClick(event.categoryName) + is SearchContract.Event.Filter.OnSubCategoryClick -> handleSubCategoryClick(event.subCategoryName) + SearchContract.Event.Filter.OnCategoryClick -> setState { + copy( + filter = filter.copy( + isCategorySectionExpanded = !filter.isCategorySectionExpanded + ) + ) + } + + is SearchContract.Event.TabRow.OnResultTabSelected -> { + setState { copy(resultTabRow = resultTabRow.copy(resultSelectedTabIndex = event.index)) } + } } } private fun handleFilterClear() { setState { copy( - selectedDeliveryOption = null, - expandedCategoryName = null, - selectedSubCategories = emptySet(), - isCategorySectionExpanded = false, - filterChips = this.filterChips.map { it.copy(isSelected = false) } + filter = filter.copy( + selectedDeliveryOption = null, + expandedCategoryName = null, + selectedSubCategories = emptySet(), + isCategorySectionExpanded = false, + filterChips = filter.filterChips.map { it.copy(isSelected = false) } + ) ) } // 현재 검색어로 검색 결과를 다시 조회 @@ -130,31 +152,35 @@ class SearchViewModel @Inject constructor( } private fun handleSubCategoryClick(subCategoryName: String) { - val currentSelected = viewState.value.selectedSubCategories + val currentSelected = viewState.value.filter.selectedSubCategories val newSelected = if (currentSelected.contains(subCategoryName)) { currentSelected - subCategoryName } else { currentSelected + subCategoryName } - setState { copy(selectedSubCategories = newSelected) } + setState { copy(filter = filter.copy(selectedSubCategories = newSelected)) } updateFilteredProductList() } private fun updateFilteredProductCount() { viewModelScope.launch { - val count = searchRepository.getFilteredProductCount(viewState.value.selectedSubCategories) - setState { copy(filteredProductCount = count) } + val count = + searchRepository.getFilteredProductCount(viewState.value.filter.selectedSubCategories) + setState { copy(filter = filter.copy(filteredProductCount = count)) } } } private fun updateFilteredProductList() { viewModelScope.launch { - val filteredProducts = searchRepository.getFilteredProducts(viewState.value.selectedSubCategories) - .map { it.toProductsModel() } + val filteredProducts = + searchRepository.getFilteredProducts(viewState.value.filter.selectedSubCategories) + .map { it.toProductsModel() } setState { copy( searchResultProducts = filteredProducts, - filteredProductCount = filteredProducts.size // 개수도 함께 업데이트 + filter = filter.copy( + filteredProductCount = filteredProducts.size // 개수도 함께 업데이트 + ) ) } } @@ -162,16 +188,16 @@ class SearchViewModel @Inject constructor( private fun handleCategoryHeaderClick(categoryName: String) { setState { - if (this.expandedCategoryName == categoryName) { - copy(expandedCategoryName = null) + if (this.filter.expandedCategoryName == categoryName) { + copy(filter = filter.copy(expandedCategoryName = null)) } else { - copy(expandedCategoryName = categoryName) + copy(filter = filter.copy(expandedCategoryName = categoryName)) } } } private fun handleDeliveryOptionChange(option: DeliveryOption) { - val currentSelectedOption = viewState.value.selectedDeliveryOption + val currentSelectedOption = viewState.value.filter.selectedDeliveryOption val newSelectedOption = if (currentSelectedOption == option) { null } else { @@ -180,24 +206,27 @@ class SearchViewModel @Inject constructor( setState { copy( - selectedDeliveryOption = newSelectedOption, - filterChips = this.filterChips.map { chip -> - if (chip.chipType != FilterChipType.TOGGLE) { - chip - } else { - val chipCorrespondsToNewSelection = - (chip.text == "오늘드림" && newSelectedOption == DeliveryOption.TODAY_DELIVERY) || - (chip.text == "픽업" && newSelectedOption == DeliveryOption.PICKUP) - - chip.copy(isSelected = chipCorrespondsToNewSelection) + filter = filter.copy( + selectedDeliveryOption = newSelectedOption, + filterChips = filter.filterChips.map { chip -> + if (chip.chipType != FilterChipType.TOGGLE) { + chip + } else { + val chipCorrespondsToNewSelection = + (chip.text == "오늘드림" && newSelectedOption == DeliveryOption.TODAY_DELIVERY) || + (chip.text == "픽업" && newSelectedOption == DeliveryOption.PICKUP) + + chip.copy(isSelected = chipCorrespondsToNewSelection) + } } - } + ) + ) } } private fun handleFilterChipClick(clickedChip: SearchResultFilterChipModel) { - val isClickedChipAlreadySelected = viewState.value.filterChips + val isClickedChipAlreadySelected = viewState.value.filter.filterChips .find { it.text == clickedChip.text } ?.isSelected == true @@ -205,7 +234,7 @@ class SearchViewModel @Inject constructor( when (clickedChip.text) { "오늘드림" -> DeliveryOption.TODAY_DELIVERY "픽업" -> DeliveryOption.PICKUP - else -> viewState.value.selectedDeliveryOption + else -> viewState.value.filter.selectedDeliveryOption } } else { null @@ -213,18 +242,20 @@ class SearchViewModel @Inject constructor( setState { copy( - selectedDeliveryOption = newSelectedOption, - filterChips = this.filterChips.map { chip -> - if (chip.chipType != FilterChipType.TOGGLE) { - chip - } else { - if (chip.text == clickedChip.text) { - chip.copy(isSelected = !isClickedChipAlreadySelected) + filter = filter.copy( + selectedDeliveryOption = newSelectedOption, + filterChips = this.filter.filterChips.map { chip -> + if (chip.chipType != FilterChipType.TOGGLE) { + chip } else { - chip.copy(isSelected = false) + if (chip.text == clickedChip.text) { + chip.copy(isSelected = !isClickedChipAlreadySelected) + } else { + chip.copy(isSelected = false) + } } } - } + ) ) } } @@ -233,7 +264,13 @@ class SearchViewModel @Inject constructor( viewModelScope.launch { val categories = searchRepository.getFilterCategories() .map { it.toUiModel() } - setState { copy(categoryFilters = categories) } + setState { + copy( + filter = filter.copy( + categoryFilters = categories + ) + ) + } } } @@ -261,7 +298,7 @@ class SearchViewModel @Inject constructor( ) ) - setState { copy(filterChips = filterChips) } + setState { copy(filter = filter.copy(filterChips = filterChips)) } } @@ -273,7 +310,9 @@ class SearchViewModel @Inject constructor( onSuccess = { searchResultProducts -> copy( searchResultProducts = searchResultProducts, - filteredProductCount = searchResultProducts.size // 개수도 함께 업데이트 + filter = filter.copy( + filteredProductCount = searchResultProducts.size + ) ) } )