From c927cc6f9c41ee701cd8ef355dccef864017a51b Mon Sep 17 00:00:00 2001 From: sanjeok77 Date: Sat, 7 Mar 2026 05:20:05 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20UI/UX=20=EB=8C=80=ED=8F=AD=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20=EB=B0=8F=20Pull=20to=20Refresh=20=EC=8A=A4?= =?UTF-8?q?=ED=94=BC=EB=84=88=20=EC=88=98=EC=A0=95=20(v1.11.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### UI/UX 개선 - DealItem 프로덕션 레벨 디자인 적용 - 키워드 매칭 그라데이션 배경 효과 - 세련된 타이포그래피 및 아이콘 스타일 - 부드러운 바운스 애니메이션 ### Pull to Refresh 수정 - 스피너가 레이어에 가려지는 문제 해결 - 상단 패딩 추가로 전체 스피너 표시 --- app/build.gradle.kts | 4 +- .../alarm/presentation/components/DealItem.kt | 346 ++++++++++-------- .../presentation/deallist/DealListScreen.kt | 19 +- version.json | 10 +- 4 files changed, 215 insertions(+), 164 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 55a6862..545c34a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,8 +24,8 @@ android { applicationId = "com.hotdeal.alarm" minSdk = 31 targetSdk = 35 - versionCode = 16 - versionName = "1.10.0" + versionCode = 17 + versionName = "1.11.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { 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 ec6a0d5..02c118f 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 @@ -16,14 +16,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.scale +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.hotdeal.alarm.domain.model.HotDeal -import com.hotdeal.alarm.ui.theme.Spacing import com.hotdeal.alarm.ui.theme.getSiteColor import com.hotdeal.alarm.util.ShareHelper @@ -37,8 +39,9 @@ fun DealItem( ) { val context = LocalContext.current + // 부드러운 바운스 애니메이션 val favoriteScale by animateFloatAsState( - targetValue = if (deal.isFavorite) 1.2f else 1f, + targetValue = if (deal.isFavorite) 1.15f else 1f, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow @@ -48,10 +51,10 @@ fun DealItem( val siteColor = getSiteColor(deal.siteType) - // 키워드 매칭 강조 - 옅은 빨간색 배경으로 단순화 + // 키워드 매칭 배경 - 그라데이션 효과 val cardColors = if (deal.isKeywordMatch) { CardDefaults.elevatedCardColors( - containerColor = Color(0xFFFFEBEE) // 옅은 빨간색 배경 (Material Red 50) + containerColor = Color.Transparent ) } else { CardDefaults.elevatedCardColors( @@ -59,183 +62,228 @@ fun DealItem( ) } - ElevatedCard( - modifier = modifier.fillMaxWidth(), - shape = RoundedCornerShape(20.dp), - colors = cardColors, - elevation = CardDefaults.elevatedCardElevation( - defaultElevation = 2.dp - ), - onClick = onClick - ) { - Column( + Box(modifier = modifier) { + if (deal.isKeywordMatch) { + // 키워드 매칭: 그라데이션 배경 + Box( + modifier = Modifier + .fillMaxWidth() + .height(IntrinsicSize.Min) + .clip(RoundedCornerShape(20.dp)) + .background( + Brush.linearGradient( + colors = listOf( + Color(0xFFFFEBEE), + Color(0xFFFFCDD2).copy(alpha = 0.4f) + ) + ) + ) + ) + } + + ElevatedCard( modifier = Modifier .fillMaxWidth() - .padding(16.dp) - ) { - // 상단: 사이트 뱃지 + 게시판 + 액션 - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - // 사이트 뱃지 (개선된 디자인) - Surface( - shape = RoundedCornerShape(10.dp), - color = siteColor.copy(alpha = 0.12f), - modifier = Modifier.height(28.dp) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(6.dp), - modifier = Modifier.padding(horizontal = 10.dp) - ) { - Box( - modifier = Modifier - .size(8.dp) - .background(siteColor, CircleShape) - ) - Text( - text = deal.siteType?.displayName ?: deal.siteName, - style = MaterialTheme.typography.labelMedium, - fontWeight = FontWeight.Medium, - color = siteColor - ) + .then( + if (deal.isKeywordMatch) { + Modifier.background(Color.Transparent) + } else { + Modifier } - } - - Spacer(modifier = Modifier.width(8.dp)) - - // 게시판 이름 - Surface( - shape = RoundedCornerShape(8.dp), - color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f), - modifier = Modifier.height(24.dp) + ), + shape = RoundedCornerShape(20.dp), + colors = cardColors, + elevation = CardDefaults.elevatedCardElevation( + defaultElevation = if (deal.isKeywordMatch) 0.dp else 2.dp + ), + onClick = onClick + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + // 상단: 사이트 뱃지 + 게시판 + 액션 + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically ) { - Text( - text = deal.boardDisplayName, - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) - ) - } - - Spacer(modifier = Modifier.weight(1f)) - - // 키워드 매칭 배지 - 빨간색으로 변경 - if (deal.isKeywordMatch) { + // 사이트 뱃지 - 개선된 디자인 Surface( - shape = RoundedCornerShape(8.dp), - color = Color(0xFFE53935), // 빨간색 - modifier = Modifier.height(24.dp) + shape = RoundedCornerShape(12.dp), + color = siteColor.copy(alpha = 0.12f), + modifier = Modifier.height(28.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.padding(horizontal = 8.dp) + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.padding(horizontal = 10.dp) ) { - Icon( - imageVector = Icons.Filled.Star, - contentDescription = null, - tint = Color.White, - modifier = Modifier.size(12.dp) + Box( + modifier = Modifier + .size(8.dp) + .background(siteColor, CircleShape) ) Text( - text = "내 키워드", - style = MaterialTheme.typography.labelSmall, - fontWeight = FontWeight.Medium, - color = Color.White + text = deal.siteType?.displayName ?: deal.siteName, + style = MaterialTheme.typography.labelMedium.copy( + fontWeight = FontWeight.SemiBold, + fontSize = 13.sp + ), + color = siteColor ) } } - Spacer(modifier = Modifier.width(4.dp)) - } - // 액션 버튼들 - Row( - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - // 공유 버튼 - IconButton( - onClick = { ShareHelper.shareDeal(context, deal) }, - modifier = Modifier.size(36.dp) + Spacer(modifier = Modifier.width(8.dp)) + + // 게시판 이름 - 더 세련된 스타일 + Surface( + shape = RoundedCornerShape(10.dp), + color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f), + modifier = Modifier.height(24.dp) ) { - Icon( - imageVector = Icons.Outlined.Share, - contentDescription = "공유", - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(18.dp) + Text( + text = deal.boardDisplayName, + style = MaterialTheme.typography.labelSmall.copy( + fontSize = 11.sp, + fontWeight = FontWeight.Medium + ), + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.8f), + modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp) ) } - // 즐겨찾기 버튼 - IconButton( - onClick = { onFavoriteToggle(deal.id) }, - modifier = Modifier - .size(36.dp) - .scale(favoriteScale) + Spacer(modifier = Modifier.weight(1f)) + + // 키워드 매칭 배지 - 더 눈에 띄는 디자인 + if (deal.isKeywordMatch) { + Surface( + shape = RoundedCornerShape(10.dp), + color = Color(0xFFE53935), + modifier = Modifier.height(24.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + modifier = Modifier.padding(horizontal = 10.dp) + ) { + Icon( + imageVector = Icons.Filled.Star, + contentDescription = null, + tint = Color.White, + modifier = Modifier.size(12.dp) + ) + Text( + text = "내 키워드", + style = MaterialTheme.typography.labelSmall.copy( + fontWeight = FontWeight.SemiBold, + fontSize = 11.sp + ), + color = Color.White + ) + } + } + Spacer(modifier = Modifier.width(4.dp)) + } + + // 액션 버튼들 - 더 작고 세련된 스타일 + Row( + horizontalArrangement = Arrangement.spacedBy(0.dp) ) { - 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) - ) + // 공유 버튼 + IconButton( + onClick = { ShareHelper.shareDeal(context, deal) }, + modifier = Modifier.size(36.dp) + ) { + Icon( + imageVector = Icons.Outlined.Share, + contentDescription = "공유", + tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), + 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.copy(alpha = 0.7f), + modifier = Modifier.size(18.dp) + ) + } } } - } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - // 제목 - Text( - text = deal.title, - style = if (deal.isKeywordMatch) { - MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold) - } else { - MaterialTheme.typography.bodyLarge - }, - maxLines = 2, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) + // 제목 - 더 크고 읽기 쉬운 스타일 + Text( + text = deal.title, + style = if (deal.isKeywordMatch) { + MaterialTheme.typography.bodyLarge.copy( + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + letterSpacing = (-0.1).sp + ) + } else { + MaterialTheme.typography.bodyLarge.copy( + fontWeight = FontWeight.Normal, + fontSize = 15.sp + ) + }, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface, + lineHeight = 22.sp + ) - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(10.dp)) - // 하단: 시간 + 추가 정보 - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - // 시간 + // 하단: 시간 + 화살표 Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically ) { - Icon( - imageVector = Icons.Outlined.Schedule, - contentDescription = null, - tint = MaterialTheme.colorScheme.onSurfaceVariant, - modifier = Modifier.size(14.dp) - ) + // 시간 - 아이콘 없이 깔끔하게 Text( text = formatTime(deal.createdAt), - style = MaterialTheme.typography.labelSmall, - color = MaterialTheme.colorScheme.onSurfaceVariant + style = MaterialTheme.typography.labelSmall.copy( + fontWeight = FontWeight.Medium, + fontSize = 12.sp + ), + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) ) + + Spacer(modifier = Modifier.weight(1f)) + + // 화살표 (클릭 유도) - 더 세련된 스타일 + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.primary.copy(alpha = 0.08f), + modifier = Modifier.size(28.dp) + ) { + Box( + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Outlined.ArrowForward, + contentDescription = "이동", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(14.dp) + ) + } + } } - - Spacer(modifier = Modifier.weight(1f)) - - // 화살표 (클릭 유도) - Icon( - imageVector = Icons.Outlined.ArrowForward, - contentDescription = "이동", - tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), - modifier = Modifier.size(16.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 2f5f5a1..c355b97 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 @@ -415,15 +415,16 @@ fun DealListScreen( } } - // Pull to Refresh 인디케이터 - z-index 높여서 잘림 방지 - PullToRefreshContainer( - state = pullToRefreshState, - modifier = Modifier - .align(Alignment.TopCenter) - .zIndex(999f), // 최상위 레이어로 설정 - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.onPrimaryContainer - ) + // Pull to Refresh 인디케이터 - 상단 패딩으로 잘 보이게 + PullToRefreshContainer( + state = pullToRefreshState, + modifier = Modifier + .align(Alignment.TopCenter) + .padding(top = 8.dp) // TopAppBar와 겹치지 않도록 패딩 + .zIndex(999f), + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) // 새로고침 트리거 LaunchedEffect(pullToRefreshState.isRefreshing) { diff --git a/version.json b/version.json index e3b2d1f..9d7eb27 100644 --- a/version.json +++ b/version.json @@ -1,12 +1,14 @@ { - "version": "1.10.0", - "versionCode": 16, + "version": "1.11.0", + "versionCode": 17, "minSdk": 31, "targetSdk": 35, "forceUpdate": false, "updateUrl": "https://git.webpluss.net/attachments/33643eaf-028a-461f-94a6-d3e9bc839ecb", "changelog": [ - "Pull to Refresh 민감도 대폭 향상 - 1/3 거리로 조정", - "새로고침이 훨씬 쉽고 빠르게 작동" + "UI/UX 대폭 개선 - 프로덕션 레벨 디자인", + "Pull to Refresh 스피너 가려짐 해결", + "키워드 매칭 그라데이션 배경 효과", + "DealItem 카드 디자인 고도화" ] }