feat(main): 홈 대화 탭을 추가한다

This commit is contained in:
Yu Sung
2026-07-08 15:54:07 +09:00
parent 403eee262f
commit 09443761af
13 changed files with 938 additions and 23 deletions

View File

@@ -0,0 +1,119 @@
import Foundation
import Combine
final class MainChatViewModel: ObservableObject {
private let repository = MainChatRepository()
private var subscription = Set<AnyCancellable>()
private var latestRequestId = 0
@Published var rooms = [MainChatRoomItem]()
@Published var selectedFilter: MainChatFilter = .all
@Published var isLoading = false
@Published var isLoadingNextPage = false
@Published var hasMore = false
@Published var nextCursor: String?
@Published var hasLoaded = false
@Published var errorMessage = ""
@Published var isShowPopup = false
var shouldShowEmptyState: Bool {
hasLoaded && isLoading == false && rooms.isEmpty
}
func fetchFirstPageIfNeeded() {
guard hasLoaded == false else { return }
fetchFirstPage(filter: selectedFilter)
}
func fetchFirstPage(filter: MainChatFilter) {
selectedFilter = filter
clearRooms()
fetchChatRooms(filter: filter, cursor: nil, isNextPage: false)
}
func applyFilter(_ filter: MainChatFilter) {
guard selectedFilter != filter else { return }
fetchFirstPage(filter: filter)
}
func fetchNextPageIfNeeded(currentItem: MainChatRoomItem) {
guard rooms.last?.roomId == currentItem.roomId else { return }
guard hasMore, let nextCursor, isLoading == false, isLoadingNextPage == false else { return }
fetchChatRooms(filter: selectedFilter, cursor: nextCursor, isNextPage: true)
}
private func fetchChatRooms(filter: MainChatFilter, cursor: String?, isNextPage: Bool) {
latestRequestId += 1
let requestId = latestRequestId
if isNextPage {
isLoadingNextPage = true
} else {
isLoading = true
}
repository.getChatRooms(filter: filter.queryValue, cursor: cursor)
.sink { [weak self] result in
guard let self else { return }
guard requestId == self.latestRequestId else { return }
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
self.applyFailureState()
}
} receiveValue: { [weak self] response in
guard let self else { return }
guard requestId == self.latestRequestId else { return }
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<MainChatRoomsResponse>.self, from: response.data)
if let data = decoded.data, decoded.success {
self.applySuccessState(data, isNextPage: isNextPage)
} else {
self.errorMessage = decoded.message ?? I18n.MainChat.loadFailedMessage
self.isShowPopup = true
}
} catch {
ERROR_LOG(error.localizedDescription)
self.errorMessage = I18n.MainChat.loadFailedMessage
self.isShowPopup = true
}
self.hasLoaded = true
self.isLoading = false
self.isLoadingNextPage = false
}
.store(in: &subscription)
}
private func applySuccessState(_ data: MainChatRoomsResponse, isNextPage: Bool) {
hasMore = data.hasMore
nextCursor = data.nextCursor
if isNextPage {
rooms.append(contentsOf: data.rooms)
} else {
rooms = data.rooms
}
}
private func applyFailureState() {
errorMessage = I18n.MainChat.loadFailedMessage
isShowPopup = true
hasLoaded = true
isLoading = false
isLoadingNextPage = false
}
private func clearRooms() {
rooms = []
hasMore = false
nextCursor = nil
hasLoaded = false
}
}