Add polling interval settings (1-30 minutes)
- Default interval: 2 minutes - User selectable: 1, 2, 5, 10, 15, 30 minutes - Battery/data saving with longer intervals - Update README
This commit is contained in:
@@ -105,7 +105,7 @@ class MainViewModel @Inject constructor(
|
|||||||
workerScheduler.executeOnce()
|
workerScheduler.executeOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startPolling(intervalMinutes: Long = 15) {
|
fun startPolling(intervalMinutes: Long = WorkerScheduler.DEFAULT_INTERVAL_MINUTES) {
|
||||||
workerScheduler.schedulePeriodicPolling(intervalMinutes)
|
workerScheduler.schedulePeriodicPolling(intervalMinutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.hotdeal.alarm.presentation.components.PermissionDialog
|
|||||||
import com.hotdeal.alarm.presentation.main.MainUiState
|
import com.hotdeal.alarm.presentation.main.MainUiState
|
||||||
import com.hotdeal.alarm.presentation.main.MainViewModel
|
import com.hotdeal.alarm.presentation.main.MainViewModel
|
||||||
import com.hotdeal.alarm.util.PermissionHelper
|
import com.hotdeal.alarm.util.PermissionHelper
|
||||||
|
import com.hotdeal.alarm.worker.WorkerScheduler
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -86,6 +87,17 @@ fun SettingsScreen(viewModel: MainViewModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 폴<> 주기 설정
|
||||||
|
item {
|
||||||
|
PollingIntervalSection(
|
||||||
|
currentInterval = 2, // 기본 2분
|
||||||
|
onIntervalChange = { minutes ->
|
||||||
|
viewModel.stopPolling()
|
||||||
|
viewModel.startPolling(minutes)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 사이트 선택 섹션
|
// 사이트 선택 섹션
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
@@ -188,6 +200,80 @@ fun SettingsScreen(viewModel: MainViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PollingIntervalSection(
|
||||||
|
currentInterval: Int,
|
||||||
|
onIntervalChange: (Long) -> Unit
|
||||||
|
) {
|
||||||
|
var selectedInterval by remember { mutableStateOf(currentInterval) }
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Schedule,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Text(
|
||||||
|
text = "폴<EFBFBD> 주기 설정",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "핫딜을 확인하는 간격을 설정합니다.\n짧을수록 배터리와 데이터 소모가 증가합니다.",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
// 간격 선택
|
||||||
|
Column {
|
||||||
|
listOf(1, 2, 5, 10, 15, 30).forEach { minutes ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedInterval == minutes,
|
||||||
|
onClick = {
|
||||||
|
selectedInterval = minutes
|
||||||
|
onIntervalChange(minutes.toLong())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = when (minutes) {
|
||||||
|
1 -> "1분 (빠름 - 배터리 소모 큼)"
|
||||||
|
2 -> "2분 (권장)"
|
||||||
|
5 -> "5분 (보통)"
|
||||||
|
10 -> "10분 (느림)"
|
||||||
|
15 -> "15분 (매우 느림)"
|
||||||
|
30 -> "30분 (절전)"
|
||||||
|
else -> "${minutes}분"
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PermissionStatusSection(
|
fun PermissionStatusSection(
|
||||||
permissionStatus: PermissionHelper.PermissionStatus,
|
permissionStatus: PermissionHelper.PermissionStatus,
|
||||||
|
|||||||
@@ -7,23 +7,41 @@ import java.util.concurrent.TimeUnit
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker 스케줄러
|
||||||
|
*
|
||||||
|
* 기본 폴<> 주기: 2분
|
||||||
|
* 배터리/데이터 절약을 위해 최소 2분 권장
|
||||||
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
class WorkerScheduler @Inject constructor(
|
class WorkerScheduler @Inject constructor(
|
||||||
@ApplicationContext private val context: Context
|
@ApplicationContext private val context: Context
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val DEFAULT_INTERVAL_MINUTES = 15L
|
const val DEFAULT_INTERVAL_MINUTES = 2L
|
||||||
|
const val MIN_INTERVAL_MINUTES = 1L
|
||||||
|
const val MAX_INTERVAL_MINUTES = 60L
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 주기적 폴<> 예약
|
||||||
|
* @param intervalMinutes 폴<> 간격 (분) - 기본 2분
|
||||||
|
*/
|
||||||
fun schedulePeriodicPolling(intervalMinutes: Long = DEFAULT_INTERVAL_MINUTES) {
|
fun schedulePeriodicPolling(intervalMinutes: Long = DEFAULT_INTERVAL_MINUTES) {
|
||||||
|
// 최소/최대 값 제한
|
||||||
|
val safeInterval = intervalMinutes.coerceIn(MIN_INTERVAL_MINUTES, MAX_INTERVAL_MINUTES)
|
||||||
|
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
.setRequiresBatteryNotLow(true)
|
.setRequiresBatteryNotLow(true) // 배터리 부족 시 실행 안 함
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
// 유연한 실행 시간 (±25%)
|
||||||
|
val flexTime = (safeInterval * 0.25).toLong().coerceAtLeast(1)
|
||||||
|
|
||||||
val workRequest = PeriodicWorkRequestBuilder<HotDealPollingWorker>(
|
val workRequest = PeriodicWorkRequestBuilder<HotDealPollingWorker>(
|
||||||
intervalMinutes, TimeUnit.MINUTES,
|
safeInterval, TimeUnit.MINUTES,
|
||||||
intervalMinutes / 3, TimeUnit.MINUTES
|
flexTime, TimeUnit.MINUTES
|
||||||
)
|
)
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.setBackoffCriteria(
|
.setBackoffCriteria(
|
||||||
@@ -41,6 +59,9 @@ class WorkerScheduler @Inject constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 한 번만 실행
|
||||||
|
*/
|
||||||
fun executeOnce() {
|
fun executeOnce() {
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
@@ -54,10 +75,16 @@ class WorkerScheduler @Inject constructor(
|
|||||||
WorkManager.getInstance(context).enqueue(workRequest)
|
WorkManager.getInstance(context).enqueue(workRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 폴<> 취소
|
||||||
|
*/
|
||||||
fun cancelPolling() {
|
fun cancelPolling() {
|
||||||
WorkManager.getInstance(context).cancelUniqueWork(HotDealPollingWorker.WORK_NAME)
|
WorkManager.getInstance(context).cancelUniqueWork(HotDealPollingWorker.WORK_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 폴<> 활성화 여부 확인
|
||||||
|
*/
|
||||||
fun isPollingActive(): Boolean {
|
fun isPollingActive(): Boolean {
|
||||||
val workInfos = WorkManager.getInstance(context)
|
val workInfos = WorkManager.getInstance(context)
|
||||||
.getWorkInfosForUniqueWork(HotDealPollingWorker.WORK_NAME)
|
.getWorkInfosForUniqueWork(HotDealPollingWorker.WORK_NAME)
|
||||||
|
|||||||
Reference in New Issue
Block a user