feat: 즐겨찾기 기능 프레젠테이션 레이어 추가
- MainViewModel에 즐겨찾기 토글/설정 메서드 추가 - DealItem에 즐겨찾기 버튼 및 콜백 추가 - DealListScreen에 즐겨찾기 필터 탭 추가 - 키워드 카드 색상을 옅은 붉은색으로 변경 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -32,13 +32,13 @@ import com.hotdeal.alarm.util.ShareHelper
|
|||||||
fun DealItem(
|
fun DealItem(
|
||||||
deal: HotDeal,
|
deal: HotDeal,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
onFavoriteToggle: (String) -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
var isFavorite by remember { mutableStateOf(false) }
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val favoriteScale by animateFloatAsState(
|
val favoriteScale by animateFloatAsState(
|
||||||
targetValue = if (isFavorite) 1.2f else 1f,
|
targetValue = if (deal.isFavorite) 1.2f else 1f,
|
||||||
animationSpec = spring(
|
animationSpec = spring(
|
||||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||||
stiffness = Spring.StiffnessLow
|
stiffness = Spring.StiffnessLow
|
||||||
@@ -205,15 +205,15 @@ fun DealItem(
|
|||||||
|
|
||||||
// 즐겨찾기 버튼
|
// 즐겨찾기 버튼
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { isFavorite = !isFavorite },
|
onClick = { onFavoriteToggle(deal.id) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(36.dp)
|
.size(36.dp)
|
||||||
.scale(favoriteScale)
|
.scale(favoriteScale)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
|
imageVector = if (deal.isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
|
||||||
contentDescription = if (isFavorite) "즐겨찾기 제거" else "즐겨찾기 추가",
|
contentDescription = if (deal.isFavorite) "즐겨찾기 제거" else "즐겨찾기 추가",
|
||||||
tint = if (isFavorite)
|
tint = if (deal.isFavorite)
|
||||||
MaterialTheme.colorScheme.error
|
MaterialTheme.colorScheme.error
|
||||||
else
|
else
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant,
|
MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ fun DealListScreen(
|
|||||||
|
|
||||||
var searchText by remember { mutableStateOf("") }
|
var searchText by remember { mutableStateOf("") }
|
||||||
var selectedSiteFilter by remember { mutableStateOf<SiteType?>(null) }
|
var selectedSiteFilter by remember { mutableStateOf<SiteType?>(null) }
|
||||||
|
var showFavoritesOnly by remember { mutableStateOf(false) }
|
||||||
var showFilterMenu by remember { mutableStateOf(false) }
|
var showFilterMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(modifier = Modifier.fillMaxSize()) {
|
Column(modifier = Modifier.fillMaxSize()) {
|
||||||
@@ -161,6 +162,14 @@ fun DealListScreen(
|
|||||||
color = color
|
color = color
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 즐겨찾기 필터
|
||||||
|
EnhancedFilterChip(
|
||||||
|
selected = showFavoritesOnly,
|
||||||
|
onClick = { showFavoritesOnly = !showFavoritesOnly },
|
||||||
|
label = "즐겨찾기",
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,13 +219,14 @@ fun DealListScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is MainUiState.Success -> {
|
is MainUiState.Success -> {
|
||||||
val filteredDeals = remember(state.deals, searchText, selectedSiteFilter) {
|
val filteredDeals = remember(state.deals, searchText, selectedSiteFilter, showFavoritesOnly) {
|
||||||
state.deals.filter { deal ->
|
state.deals.filter { deal ->
|
||||||
val matchesSearch = searchText.isBlank() ||
|
val matchesSearch = searchText.isBlank() ||
|
||||||
deal.title.contains(searchText, ignoreCase = true)
|
deal.title.contains(searchText, ignoreCase = true)
|
||||||
val matchesSite = selectedSiteFilter == null ||
|
val matchesSite = selectedSiteFilter == null ||
|
||||||
deal.siteType == selectedSiteFilter
|
deal.siteType == selectedSiteFilter
|
||||||
matchesSearch && matchesSite
|
val matchesFavorite = !showFavoritesOnly || deal.isFavorite
|
||||||
|
matchesSearch && matchesSite && matchesFavorite
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +304,9 @@ fun DealListScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(deal.url))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(deal.url))
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
|
},
|
||||||
|
onFavoriteToggle = { dealId ->
|
||||||
|
viewModel.toggleFavorite(dealId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,24 @@ class MainViewModel @Inject constructor(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
keywordDao.updateEnabled(id, enabled)
|
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() {
|
fun refresh() {
|
||||||
|
|||||||
@@ -754,10 +754,7 @@ private fun EnhancedKeywordCard(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier.size(40.dp)
|
modifier = Modifier.size(40.dp)
|
||||||
.background(
|
.background(
|
||||||
if (keyword.isEnabled)
|
Color(0xFFFFCDD2), // 옅은 붉은색 (Light Red)
|
||||||
MaterialTheme.colorScheme.primary
|
|
||||||
else
|
|
||||||
MaterialTheme.colorScheme.outline.copy(alpha = 0.5f),
|
|
||||||
CircleShape
|
CircleShape
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -765,7 +762,7 @@ private fun EnhancedKeywordCard(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Tag,
|
imageVector = Icons.Filled.Tag,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Color.White,
|
tint = Color(0xFFD32F2F), // 붉은색 아이콘
|
||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier.size(20.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user