feat: UI/UX 개선 및 자동 새로고침 기능 추가

- Edge-to-edge UI 적용 (상태바/네비게이션바 투명)
- WindowInsets 처리로 시스템 바 가림 해결
- 내 키워드 필터 추가 (키워드 매칭된 게시물만 표시)
- 백그라운드에서 포어그라운드 전환 시 자동 새로고침
- Scaffold contentWindowInsets 적용

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
sanjeok77
2026-03-05 07:56:25 +09:00
parent 426393ba86
commit 447db0a0b7
5 changed files with 342 additions and 290 deletions

View File

@@ -24,8 +24,8 @@ android {
applicationId = "com.hotdeal.alarm" applicationId = "com.hotdeal.alarm"
minSdk = 31 minSdk = 31
targetSdk = 35 targetSdk = 35
versionCode = 10 versionCode = 11
versionName = "1.4.1" versionName = "1.5.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@@ -43,9 +43,11 @@ 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 showFavoritesOnly by remember { mutableStateOf(false) }
var showKeywordMatchOnly by remember { mutableStateOf(false) }
var showFilterMenu by remember { mutableStateOf(false) } var showFilterMenu by remember { mutableStateOf(false) }
Column(modifier = Modifier.fillMaxSize()) { Scaffold(
topBar = {
TopAppBar( TopAppBar(
title = { title = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
@@ -73,7 +75,7 @@ fun DealListScreen(
actions = { actions = {
BadgedBox( BadgedBox(
badge = { badge = {
if (selectedSiteFilter != null) { if (selectedSiteFilter != null || showFavoritesOnly || showKeywordMatchOnly) {
Badge( Badge(
containerColor = MaterialTheme.colorScheme.primary, containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary contentColor = MaterialTheme.colorScheme.onPrimary
@@ -105,7 +107,15 @@ fun DealListScreen(
containerColor = MaterialTheme.colorScheme.surface containerColor = MaterialTheme.colorScheme.surface
) )
) )
},
contentWindowInsets = WindowInsets.systemBars
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// 필터 메뉴
AnimatedVisibility( AnimatedVisibility(
visible = showFilterMenu, visible = showFilterMenu,
enter = expandVertically(animationSpec = spring(stiffness = Spring.StiffnessLow)) + fadeIn(), enter = expandVertically(animationSpec = spring(stiffness = Spring.StiffnessLow)) + fadeIn(),
@@ -162,6 +172,21 @@ fun DealListScreen(
color = color color = color
) )
} }
}
Spacer(modifier = Modifier.height(8.dp))
// 특수 필터
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// 내 키워드 필터
EnhancedFilterChip(
selected = showKeywordMatchOnly,
onClick = { showKeywordMatchOnly = !showKeywordMatchOnly },
label = "내 키워드",
color = MaterialTheme.colorScheme.primary
)
// 즐겨찾기 필터 // 즐겨찾기 필터
EnhancedFilterChip( EnhancedFilterChip(
@@ -175,6 +200,7 @@ fun DealListScreen(
} }
} }
// 검색창
OutlinedTextField( OutlinedTextField(
value = searchText, value = searchText,
onValueChange = { searchText = it }, onValueChange = { searchText = it },
@@ -213,20 +239,22 @@ fun DealListScreen(
) )
) )
// 딜 리스트
when (val state = uiState) { when (val state = uiState) {
is MainUiState.Loading -> { is MainUiState.Loading -> {
DealListSkeleton(count = 5) DealListSkeleton(count = 5)
} }
is MainUiState.Success -> { is MainUiState.Success -> {
val filteredDeals = remember(state.deals, searchText, selectedSiteFilter, showFavoritesOnly) { val filteredDeals = remember(state.deals, searchText, selectedSiteFilter, showFavoritesOnly, showKeywordMatchOnly) {
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
val matchesFavorite = !showFavoritesOnly || deal.isFavorite val matchesFavorite = !showFavoritesOnly || deal.isFavorite
matchesSearch && matchesSite && matchesFavorite val matchesKeyword = !showKeywordMatchOnly || deal.isKeywordMatch
matchesSearch && matchesSite && matchesFavorite && matchesKeyword
} }
} }
@@ -236,6 +264,8 @@ fun DealListScreen(
} else { } else {
val selectedFilter = selectedSiteFilter val selectedFilter = selectedSiteFilter
val message = when { val message = when {
showKeywordMatchOnly -> "키워드 매칭된 핫딜이 없습니다"
showFavoritesOnly -> "즐겨찾기한 핫딜이 없습니다"
selectedFilter != null -> "${selectedFilter.displayName}의 핫딜이 없습니다" selectedFilter != null -> "${selectedFilter.displayName}의 핫딜이 없습니다"
searchText.isNotBlank() -> "'$searchText'에 대한 검색 결과가 없습니다" searchText.isNotBlank() -> "'$searchText'에 대한 검색 결과가 없습니다"
else -> "표시할 핫딜이 없습니다" else -> "표시할 핫딜이 없습니다"
@@ -328,6 +358,7 @@ fun DealListScreen(
} }
} }
} }
}
@Composable @Composable
private fun EnhancedFilterChip( private fun EnhancedFilterChip(

View File

@@ -130,6 +130,23 @@ class MainActivity : ComponentActivity() {
downloadReceiver?.let { downloadReceiver?.let {
ApkDownloadManager.unregisterDownloadCompleteReceiver(this, it) ApkDownloadManager.unregisterDownloadCompleteReceiver(this, it)
} }
}
// 비저빌리티 콜백 - 백그라운드에서 포어그라운드 전환 시 새로고침
private var isInBackground = false
override fun onResume() {
super.onResume()
if (isInBackground) {
// 백그라운드에서 돌아왔을 때 새로고침
workerScheduler.executeOnce()
isInBackground = false
}
}
override fun onPause() {
super.onPause()
isInBackground = true
} }
/** /**

View File

@@ -199,9 +199,11 @@ fun HotDealTheme(
if (!view.isInEditMode) { if (!view.isInEditMode) {
SideEffect { SideEffect {
val window = (view.context as Activity).window val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb() // Edge-to-edge: 투명한 상태바
window.statusBarColor = android.graphics.Color.TRANSPARENT
window.navigationBarColor = android.graphics.Color.TRANSPARENT
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
} WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars = !darkTheme
} }
MaterialTheme( MaterialTheme(
@@ -210,3 +212,4 @@ fun HotDealTheme(
content = content content = content
) )
} }
}

View File

@@ -1,13 +1,14 @@
{ {
"version": "1.4.1", "version": "1.5.0",
"versionCode": 10, "versionCode": 11,
"minSdk": 31, "minSdk": 31,
"targetSdk": 35, "targetSdk": 35,
"forceUpdate": false, "forceUpdate": false,
"updateUrl": "https://git.webpluss.net/attachments/8571e491-bc80-4773-b6c7-32254adc4117", "updateUrl": "https://git.webpluss.net/attachments/8571e491-bc80-4773-b6c7-32254adc4117",
"changelog": [ "changelog": [
"설정 화면 진입 시 크래시 수정", "Edge-to-edge UI 적용 (상태바/네비게이션바 투명)",
"AppDatabase 누락된 괄호 수정", "내 키워드 필터 추가",
"REQUEST_INSTALL_PACKAGES 권한 예외 처리" "백그라운드에서 포어그라운드 전환 시 자동 새로고침",
"WindowInsets 처리로 시스템 바 가림 해결"
] ]
} }