diff --git a/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt b/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt index f28f5a9..459fd4e 100644 --- a/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/hotdeal/alarm/presentation/main/MainViewModel.kt @@ -105,7 +105,7 @@ class MainViewModel @Inject constructor( workerScheduler.executeOnce() } - fun startPolling(intervalMinutes: Long = 15) { + fun startPolling(intervalMinutes: Long = WorkerScheduler.DEFAULT_INTERVAL_MINUTES) { workerScheduler.schedulePeriodicPolling(intervalMinutes) } diff --git a/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt index 8c2c769..22dbd66 100644 --- a/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/hotdeal/alarm/presentation/settings/SettingsScreen.kt @@ -28,6 +28,7 @@ import com.hotdeal.alarm.presentation.components.PermissionDialog import com.hotdeal.alarm.presentation.main.MainUiState import com.hotdeal.alarm.presentation.main.MainViewModel import com.hotdeal.alarm.util.PermissionHelper +import com.hotdeal.alarm.worker.WorkerScheduler @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -86,6 +87,17 @@ fun SettingsScreen(viewModel: MainViewModel) { ) } + // 폴� 주기 설정 + item { + PollingIntervalSection( + currentInterval = 2, // 기본 2분 + onIntervalChange = { minutes -> + viewModel.stopPolling() + viewModel.startPolling(minutes) + } + ) + } + // 사이트 선택 섹션 item { 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 = "폴� 주기 설정", + 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 fun PermissionStatusSection( permissionStatus: PermissionHelper.PermissionStatus, diff --git a/app/src/main/java/com/hotdeal/alarm/worker/WorkerScheduler.kt b/app/src/main/java/com/hotdeal/alarm/worker/WorkerScheduler.kt index 928e682..d246398 100644 --- a/app/src/main/java/com/hotdeal/alarm/worker/WorkerScheduler.kt +++ b/app/src/main/java/com/hotdeal/alarm/worker/WorkerScheduler.kt @@ -7,23 +7,41 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton +/** + * Worker 스케줄러 + * + * 기본 폴� 주기: 2분 + * 배터리/데이터 절약을 위해 최소 2분 권장 + */ @Singleton class WorkerScheduler @Inject constructor( @ApplicationContext private val context: Context ) { 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) { + // 최소/최대 값 제한 + val safeInterval = intervalMinutes.coerceIn(MIN_INTERVAL_MINUTES, MAX_INTERVAL_MINUTES) + val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) + .setRequiresBatteryNotLow(true) // 배터리 부족 시 실행 안 함 .build() + // 유연한 실행 시간 (±25%) + val flexTime = (safeInterval * 0.25).toLong().coerceAtLeast(1) + val workRequest = PeriodicWorkRequestBuilder( - intervalMinutes, TimeUnit.MINUTES, - intervalMinutes / 3, TimeUnit.MINUTES + safeInterval, TimeUnit.MINUTES, + flexTime, TimeUnit.MINUTES ) .setConstraints(constraints) .setBackoffCriteria( @@ -41,6 +59,9 @@ class WorkerScheduler @Inject constructor( ) } + /** + * 한 번만 실행 + */ fun executeOnce() { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) @@ -54,10 +75,16 @@ class WorkerScheduler @Inject constructor( WorkManager.getInstance(context).enqueue(workRequest) } + /** + * 폴� 취소 + */ fun cancelPolling() { WorkManager.getInstance(context).cancelUniqueWork(HotDealPollingWorker.WORK_NAME) } + /** + * 폴� 활성화 여부 확인 + */ fun isPollingActive(): Boolean { val workInfos = WorkManager.getInstance(context) .getWorkInfosForUniqueWork(HotDealPollingWorker.WORK_NAME)