Files
sodalive-ios/SodaLive/Sources/V2/Gift/Detail/GiftDetailViewModel.swift
T

158 lines
5.7 KiB
Swift

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 else { return }
self.isCancelSubmitting = false
guard !didReplace else { return }
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
}
}