From bb1fbbb80c42c892bb82796c4f7e19f6fea3d0e5 Mon Sep 17 00:00:00 2001 From: sanjeok77 Date: Thu, 5 Mar 2026 04:15:18 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A6=90=EA=B2=A8=EC=B0=BE=EA=B8=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=ED=94=84=EB=A0=88=EC=A0=A0=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MainViewModel에 즐겨찾기 토글/설정 메서드 추가 - DealItem에 즐겨찾기 버튼 및 콜백 추가 - DealListScreen에 즐겨찾기 필터 탭 추가 - 키워드 카드 색상을 옅은 붉은색으로 변경 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- .../alarm/presentation/components/DealItem.kt | 60 ++++++++-------- .../presentation/deallist/DealListScreen.kt | 71 +++++++++++-------- .../alarm/presentation/main/MainViewModel.kt | 28 ++++++-- .../presentation/settings/SettingsScreen.kt | 33 ++++----- 4 files changed, 110 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/com/hotdeal/alarm/presentation/components/DealItem.kt b/app/src/main/java/com/hotdeal/alarm/presentation/components/DealItem.kt index 6b22fd9..c23eff4 100644 --- a/app/src/main/java/com/hotdeal/alarm/presentation/components/DealItem.kt +++ b/app/src/main/java/com/hotdeal/alarm/presentation/components/DealItem.kt @@ -30,21 +30,21 @@ import com.hotdeal.alarm.util.ShareHelper @OptIn(ExperimentalMaterial3Api::class) @Composable fun DealItem( - deal: HotDeal, - onClick: () -> Unit, - modifier: Modifier = Modifier + deal: HotDeal, + onClick: () -> Unit, + onFavoriteToggle: (String) -> Unit = {}, + modifier: Modifier = Modifier ) { - var isFavorite by remember { mutableStateOf(false) } - val context = LocalContext.current + val context = LocalContext.current - val favoriteScale by animateFloatAsState( - targetValue = if (isFavorite) 1.2f else 1f, - animationSpec = spring( - dampingRatio = Spring.DampingRatioMediumBouncy, - stiffness = Spring.StiffnessLow - ), - label = "favorite_scale" - ) + val favoriteScale by animateFloatAsState( + targetValue = if (deal.isFavorite) 1.2f else 1f, + animationSpec = spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessLow + ), + label = "favorite_scale" + ) val siteColor = getSiteColor(deal.siteType) @@ -203,23 +203,23 @@ fun DealItem( ) } - // 즐겨찾기 버튼 - IconButton( - onClick = { isFavorite = !isFavorite }, - modifier = Modifier - .size(36.dp) - .scale(favoriteScale) - ) { - Icon( - imageVector = if (isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, - contentDescription = if (isFavorite) "즐겨찾기 제거" else "즐겨찾기 추가", - tint = if (isFavorite) - MaterialTheme.colorScheme.error - else - MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(18.dp) - ) - } + // 즐겨찾기 버튼 + IconButton( + onClick = { onFavoriteToggle(deal.id) }, + modifier = Modifier + .size(36.dp) + .scale(favoriteScale) + ) { + Icon( + imageVector = if (deal.isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, + contentDescription = if (deal.isFavorite) "즐겨찾기 제거" else "즐겨찾기 추가", + tint = if (deal.isFavorite) + MaterialTheme.colorScheme.error + else + MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(18.dp) + ) + } } } diff --git a/app/src/main/java/com/hotdeal/alarm/presentation/deallist/DealListScreen.kt b/app/src/main/java/com/hotdeal/alarm/presentation/deallist/DealListScreen.kt index b768226..5abf7fd 100644 --- a/app/src/main/java/com/hotdeal/alarm/presentation/deallist/DealListScreen.kt +++ b/app/src/main/java/com/hotdeal/alarm/presentation/deallist/DealListScreen.kt @@ -40,9 +40,10 @@ fun DealListScreen( val uiState by viewModel.uiState.collectAsStateWithLifecycle() val context = LocalContext.current - var searchText by remember { mutableStateOf("") } - var selectedSiteFilter by remember { mutableStateOf(null) } - var showFilterMenu by remember { mutableStateOf(false) } + var searchText by remember { mutableStateOf("") } + var selectedSiteFilter by remember { mutableStateOf(null) } + var showFavoritesOnly by remember { mutableStateOf(false) } + var showFilterMenu by remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxSize()) { TopAppBar( @@ -150,17 +151,25 @@ fun DealListScreen( color = MaterialTheme.colorScheme.primary ) - SiteType.entries.forEach { siteType -> - val color = getSiteColor(siteType) - EnhancedFilterChip( - selected = selectedSiteFilter == siteType, - onClick = { - selectedSiteFilter = if (selectedSiteFilter == siteType) null else siteType - }, - label = siteType.displayName, - color = color - ) - } + SiteType.entries.forEach { siteType -> + val color = getSiteColor(siteType) + EnhancedFilterChip( + selected = selectedSiteFilter == siteType, + onClick = { + selectedSiteFilter = if (selectedSiteFilter == siteType) null else siteType + }, + label = siteType.displayName, + color = color + ) + } + + // 즐겨찾기 필터 + EnhancedFilterChip( + selected = showFavoritesOnly, + onClick = { showFavoritesOnly = !showFavoritesOnly }, + label = "즐겨찾기", + color = MaterialTheme.colorScheme.error + ) } } } @@ -210,14 +219,15 @@ fun DealListScreen( } is MainUiState.Success -> { - val filteredDeals = remember(state.deals, searchText, selectedSiteFilter) { - state.deals.filter { deal -> - val matchesSearch = searchText.isBlank() || - deal.title.contains(searchText, ignoreCase = true) - val matchesSite = selectedSiteFilter == null || - deal.siteType == selectedSiteFilter - matchesSearch && matchesSite - } + val filteredDeals = remember(state.deals, searchText, selectedSiteFilter, showFavoritesOnly) { + state.deals.filter { deal -> + val matchesSearch = searchText.isBlank() || + deal.title.contains(searchText, ignoreCase = true) + val matchesSite = selectedSiteFilter == null || + deal.siteType == selectedSiteFilter + val matchesFavorite = !showFavoritesOnly || deal.isFavorite + matchesSearch && matchesSite && matchesFavorite + } } if (filteredDeals.isEmpty()) { @@ -289,13 +299,16 @@ fun DealListScreen( ), exit = fadeOut(animationSpec = tween(200)) ) { - DealItem( - deal = deal, - onClick = { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(deal.url)) - context.startActivity(intent) - } - ) + DealItem( + deal = deal, + onClick = { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(deal.url)) + context.startActivity(intent) + }, + onFavoriteToggle = { dealId -> + viewModel.toggleFavorite(dealId) + } + ) } } diff --git a/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt b/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt index cd1217a..32faf19 100644 --- a/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt @@ -104,11 +104,29 @@ class MainViewModel @Inject constructor( } } - fun toggleKeyword(id: Long, enabled: Boolean) { - viewModelScope.launch { - keywordDao.updateEnabled(id, enabled) - } - } + fun toggleKeyword(id: Long, enabled: Boolean) { + viewModelScope.launch { + keywordDao.updateEnabled(id, enabled) + } + } + + /** + * 즐겨찾기 토글 + */ + fun toggleFavorite(dealId: String) { + viewModelScope.launch { + hotDealDao.toggleFavorite(dealId) + } + } + + /** + * 즐겨찾기 설정 + */ + fun setFavorite(dealId: String, isFavorite: Boolean) { + viewModelScope.launch { + hotDealDao.setFavorite(dealId, isFavorite) + } + } fun refresh() { workerScheduler.executeOnce() diff --git a/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt index 6567c53..7df0432 100644 --- a/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt @@ -751,24 +751,21 @@ private fun EnhancedKeywordCard( verticalAlignment = Alignment.CenterVertically ) { // 키워드 아이콘 - Box( - modifier = Modifier.size(40.dp) - .background( - if (keyword.isEnabled) - MaterialTheme.colorScheme.primary - else - MaterialTheme.colorScheme.outline.copy(alpha = 0.5f), - CircleShape - ), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.Filled.Tag, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(20.dp) - ) - } + Box( + modifier = Modifier.size(40.dp) + .background( + Color(0xFFFFCDD2), // 옅은 붉은색 (Light Red) + CircleShape + ), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Filled.Tag, + contentDescription = null, + tint = Color(0xFFD32F2F), // 붉은색 아이콘 + modifier = Modifier.size(20.dp) + ) + } Spacer(modifier = Modifier.width(12.dp))