v0.3.5: update MainViewModel.kt
This commit is contained in:
1 parent
af33f0d02b
commit
9785f55b81
1 file changed
+35
-13
@@ -10,6 +10,7 @@ import com.hotdeal.alarm.data.local.db.entity.SiteConfigEntity
|
|||||||
import com.hotdeal.alarm.data.local.preferences.AppSettings
|
import com.hotdeal.alarm.data.local.preferences.AppSettings
|
||||||
import com.hotdeal.alarm.domain.model.Keyword
|
import com.hotdeal.alarm.domain.model.Keyword
|
||||||
import com.hotdeal.alarm.domain.model.SiteType
|
import com.hotdeal.alarm.domain.model.SiteType
|
||||||
|
import com.hotdeal.alarm.domain.usecase.HotDealScraperManager
|
||||||
import com.hotdeal.alarm.worker.WorkerScheduler
|
import com.hotdeal.alarm.worker.WorkerScheduler
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@@ -21,6 +22,7 @@ class MainViewModel @Inject constructor(
|
|||||||
private val hotDealDao: HotDealDao,
|
private val hotDealDao: HotDealDao,
|
||||||
private val siteConfigDao: SiteConfigDao,
|
private val siteConfigDao: SiteConfigDao,
|
||||||
private val keywordDao: KeywordDao,
|
private val keywordDao: KeywordDao,
|
||||||
|
private val scraperManager: HotDealScraperManager,
|
||||||
private val workerScheduler: WorkerScheduler,
|
private val workerScheduler: WorkerScheduler,
|
||||||
private val appSettings: AppSettings
|
private val appSettings: AppSettings
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -28,6 +30,10 @@ class MainViewModel @Inject constructor(
|
|||||||
private val _uiState = MutableStateFlow<MainUiState>(MainUiState.Loading)
|
private val _uiState = MutableStateFlow<MainUiState>(MainUiState.Loading)
|
||||||
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
|
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
// 새로고침 진행 상태
|
||||||
|
private val _isRefreshing = MutableStateFlow(false)
|
||||||
|
val isRefreshing: StateFlow<Boolean> = _isRefreshing.asStateFlow()
|
||||||
|
|
||||||
// 폴링 주기
|
// 폴링 주기
|
||||||
val pollingInterval: StateFlow<Int> = appSettings.pollingInterval
|
val pollingInterval: StateFlow<Int> = appSettings.pollingInterval
|
||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, 2)
|
.stateIn(viewModelScope, SharingStarted.Eagerly, 2)
|
||||||
@@ -44,18 +50,19 @@ class MainViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeApp() {
|
private fun initializeApp() {
|
||||||
// 1. 사이트 설정 초기화 후 DB Flow 구독 (별도 코루틴 — collect는 영원히 suspend)
|
// 1. 사이트 설정 초기화 후 DB Flow 구독 (별도 코루틴 — collect는 지속 실행)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
initializeDefaultSiteConfigs()
|
initializeDefaultSiteConfigs()
|
||||||
loadState() // combine().collect — 이 코루틴은 영원히 살아있음
|
loadState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 백그라운드 폴링 등록 + 즉시 1회 스크래핑 (별도 코루틴)
|
// 2. 백그라운드 폴링 등록 + 앱 진입 즉시 직접 1회 스크래핑 실행
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val savedInterval = appSettings.pollingInterval.first()
|
val savedInterval = appSettings.pollingInterval.first()
|
||||||
workerScheduler.schedulePeriodicPolling(savedInterval.toLong())
|
workerScheduler.schedulePeriodicPolling(savedInterval.toLong())
|
||||||
// 앱 시작 시 즉시 1회 실행하여 최신 데이터 갱신
|
|
||||||
workerScheduler.executeOnce()
|
// 지연 없는 직접 스크래핑 실행 -> Room Flow를 통해 메인화면에 즉시 반영됨!
|
||||||
|
refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +77,7 @@ class MainViewModel @Inject constructor(
|
|||||||
siteName = site.name,
|
siteName = site.name,
|
||||||
boardName = board.id,
|
boardName = board.id,
|
||||||
displayName = "${site.displayName} - ${board.displayName}",
|
displayName = "${site.displayName} - ${board.displayName}",
|
||||||
isEnabled = true // 기본값: 활성화 (설치 즉시 수집 작동)
|
isEnabled = true // 기본 활성화 (설치 즉시 수집 가동)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +95,6 @@ class MainViewModel @Inject constructor(
|
|||||||
keywordDao.observeAllKeywords(),
|
keywordDao.observeAllKeywords(),
|
||||||
appSettings.keywordOrder
|
appSettings.keywordOrder
|
||||||
) { deals, configs, keywords, keywordOrder ->
|
) { deals, configs, keywords, keywordOrder ->
|
||||||
// 사용자가 설정한 키워드 순서에 따라 정렬
|
|
||||||
val domainKeywords = keywords.map { it.toDomain() }
|
val domainKeywords = keywords.map { it.toDomain() }
|
||||||
val sortedKeywords = if (keywordOrder.isEmpty()) {
|
val sortedKeywords = if (keywordOrder.isEmpty()) {
|
||||||
domainKeywords
|
domainKeywords
|
||||||
@@ -112,12 +118,14 @@ class MainViewModel @Inject constructor(
|
|||||||
fun toggleSiteConfig(siteBoardKey: String, enabled: Boolean) {
|
fun toggleSiteConfig(siteBoardKey: String, enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
siteConfigDao.updateEnabled(siteBoardKey, enabled)
|
siteConfigDao.updateEnabled(siteBoardKey, enabled)
|
||||||
|
// 사이트 활성화 변경 시 즉시 새 핫딜 갱신
|
||||||
|
if (enabled) {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reorderSites(fromIndex: Int, toIndex: Int) {
|
fun reorderSites(fromIndex: Int, toIndex: Int) {
|
||||||
// siteOrder는 appSettings.siteOrder에서 stateIn으로 가져오므로
|
|
||||||
// appSettings를 즉시 저장하고 Flow가 업데이트되도록 함
|
|
||||||
val current = siteOrder.value.toMutableList()
|
val current = siteOrder.value.toMutableList()
|
||||||
if (fromIndex in current.indices && toIndex in current.indices) {
|
if (fromIndex in current.indices && toIndex in current.indices) {
|
||||||
val item = current.removeAt(fromIndex)
|
val item = current.removeAt(fromIndex)
|
||||||
@@ -140,6 +148,8 @@ class MainViewModel @Inject constructor(
|
|||||||
val currentOrder = appSettings.keywordOrder.first().toMutableList()
|
val currentOrder = appSettings.keywordOrder.first().toMutableList()
|
||||||
currentOrder.add(0, newId)
|
currentOrder.add(0, newId)
|
||||||
appSettings.setKeywordOrder(currentOrder)
|
appSettings.setKeywordOrder(currentOrder)
|
||||||
|
// 키워드 등록 후 매칭 검사를 위해 즉시 갱신
|
||||||
|
refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,19 +165,19 @@ class MainViewModel @Inject constructor(
|
|||||||
fun toggleKeyword(id: Long, enabled: Boolean) {
|
fun toggleKeyword(id: Long, enabled: Boolean) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
keywordDao.updateEnabled(id, enabled)
|
keywordDao.updateEnabled(id, enabled)
|
||||||
|
if (enabled) {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reorderKeywords(fromIndex: Int, toIndex: Int) {
|
fun reorderKeywords(fromIndex: Int, toIndex: Int) {
|
||||||
// uiState를 동기적으로 즉시 업데이트하여 빠른 드래그 시 stale 데이터 방지
|
|
||||||
val state = _uiState.value as? MainUiState.Success ?: return
|
val state = _uiState.value as? MainUiState.Success ?: return
|
||||||
val current = state.keywords.toMutableList()
|
val current = state.keywords.toMutableList()
|
||||||
if (fromIndex in current.indices && toIndex in current.indices) {
|
if (fromIndex in current.indices && toIndex in current.indices) {
|
||||||
val item = current.removeAt(fromIndex)
|
val item = current.removeAt(fromIndex)
|
||||||
current.add(toIndex, item)
|
current.add(toIndex, item)
|
||||||
// UI 즉시 갱신
|
|
||||||
_uiState.value = state.copy(keywords = current)
|
_uiState.value = state.copy(keywords = current)
|
||||||
// DataStore 비동기 영속화
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
appSettings.setKeywordOrder(current.map { it.id })
|
appSettings.setKeywordOrder(current.map { it.id })
|
||||||
}
|
}
|
||||||
@@ -186,8 +196,20 @@ class MainViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 직접 즉시 스크래핑 실행 (WorkManager 큐 대기 지연 없이 UI 및 DB 즉시 갱신)
|
||||||
|
*/
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
workerScheduler.executeOnce()
|
viewModelScope.launch {
|
||||||
|
_isRefreshing.value = true
|
||||||
|
try {
|
||||||
|
scraperManager.fetchAndProcessDeals()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 에러 발생 시 무시 (UI는 DB Flow로 유지)
|
||||||
|
} finally {
|
||||||
|
_isRefreshing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startPolling(intervalMinutes: Long = WorkerScheduler.DEFAULT_INTERVAL_MINUTES) {
|
fun startPolling(intervalMinutes: Long = WorkerScheduler.DEFAULT_INTERVAL_MINUTES) {
|
||||||
|
|||||||
Reference in new issue
Block a user