v0.2.8: Site & Keyword Reordering Support & Slim Bottom Bar Margins
This commit is contained in:
@@ -5,15 +5,15 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.hotdeal.alarm.data.local.db.dao.HotDealDao
|
||||
import com.hotdeal.alarm.data.local.db.dao.KeywordDao
|
||||
import com.hotdeal.alarm.data.local.db.dao.SiteConfigDao
|
||||
import com.hotdeal.alarm.data.local.db.entity.KeywordEntity
|
||||
import com.hotdeal.alarm.data.local.db.entity.SiteConfigEntity
|
||||
import com.hotdeal.alarm.data.local.preferences.AppSettings
|
||||
import com.hotdeal.alarm.domain.model.Keyword
|
||||
import com.hotdeal.alarm.domain.model.SiteType
|
||||
import com.hotdeal.alarm.worker.WorkerScheduler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -24,28 +24,34 @@ class MainViewModel @Inject constructor(
|
||||
private val workerScheduler: WorkerScheduler,
|
||||
private val appSettings: AppSettings
|
||||
) : ViewModel() {
|
||||
|
||||
|
||||
private val _uiState = MutableStateFlow<MainUiState>(MainUiState.Loading)
|
||||
val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()
|
||||
|
||||
// 폴링 주기 (저장된 값 즉시 반영)
|
||||
|
||||
// 폴링 주기
|
||||
val pollingInterval: StateFlow<Int> = appSettings.pollingInterval
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, 2)
|
||||
|
||||
|
||||
// 사이트 표시 순서
|
||||
val siteOrder: StateFlow<List<SiteType>> = appSettings.siteOrder
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, SiteType.entries)
|
||||
|
||||
private val _toastEvent = MutableSharedFlow<String>()
|
||||
val toastEvent: SharedFlow<String> = _toastEvent.asSharedFlow()
|
||||
|
||||
init {
|
||||
initializeApp()
|
||||
}
|
||||
|
||||
|
||||
private fun initializeApp() {
|
||||
viewModelScope.launch {
|
||||
initializeDefaultSiteConfigs()
|
||||
loadState()
|
||||
// 저장된 폴� 주기로 시작
|
||||
val savedInterval = appSettings.pollingInterval.first()
|
||||
startPolling(savedInterval.toLong())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun initializeDefaultSiteConfigs() {
|
||||
val existingConfigs = siteConfigDao.getAllConfigs()
|
||||
val existingKeys = existingConfigs.map { it.siteBoardKey }.toSet()
|
||||
@@ -67,17 +73,27 @@ class MainViewModel @Inject constructor(
|
||||
siteConfigDao.insertConfigs(missingConfigs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun loadState() {
|
||||
combine(
|
||||
hotDealDao.observeAllDeals(),
|
||||
siteConfigDao.observeAllConfigs(),
|
||||
keywordDao.observeAllKeywords()
|
||||
) { deals, configs, keywords ->
|
||||
keywordDao.observeAllKeywords(),
|
||||
appSettings.keywordOrder
|
||||
) { deals, configs, keywords, keywordOrder ->
|
||||
// 사용자가 설정한 키워드 순서에 따라 정렬
|
||||
val domainKeywords = keywords.map { it.toDomain() }
|
||||
val sortedKeywords = if (keywordOrder.isEmpty()) {
|
||||
domainKeywords
|
||||
} else {
|
||||
val orderMap = keywordOrder.withIndex().associate { it.value to it.index }
|
||||
domainKeywords.sortedBy { orderMap[it.id] ?: Int.MAX_VALUE }
|
||||
}
|
||||
|
||||
MainUiState.Success(
|
||||
deals = deals.map { it.toDomain() },
|
||||
siteConfigs = configs.map { it.toDomain() },
|
||||
keywords = keywords.map { it.toDomain() }
|
||||
keywords = sortedKeywords
|
||||
)
|
||||
}.catch { e ->
|
||||
_uiState.value = MainUiState.Error(e.message ?: "Unknown error")
|
||||
@@ -85,87 +101,108 @@ class MainViewModel @Inject constructor(
|
||||
_uiState.value = state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun toggleSiteConfig(siteBoardKey: String, enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
siteConfigDao.updateEnabled(siteBoardKey, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun reorderSites(fromIndex: Int, toIndex: Int) {
|
||||
viewModelScope.launch {
|
||||
val current = siteOrder.value.toMutableList()
|
||||
if (fromIndex in current.indices && toIndex in current.indices) {
|
||||
val item = current.removeAt(fromIndex)
|
||||
current.add(toIndex, item)
|
||||
appSettings.setSiteOrder(current)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addKeyword(keyword: String) {
|
||||
if (keyword.isBlank()) return
|
||||
viewModelScope.launch {
|
||||
keywordDao.insertKeyword(
|
||||
com.hotdeal.alarm.data.local.db.entity.KeywordEntity(
|
||||
val newId = keywordDao.insertKeyword(
|
||||
KeywordEntity(
|
||||
keyword = keyword.trim(),
|
||||
isEnabled = true
|
||||
)
|
||||
)
|
||||
// 키워드 순서 맨 앞에 추가
|
||||
val currentOrder = appSettings.keywordOrder.first().toMutableList()
|
||||
currentOrder.add(0, newId)
|
||||
appSettings.setKeywordOrder(currentOrder)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun deleteKeyword(id: Long) {
|
||||
viewModelScope.launch {
|
||||
keywordDao.deleteKeywordById(id)
|
||||
val currentOrder = appSettings.keywordOrder.first().toMutableList()
|
||||
currentOrder.remove(id)
|
||||
appSettings.setKeywordOrder(currentOrder)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleKeyword(id: Long, enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
keywordDao.updateEnabled(id, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 즐겨찾기 토글
|
||||
*/
|
||||
fun toggleFavorite(dealId: String) {
|
||||
viewModelScope.launch {
|
||||
hotDealDao.toggleFavorite(dealId)
|
||||
}
|
||||
}
|
||||
fun toggleKeyword(id: Long, enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
keywordDao.updateEnabled(id, enabled)
|
||||
}
|
||||
}
|
||||
|
||||
fun reorderKeywords(fromIndex: Int, toIndex: Int) {
|
||||
val state = uiState.value as? MainUiState.Success ?: return
|
||||
viewModelScope.launch {
|
||||
val current = state.keywords.toMutableList()
|
||||
if (fromIndex in current.indices && toIndex in current.indices) {
|
||||
val item = current.removeAt(fromIndex)
|
||||
current.add(toIndex, item)
|
||||
appSettings.setKeywordOrder(current.map { it.id })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleFavorite(dealId: String) {
|
||||
viewModelScope.launch {
|
||||
hotDealDao.toggleFavorite(dealId)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFavorite(dealId: String, isFavorite: Boolean) {
|
||||
viewModelScope.launch {
|
||||
hotDealDao.setFavorite(dealId, isFavorite)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 즐겨찾기 설정
|
||||
*/
|
||||
fun setFavorite(dealId: String, isFavorite: Boolean) {
|
||||
viewModelScope.launch {
|
||||
hotDealDao.setFavorite(dealId, isFavorite)
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
workerScheduler.executeOnce()
|
||||
}
|
||||
|
||||
/**
|
||||
* 폴� 시작 (주기 저장)
|
||||
*/
|
||||
|
||||
fun startPolling(intervalMinutes: Long = WorkerScheduler.DEFAULT_INTERVAL_MINUTES) {
|
||||
viewModelScope.launch {
|
||||
appSettings.setPollingInterval(intervalMinutes.toInt())
|
||||
workerScheduler.schedulePeriodicPolling(intervalMinutes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun stopPolling() {
|
||||
workerScheduler.cancelPolling()
|
||||
}
|
||||
|
||||
// 데이터 파싱 핫딜 데이터 전체 삭제 및 사용자 피드백 트리거
|
||||
private val _toastEvent = MutableSharedFlow<String>(extraBufferCapacity = 1)
|
||||
val toastEvent = _toastEvent.asSharedFlow()
|
||||
|
||||
fun deleteAllParsedData() {
|
||||
viewModelScope.launch {
|
||||
hotDealDao.deleteAllDeals()
|
||||
_toastEvent.emit("파싱 데이터가 삭제되었습니다")
|
||||
try {
|
||||
hotDealDao.deleteAllDeals()
|
||||
_toastEvent.emit("모든 수집 데이터가 삭제되었습니다")
|
||||
} catch (e: Exception) {
|
||||
_toastEvent.emit("데이터 삭제 중 오류가 발생했습니다: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MainUiState {
|
||||
data object Loading : MainUiState()
|
||||
object Loading : MainUiState()
|
||||
data class Success(
|
||||
val deals: List<com.hotdeal.alarm.domain.model.HotDeal>,
|
||||
val siteConfigs: List<com.hotdeal.alarm.domain.model.SiteConfig>,
|
||||
|
||||
Reference in New Issue
Block a user