Skip to content

feature#44 search 검색 후 화면 개발 #46

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ac9dfc0
feat: 문자열 리소스 추가
soochan8 Aug 18, 2025
7e12c34
feat: 초기 필터 더미데이터 생성 및 클릭 시 상태 변경 추가
soochan8 Aug 18, 2025
232131e
feat: 최근 검색어 click 추가
soochan8 Aug 18, 2025
621cb16
feat: 검색어 입력 후, 포커스 해제
soochan8 Aug 18, 2025
2d39afc
feat: 필터(오늘드림, 픽업) enum class 생성
soochan8 Aug 18, 2025
fddf615
change: 클래스 네이밍 변경
soochan8 Aug 18, 2025
cbaa9d8
remove 필터 data class 제거
soochan8 Aug 18, 2025
dce7acf
feat: 필터 관련 이벤트 추가
soochan8 Aug 18, 2025
7a1a07e
feat: 구조변경 및 AnimatedVisibility 적용
soochan8 Aug 18, 2025
78a083c
feat: 검색 후 보여지는 화면 추가
soochan8 Aug 18, 2025
dda15d9
feat: 필터 composable 추가
soochan8 Aug 18, 2025
e05d79f
feat: productDao 필터 카테고리 가져오는 메소드 추가
soochan8 Aug 19, 2025
3ae2c1a
feat: 필터 카테고리 DTO 추가
soochan8 Aug 19, 2025
92a2be7
feat: 필터 카테고리 Mapper, VO, Model 생성
soochan8 Aug 19, 2025
d98e56b
remove: import 제거
soochan8 Aug 19, 2025
5a2412a
feat:문자열 리소스 추가
soochan8 Aug 19, 2025
d203cf3
feat: 필터 카테고리, 카테고리 적용된 상품, 카테고리 적용된 상품 개수 메소드 추가
soochan8 Aug 19, 2025
6b1a4d1
feat: 카테고리 필터 상태 및 이벤트 처리 로직 추가
soochan8 Aug 19, 2025
3e9ce08
feat: SearchContract 카테고리 필터 상태 및 이벤트 추가
soochan8 Aug 19, 2025
8950acd
feat: SearchViewModel 카테고리 필터링 로직 추가
soochan8 Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions core/database/src/main/java/com/chan/database/dao/ProductDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.chan.database.dto.CategoryDetailTabsDto
import com.chan.database.dto.CategoryTabDto
import com.chan.database.dto.FilterCategoriesDto
import com.chan.database.entity.ProductEntity

@Dao
Expand Down Expand Up @@ -43,7 +44,7 @@ interface ProductDao {
}

//세일 상품 가져오기
suspend fun getSaleProducts(limit : Int): List<ProductEntity.Categories.SubCategories.Products> {
suspend fun getSaleProducts(limit: Int): List<ProductEntity.Categories.SubCategories.Products> {
return getAll()
.flatMap { it.categories }
.flatMap { it.subCategories }
Expand All @@ -61,7 +62,12 @@ interface ProductDao {
category.subCategories.any { it.categoryId == subCategoryId }
}
?.subCategories
?.map { CategoryDetailTabsDto(categoryId = it.categoryId, categoryName = it.categoryName) }
?.map {
CategoryDetailTabsDto(
categoryId = it.categoryId,
categoryName = it.categoryName
)
}
?: emptyList()
}

Expand All @@ -86,4 +92,55 @@ interface ProductDao {
.flatMap { it.products }
.filter { it.productName.contains(query, ignoreCase = true) }
}

// 선택된 하위 카테고리 이름 목록으로 상품 필터링하기
suspend fun getProductsBySubCategoryNames(subCategoryNames: Set<String>): List<ProductEntity.Categories.SubCategories.Products> {
if (subCategoryNames.isEmpty()) {
return emptyList() // 선택된 필터가 없으면 빈 리스트 반환
}
return getAll()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ProductDao 전반적으로 getAll() 를 사용해서 쿼리하고 있는데요. DB 쿼리에 파라메터로 넣지 않는 이유가 있으신가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@f-lab-pepe @jaeho
단순하고 빠르게 기능을 구현하기 위해서 사용했던 것 같습니다. 멘토님의 코멘트를 생각해보다가 getAll()을 계속 사용하면 중복으로 조회가 발생하므로 불필요한 비용이 발생하는 것 같습니다. 현재 구조에서는 고정된 상품 리스트를 사용하고 있으므로 core모듈에서 공통으로 1번만 조회한 후 여러 모듈에서 재사용 하는 것이 성능면에서 최적화 될 것이라고 생각됩니다.
이렇게 진행하는 것이 괜찮은 방법일까요?

.flatMap { it.categories }
.flatMap { it.subCategories }
.filter { subCategory -> subCategoryNames.contains(subCategory.categoryName) }
.flatMap { it.products }
.distinctBy { it.productId } // 여러 카테고리에 속한 동일 상품 중복 제거
}

// 선택된 하위 카테고리 이름 목록으로 상품 개수 세기
suspend fun getProductCountBySubCategoryNames(subCategoryNames: Set<String>): Int {
val subCategoriesWithProducts = getAll()
.flatMap { it.categories }
.flatMap { it.subCategories }

val products = if (subCategoryNames.isEmpty()) {
// 선택된 필터가 없으면 전체 상품이 대상
subCategoriesWithProducts.flatMap { it.products }
} else {
// 선택된 필터가 있으면 해당 카테고리의 상품만 대상
subCategoriesWithProducts
.filter { subCategory -> subCategoryNames.contains(subCategory.categoryName) }
.flatMap { it.products }
}
// 중복 제거 후 개수 반환
return products.distinctBy { it.productId }.size
}

//필터 카테고리 정보 가져오기
suspend fun getFilterCategories(): List<FilterCategoriesDto> {
return getAll()
.flatMap { productEntity -> productEntity.categories }
.distinctBy { category -> category.name }
.map { category ->
FilterCategoriesDto(
categoryId = category.id,
name = category.name,
subCategories = category.subCategories.map { subCategory ->
FilterCategoriesDto.SubCategoryDto(
subCategoryId = subCategory.categoryId,
subCategoryName = subCategory.categoryName
)
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.chan.database.dto

data class FilterCategoriesDto(
val categoryId: String,
val name: String,
val subCategories: List<SubCategoryDto>
) {
data class SubCategoryDto(
val subCategoryId: String,
val subCategoryName: String,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.chan.search.data.mappers

import com.chan.database.dto.FilterCategoriesDto
import com.chan.search.domain.model.FilterCategoriesVO
import com.chan.search.domain.model.SubCategoryVO

fun FilterCategoriesDto.toCategoryFilterDomain(): FilterCategoriesVO =
FilterCategoriesVO(
categoryId = this.categoryId,
name = this.name,
subCategories = this.subCategories.map {
SubCategoryVO(
subCategoryId = it.subCategoryId,
subCategoryName = it.subCategoryName
)
}
)

Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.chan.search.data.mappers

import com.chan.database.dto.FilterCategoriesDto
import com.chan.database.entity.ProductEntity
import com.chan.database.entity.search.SearchHistoryEntity
import com.chan.domain.ProductVO
import com.chan.search.domain.model.FilterCategoriesVO
import com.chan.search.domain.model.SearchHistoryVO
import com.chan.search.ui.model.SearchHistoryModel

fun ProductEntity.Categories.SubCategories.Products.toDomain(): ProductVO {
return ProductVO(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package com.chan.search.data.repository
import com.chan.database.dao.ProductDao
import com.chan.database.dao.SearchHistoryDao
import com.chan.domain.ProductVO
import com.chan.search.data.mappers.toCategoryFilterDomain
import com.chan.search.data.mappers.toDomain
import com.chan.search.data.mappers.toSearchHistoryEntity
import com.chan.search.domain.model.FilterCategoriesVO
import com.chan.search.domain.model.SearchHistoryVO
import com.chan.search.domain.repository.SearchRepository
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -44,4 +46,16 @@ class SearchRepositoryImpl @Inject constructor(
override suspend fun getSearchResultProducts(search: String): List<ProductVO> {
return productDao.searchProductsByName(search).map { it.toDomain() }
}

override suspend fun getFilterCategories(): List<FilterCategoriesVO> {
return productDao.getFilterCategories().map { it.toCategoryFilterDomain() }
}

override suspend fun getFilteredProducts(subCategoryNames: Set<String>): List<ProductVO> {
return productDao.getProductsBySubCategoryNames(subCategoryNames).map { it.toDomain() }
}

override suspend fun getFilteredProductCount(subCategoryNames: Set<String>): Int {
return productDao.getProductCountBySubCategoryNames(subCategoryNames)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.chan.search.domain.model

data class FilterCategoriesVO(
val categoryId: String,
val name: String,
val subCategories: List<SubCategoryVO>
)

data class SubCategoryVO(
val subCategoryId: String,
val subCategoryName: String,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.chan.search.domain.repository

import com.chan.domain.ProductVO
import com.chan.search.domain.model.FilterCategoriesVO
import com.chan.search.domain.model.SearchHistoryVO
import kotlinx.coroutines.flow.Flow

Expand All @@ -13,4 +14,8 @@ interface SearchRepository {
suspend fun clearAll()

suspend fun getSearchResultProducts(search: String): List<ProductVO>

suspend fun getFilterCategories(): List<FilterCategoriesVO>
suspend fun getFilteredProducts(subCategoryNames: Set<String>): List<ProductVO>
suspend fun getFilteredProductCount(subCategoryNames: Set<String>): Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand All @@ -40,7 +41,8 @@ import com.chan.search.ui.model.SearchHistoryModel
fun RecentSearchList(
recentSearches: List<SearchHistoryModel>,
onRemoveSearch: (String) -> Unit,
onClearAllRecentSearches: () -> Unit
onClearAllRecentSearches: () -> Unit,
onSearchClick: (String) -> Unit,
) {
Column(modifier = Modifier.padding(Spacing.spacing4)) {
Row(
Expand Down Expand Up @@ -70,7 +72,8 @@ fun RecentSearchList(
recentSearches.forEach { search ->
RecentSearchChip(
keyword = search.search,
onRemove = { onRemoveSearch(search.search) }
onRemove = { onRemoveSearch(search.search) },
onSearchClick = { onSearchClick(it) }
)
}
}
Expand All @@ -80,17 +83,20 @@ fun RecentSearchList(
@Composable
fun RecentSearchChip(
keyword: String,
onRemove: () -> Unit
onRemove: () -> Unit,
onSearchClick: (String) -> Unit
) {
Surface(
shape = RoundedCornerShape(Radius.radius6),
color = Color.White,
border = BorderStroke(1.dp, LightGray.copy(alpha = 0.2f))
border = BorderStroke(1.dp, LightGray.copy(alpha = 0.2f)),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = Spacing.spacing3, vertical = Spacing.spacing2)
.padding(horizontal = Spacing.spacing3, vertical = Spacing.spacing2).clickable {
onSearchClick(keyword)
}
) {
Text(text = keyword, style = MaterialTheme.appTypography.searchChip)
IconButton(
Expand Down
Loading