From 0fecdea900db3ab74de8d2a85053ba3b43fb76d5 Mon Sep 17 00:00:00 2001 From: devfeijoa Date: Sat, 14 Feb 2026 11:33:40 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=94=BC=EB=93=9C=20=EC=A0=95=EB=A0=AC?= =?UTF-8?q?=20=EB=B0=8F=20=ED=95=84=ED=84=B0=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=8B=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=EC=83=88=EB=A1=9C=EA=B3=A0=EC=B9=A8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/feed/UpdatedFeedViewModel.kt | 292 ++++++++++-------- 1 file changed, 164 insertions(+), 128 deletions(-) diff --git a/feature/feed/src/main/java/com/into/websoso/feature/feed/UpdatedFeedViewModel.kt b/feature/feed/src/main/java/com/into/websoso/feature/feed/UpdatedFeedViewModel.kt index 8615bd8e0..6c2a11876 100644 --- a/feature/feed/src/main/java/com/into/websoso/feature/feed/UpdatedFeedViewModel.kt +++ b/feature/feed/src/main/java/com/into/websoso/feature/feed/UpdatedFeedViewModel.kt @@ -19,160 +19,196 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class UpdatedFeedViewModel @Inject constructor( - private val getFeedsUseCase: UpdatedGetFeedsUseCase, - private val getMyFeedsUseCase: UpdatedGetMyFeedsUseCase, - private val feedRepository: UpdatedFeedRepository, -) : ViewModel() { - - private val _uiState = MutableStateFlow(FeedUiState()) - val uiState = _uiState.asStateFlow() - - init { - collectFeedFlows() - fetchNextPage() - } +class UpdatedFeedViewModel + @Inject + constructor( + private val getFeedsUseCase: UpdatedGetFeedsUseCase, + private val getMyFeedsUseCase: UpdatedGetMyFeedsUseCase, + private val feedRepository: UpdatedFeedRepository, + ) : ViewModel() { + private val _uiState = MutableStateFlow(FeedUiState()) + val uiState = _uiState.asStateFlow() + + init { + collectFeedFlows() + fetchNextPage() + } - private fun collectFeedFlows() { - viewModelScope.launch { - getFeedsUseCase.sosoAllFlow.collect { feeds -> - val uiFeeds = feeds.map { it.toFeedUiModel() }.toImmutableList() - _uiState.update { state -> - val updatedAllData = state.sosoAllData.copy(feeds = uiFeeds) - if (state.selectedTab == FeedTab.SOSO_FEED && state.sosoCategory == SosoFeedType.ALL) { - state.copy(sosoAllData = updatedAllData).updateCurrentSource(updatedAllData) - } else { - state.copy(sosoAllData = updatedAllData) + private fun collectFeedFlows() { + viewModelScope.launch { + getFeedsUseCase.sosoAllFlow.collect { feeds -> + val uiFeeds = feeds.map { it.toFeedUiModel() }.toImmutableList() + _uiState.update { state -> + val updatedAllData = state.sosoAllData.copy(feeds = uiFeeds) + if (state.selectedTab == FeedTab.SOSO_FEED && state.sosoCategory == SosoFeedType.ALL) { + state.copy(sosoAllData = updatedAllData).updateCurrentSource(updatedAllData) + } else { + state.copy(sosoAllData = updatedAllData) + } } } } - } - viewModelScope.launch { - getFeedsUseCase.sosoRecommendedFlow.collect { feeds -> - val uiFeeds = feeds.map { it.toFeedUiModel() }.toImmutableList() - _uiState.update { state -> - val updatedRecData = state.sosoRecommendationData.copy(feeds = uiFeeds) - if (state.selectedTab == FeedTab.SOSO_FEED && state.sosoCategory == SosoFeedType.RECOMMENDED) { - state.copy(sosoRecommendationData = updatedRecData) - .updateCurrentSource(updatedRecData) - } else { - state.copy(sosoRecommendationData = updatedRecData) + viewModelScope.launch { + getFeedsUseCase.sosoRecommendedFlow.collect { feeds -> + val uiFeeds = feeds.map { it.toFeedUiModel() }.toImmutableList() + _uiState.update { state -> + val updatedRecData = state.sosoRecommendationData.copy(feeds = uiFeeds) + if (state.selectedTab == FeedTab.SOSO_FEED && state.sosoCategory == SosoFeedType.RECOMMENDED) { + state + .copy(sosoRecommendationData = updatedRecData) + .updateCurrentSource(updatedRecData) + } else { + state.copy(sosoRecommendationData = updatedRecData) + } } } } - } - viewModelScope.launch { - getMyFeedsUseCase.myFeedsFlow.collect { feeds -> - val uiFeeds = feeds.map { it.toFeedUiModel() }.toImmutableList() - _uiState.update { state -> - val updatedMyData = state.myFeedData.copy(feeds = uiFeeds) - if (state.selectedTab == FeedTab.MY_FEED) { - state.copy(myFeedData = updatedMyData).updateCurrentSource(updatedMyData) - } else { - state.copy(myFeedData = updatedMyData) + viewModelScope.launch { + getMyFeedsUseCase.myFeedsFlow.collect { feeds -> + val uiFeeds = feeds.map { it.toFeedUiModel() }.toImmutableList() + _uiState.update { state -> + val updatedMyData = state.myFeedData.copy(feeds = uiFeeds) + if (state.selectedTab == FeedTab.MY_FEED) { + state.copy(myFeedData = updatedMyData).updateCurrentSource(updatedMyData) + } else { + state.copy(myFeedData = updatedMyData) + } } } } } - } - - fun fetchNextPage(feedId: Long? = null) { - val state = uiState.value - val current = state.currentData - val lastFeedId = feedId ?: current.lastId - - if (state.loading || (!current.isLoadable && lastFeedId != 0L)) return - - _uiState.update { it.copy(loading = true) } - - viewModelScope.launch { - runCatching { - when (state.selectedTab) { - FeedTab.MY_FEED -> { - getMyFeedsUseCase( - lastFeedId = lastFeedId, - genres = state.currentFilter.selectedGenres.map { it.tag }, - isVisible = state.currentFilter.isVisible, - sortCriteria = state.myFeedData.sort.name.uppercase(), - isUnVisible = state.currentFilter.isUnVisible, + fun fetchNextPage(feedId: Long? = null) { + val state = uiState.value + val current = state.currentData + val lastFeedId = feedId ?: current.lastId + + if (state.loading || (!current.isLoadable && lastFeedId != 0L)) return + + _uiState.update { it.copy(loading = true) } + + viewModelScope.launch { + runCatching { + when (state.selectedTab) { + FeedTab.MY_FEED -> { + getMyFeedsUseCase( + lastFeedId = lastFeedId, + genres = state.currentFilter.selectedGenres.map { it.tag }, + isVisible = state.currentFilter.isVisible, + sortCriteria = state.myFeedData.sort.name + .uppercase(), + isUnVisible = state.currentFilter.isUnVisible, + ) + } + + FeedTab.SOSO_FEED -> { + getFeedsUseCase( + feedsOption = state.sosoCategory.name.uppercase(), + lastFeedId = lastFeedId, + ) + } + } + }.onSuccess { result -> + _uiState.update { currentState -> + val updatedSource = currentState.currentData.copy( + lastId = result.isLoadable.let { + if (it) result.feeds.lastOrNull()?.id ?: 0 else 0 + }, + isLoadable = result.isLoadable, ) + currentState + .updateCurrentSource(updatedSource) + .copy(loading = false, isRefreshing = false) } - - FeedTab.SOSO_FEED -> getFeedsUseCase( - feedsOption = state.sosoCategory.name.uppercase(), - lastFeedId = lastFeedId, - ) + }.onFailure { + _uiState.update { it.copy(loading = false, isRefreshing = false, error = true) } } - }.onSuccess { result -> - _uiState.update { currentState -> - val updatedSource = currentState.currentData.copy( - lastId = result.isLoadable.let { - if (it) result.feeds.lastOrNull()?.id ?: 0 else 0 - }, - isLoadable = result.isLoadable, - ) - currentState.updateCurrentSource(updatedSource) - .copy(loading = false, isRefreshing = false) - } - }.onFailure { - _uiState.update { it.copy(loading = false, isRefreshing = false, error = true) } } } - } - /** - * [새로고침] 기존 데이터를 지우지 않고 isRefreshing만 켠 후 재요청 - * 데이터 교체는 Repository가 Flow를 방출할 때 자연스럽게 이루어짐 - */ - fun refresh() { - _uiState.update { it.copy(isRefreshing = true) } - fetchNextPage(feedId = 0L) - } + /** + * [새로고침] 기존 데이터를 지우지 않고 isRefreshing만 켠 후 재요청 + * 데이터 교체는 Repository가 Flow를 방출할 때 자연스럽게 이루어짐 + */ + fun refresh() { + _uiState.update { it.copy(isRefreshing = true) } + fetchNextPage(feedId = 0L) + } - fun updateLike(selectedFeedId: Long) = feedRepository.toggleLikeLocal(selectedFeedId) + fun updateLike(selectedFeedId: Long) = feedRepository.toggleLikeLocal(selectedFeedId) - fun updateRemovedFeed(feedId: Long) { - _uiState.update { it.copy(loading = true) } - viewModelScope.launch { - feedRepository.saveRemovedFeed(feedId) - _uiState.update { it.copy(loading = false) } + fun updateRemovedFeed(feedId: Long) { + _uiState.update { it.copy(loading = true) } + viewModelScope.launch { + feedRepository.saveRemovedFeed(feedId) + _uiState.update { it.copy(loading = false) } + } } - } - fun updateReportedSpoilerFeed(feedId: Long) { - viewModelScope.launch { feedRepository.saveSpoilerFeed(feedId) } - } + fun updateReportedSpoilerFeed(feedId: Long) { + viewModelScope.launch { feedRepository.saveSpoilerFeed(feedId) } + } - fun updateReportedImpertinenceFeed(feedId: Long) { - viewModelScope.launch { feedRepository.saveImpertinenceFeed(feedId) } - } + fun updateReportedImpertinenceFeed(feedId: Long) { + viewModelScope.launch { feedRepository.saveImpertinenceFeed(feedId) } + } - override fun onCleared() { - super.onCleared() - feedRepository.syncDirtyFeeds() - } + override fun onCleared() { + super.onCleared() + feedRepository.syncDirtyFeeds() + } - // --- 기타 탭/필터 로직 --- - fun updateMyFeedSort(sort: FeedOrder) { - if (uiState.value.myFeedData.sort == sort) return - _uiState.update { it.copy(myFeedData = FeedSourceData(sort = sort)) } - fetchNextPage(feedId = 0L) - } + // --- 기타 탭/필터 로직 --- + fun updateMyFeedSort(sort: FeedOrder) { + if (uiState.value.myFeedData.sort == sort) return + + _uiState.update { state -> + val resetMyData = state.myFeedData.copy( + lastId = 0L, + isLoadable = true, + sort = sort, + ) + state.copy( + myFeedData = resetMyData, + isRefreshing = true, + error = false, + ) + } + fetchNextPage(feedId = 0L) + } - fun updateTab(tab: FeedTab) { - _uiState.update { it.copy(selectedTab = tab) } - if (uiState.value.currentData.feeds.isEmpty()) fetchNextPage(feedId = 0L) - } + fun updateTab(tab: FeedTab) { + _uiState.update { it.copy(selectedTab = tab) } + if (uiState.value.currentData.feeds + .isEmpty() + ) { + fetchNextPage(feedId = 0L) + } + } - fun updateSosoCategory(category: SosoFeedType) { - if (uiState.value.sosoCategory == category) return - _uiState.update { it.copy(sosoCategory = category) } - if (uiState.value.currentData.feeds.isEmpty()) fetchNextPage(feedId = 0L) - } + fun updateSosoCategory(category: SosoFeedType) { + if (uiState.value.sosoCategory == category) return + _uiState.update { it.copy(sosoCategory = category) } + if (uiState.value.currentData.feeds + .isEmpty() + ) { + fetchNextPage(feedId = 0L) + } + } - fun applyMyFilter(filter: MyFeedFilter) { - _uiState.update { it.copy(currentFilter = filter, myFeedData = FeedSourceData()) } - fetchNextPage(feedId = 0L) + fun applyMyFilter(filter: MyFeedFilter) { + _uiState.update { state -> + val resetMyData = state.myFeedData.copy( + lastId = 0L, + isLoadable = true, + ) + state.copy( + currentFilter = filter, + myFeedData = resetMyData, + isRefreshing = true, + error = false, + ) + } + fetchNextPage(feedId = 0L) + } } -}