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:
sanjeok77
2026-03-04 01:37:08 +09:00
parent 5f724f4dd6
commit 037925a040
3 changed files with 118 additions and 5 deletions

View File

@@ -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)
} }

View File

@@ -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,

View File

@@ -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)