feat: EdgeToEdge 개선, Pull to Refresh 추가, 출처를 알 수 없는 앱 설치 권한 추가 (v1.6.0)
- EdgeToEdge: 투명 상태바/네비게이션바 설정, WindowInsets 처리 개선 - Pull to Refresh: 아래로 당겨서 새로고침 기능 추가 - 권한: REQUEST_INSTALL_PACKAGES 추가로 APK 업데이트 설치 지원 - 버전: 1.5.0 -> 1.6.0 (versionCode 11 -> 12)
This commit is contained in:
@@ -24,8 +24,8 @@ android {
|
|||||||
applicationId = "com.hotdeal.alarm"
|
applicationId = "com.hotdeal.alarm"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 11
|
versionCode = 12
|
||||||
versionName = "1.5.0"
|
versionName = "1.6.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
<!-- Boot Completed -->
|
<!-- Boot Completed -->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
|
<!-- Install Unknown Apps (for APK updates) -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".HotDealApplication"
|
android:name=".HotDealApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshContainer
|
||||||
|
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -40,6 +43,18 @@ fun DealListScreen(
|
|||||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// Pull to Refresh 상태
|
||||||
|
val pullToRefreshState = rememberPullToRefreshState()
|
||||||
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 새로고침 완료 감지
|
||||||
|
LaunchedEffect(uiState) {
|
||||||
|
if (uiState !is MainUiState.Loading) {
|
||||||
|
isRefreshing = false
|
||||||
|
pullToRefreshState.endRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) }
|
||||||
@@ -108,12 +123,18 @@ fun DealListScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
contentWindowInsets = WindowInsets.systemBars
|
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.nestedScroll(pullToRefreshState.nestedScrollConnection)
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
|
.consumeWindowInsets(paddingValues)
|
||||||
) {
|
) {
|
||||||
// 필터 메뉴
|
// 필터 메뉴
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
@@ -248,10 +269,8 @@ fun DealListScreen(
|
|||||||
is MainUiState.Success -> {
|
is MainUiState.Success -> {
|
||||||
val filteredDeals = remember(state.deals, searchText, selectedSiteFilter, showFavoritesOnly, showKeywordMatchOnly) {
|
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 || deal.siteType == selectedSiteFilter
|
||||||
val matchesSite = selectedSiteFilter == null ||
|
|
||||||
deal.siteType == selectedSiteFilter
|
|
||||||
val matchesFavorite = !showFavoritesOnly || deal.isFavorite
|
val matchesFavorite = !showFavoritesOnly || deal.isFavorite
|
||||||
val matchesKeyword = !showKeywordMatchOnly || deal.isKeywordMatch
|
val matchesKeyword = !showKeywordMatchOnly || deal.isKeywordMatch
|
||||||
matchesSearch && matchesSite && matchesFavorite && matchesKeyword
|
matchesSearch && matchesSite && matchesFavorite && matchesKeyword
|
||||||
@@ -342,8 +361,9 @@ fun DealListScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EdgeToEdge: 하단 네비게이션 바 공간 확보
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,6 +377,23 @@ fun DealListScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pull to Refresh 인디케이터
|
||||||
|
PullToRefreshContainer(
|
||||||
|
state = pullToRefreshState,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
// 새로고침 트리거
|
||||||
|
LaunchedEffect(pullToRefreshState.isRefreshing) {
|
||||||
|
if (pullToRefreshState.isRefreshing) {
|
||||||
|
isRefreshing = true
|
||||||
|
viewModel.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,8 @@ import androidx.activity.enableEdgeToEdge
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.*
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.hotdeal.alarm.ui.theme.HotDealTheme
|
import com.hotdeal.alarm.ui.theme.HotDealTheme
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.HotDealAlarm" parent="android:Theme.Material.Light.NoActionBar">
|
<!-- EdgeToEdge compatible theme using AppCompat -->
|
||||||
<item name="android:colorPrimary">@color/primary</item>
|
<style name="Theme.HotDealAlarm" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<item name="android:colorPrimaryDark">@color/primary_dark</item>
|
<item name="colorPrimary">@color/primary</item>
|
||||||
<item name="android:colorAccent">@color/secondary</item>
|
<item name="colorPrimaryDark">@color/primary_dark</item>
|
||||||
<item name="android:statusBarColor">@color/primary_dark</item>
|
<item name="colorAccent">@color/secondary</item>
|
||||||
<item name="android:navigationBarColor">@color/background</item>
|
<!-- EdgeToEdge: 투명 상태바/네비게이션바 -->
|
||||||
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="android:windowLightNavigationBar">true</item>
|
<item name="android:windowLightNavigationBar">true</item>
|
||||||
|
<item name="android:enforceNavigationBarContrast">false</item>
|
||||||
|
<item name="android:enforceStatusBarContrast">false</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user