feat(gift): 보낸 선물 상세를 추가한다
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
enum GiftDetailRenderState {
|
||||
case sent(SentGiftDetailDisplayModel)
|
||||
case received(ReceivedGiftDetailDisplayModel)
|
||||
case canceled(SentGiftDetailDisplayModel)
|
||||
|
||||
init?(detail: GiftDetailResponse) {
|
||||
switch GiftDirection(rawValue: detail.direction) {
|
||||
case .sent:
|
||||
guard let model = GiftDisplayPolicy.sentDetail(from: detail) else { return nil }
|
||||
self = model.status == .canceled ? .canceled(model) : .sent(model)
|
||||
case .received:
|
||||
guard let model = GiftDisplayPolicy.receivedDetail(from: detail) else { return nil }
|
||||
self = .received(model)
|
||||
case nil:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class GiftDetailViewModel: ObservableObject {
|
||||
private let applicationNo: String
|
||||
private let onCanceled: (String, @escaping (Bool) -> Void) -> Void
|
||||
private let repository = GiftRepository()
|
||||
private var subscription: AnyCancellable?
|
||||
private var cancelSubscription: AnyCancellable?
|
||||
private var hasCompletedCancellation = false
|
||||
|
||||
@Published private(set) var renderState: GiftDetailRenderState?
|
||||
@Published private(set) var isLoading = false
|
||||
@Published private(set) var hasLoaded = false
|
||||
@Published private(set) var isShowingCancelDialog = false
|
||||
@Published private(set) var isCancelSubmitting = false
|
||||
@Published private(set) var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
|
||||
init(
|
||||
applicationNo: String,
|
||||
onCanceled: @escaping (String, @escaping (Bool) -> Void) -> Void = { _, completion in
|
||||
completion(true)
|
||||
}
|
||||
) {
|
||||
self.applicationNo = applicationNo
|
||||
self.onCanceled = onCanceled
|
||||
}
|
||||
|
||||
func presentCancelDialog() {
|
||||
guard !isLoading, !isCancelSubmitting, !hasCompletedCancellation, canCancel else { return }
|
||||
|
||||
errorMessage = ""
|
||||
isShowPopup = false
|
||||
isShowingCancelDialog = true
|
||||
}
|
||||
|
||||
func dismissCancelDialog() {
|
||||
guard !isCancelSubmitting else { return }
|
||||
|
||||
isShowingCancelDialog = false
|
||||
errorMessage = ""
|
||||
isShowPopup = false
|
||||
}
|
||||
|
||||
func confirmCancel() {
|
||||
guard isShowingCancelDialog, !isLoading, !isCancelSubmitting, !hasCompletedCancellation, canCancel else { return }
|
||||
|
||||
isCancelSubmitting = true
|
||||
errorMessage = ""
|
||||
isShowPopup = false
|
||||
|
||||
cancelSubscription = repository.cancelGift(applicationNo: applicationNo)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self, !self.hasCompletedCancellation else { return }
|
||||
if case .failure(let error) = result {
|
||||
self.showCancelError(data: error.response?.data)
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self, !self.hasCompletedCancellation else { return }
|
||||
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data)
|
||||
guard decoded.success else {
|
||||
self.showCancelError(data: response.data)
|
||||
return
|
||||
}
|
||||
|
||||
self.hasCompletedCancellation = true
|
||||
self.isShowingCancelDialog = false
|
||||
self.onCanceled(self.applicationNo) { [weak self] didReplace in
|
||||
guard let self, !didReplace else { return }
|
||||
self.isCancelSubmitting = false
|
||||
self.fetch()
|
||||
}
|
||||
} catch {
|
||||
self.showCancelError(data: response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetch() {
|
||||
guard !isLoading, !isShowingCancelDialog, !isCancelSubmitting else { return }
|
||||
isLoading = true
|
||||
errorMessage = ""
|
||||
isShowPopup = false
|
||||
|
||||
subscription = repository.getGift(applicationNo: applicationNo)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self else { return }
|
||||
if case .failure(let error) = result {
|
||||
self.showError(data: error.response?.data)
|
||||
}
|
||||
self.isLoading = false
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self else { return }
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponse<GiftDetailResponse>.self, from: response.data)
|
||||
guard decoded.success, let detail = decoded.data else {
|
||||
self.showError(data: response.data)
|
||||
return
|
||||
}
|
||||
guard let state = GiftDetailRenderState(detail: detail) else {
|
||||
self.showError(data: response.data)
|
||||
return
|
||||
}
|
||||
self.renderState = state
|
||||
self.hasLoaded = true
|
||||
} catch {
|
||||
self.showError(data: response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var canCancel: Bool {
|
||||
guard case .sent(let detail) = renderState else { return false }
|
||||
return detail.status == .received
|
||||
}
|
||||
|
||||
private func showCancelError(data: Data?) {
|
||||
isCancelSubmitting = false
|
||||
showError(data: data)
|
||||
}
|
||||
|
||||
private func showError(data: Data?) {
|
||||
struct ErrorResponse: Decodable {
|
||||
let message: String?
|
||||
}
|
||||
|
||||
let decoded = data.flatMap { try? JSONDecoder().decode(ErrorResponse.self, from: $0) }
|
||||
let message = decoded?.message?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||
errorMessage = message.isEmpty ? I18n.Common.commonError : message
|
||||
isShowPopup = true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user