feat(event): ID 기반 이벤트 상세 API를 연동한다

This commit is contained in:
Yu Sung
2026-10-29 12:03:02 +09:00
parent d664448f38
commit 468a766242
15 changed files with 549 additions and 44 deletions
@@ -0,0 +1,114 @@
import Foundation
import Combine
final class EventDetailViewModel: ObservableObject {
enum State: Equatable {
case idle
case loading
case loaded(EventItem)
case unavailable
case error
}
@Published private(set) var state: State = .idle
@Published var isLoading = false
@Published var errorMessage = ""
@Published var isShowPopup = false
private let eventId: Int
private let repository = EventRepository()
private var subscription: AnyCancellable?
private var activeRequestID: UUID?
private var isUnavailableDismissalPending = false
init(eventId: Int) {
self.eventId = eventId
}
var participationURL: URL? {
guard case .loaded(let event) = state,
let link = event.link,
let url = URL(string: link),
let scheme = url.scheme?.lowercased(),
["http", "https"].contains(scheme),
let host = url.host, !host.isEmpty else { return nil }
return url
}
func load() {
guard state == .idle else { return }
guard eventId > 0 else {
showUnavailable()
return
}
isLoading = true
state = .loading
let requestID = UUID()
activeRequestID = requestID
subscription = repository.getEventDetail(id: eventId)
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
guard let self, self.activeRequestID == requestID else { return }
self.activeRequestID = nil
self.showError()
} receiveValue: { [weak self] response in
guard let self, self.activeRequestID == requestID else { return }
self.activeRequestID = nil
self.isLoading = false
guard response.statusCode == 200 else {
self.showError()
return
}
do {
let decoder = JSONDecoder()
let envelope = try decoder.decode(ApiResponseWithoutData.self, from: response.data)
guard envelope.success else {
self.showUnavailable()
return
}
let decoded = try decoder.decode(ApiResponse<EventItem>.self, from: response.data)
guard let event = decoded.data, event.id > 0, event.id == self.eventId else {
self.showError()
return
}
self.state = .loaded(event)
} catch {
self.showError()
}
}
}
func cancel() {
activeRequestID = nil
isUnavailableDismissalPending = false
subscription?.cancel()
subscription = nil
isLoading = false
if state == .loading {
state = .idle
}
}
func takeUnavailableDismissal() -> Bool {
guard isUnavailableDismissalPending else { return false }
isUnavailableDismissalPending = false
return true
}
private func showUnavailable() {
isLoading = false
isUnavailableDismissalPending = true
state = .unavailable
}
private func showError() {
isLoading = false
errorMessage = I18n.Common.commonError
isShowPopup = true
state = .error
}
}