feat(gift): 운송장 등록을 추가한다
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class GiftTrackingRegistrationViewModel: ObservableObject {
|
||||
private let applicationNo: String
|
||||
private let onUnavailable: (String) -> Void
|
||||
private let onMutationCompleted: (String, @escaping (Bool) -> Void) -> Void
|
||||
private let repository = GiftRepository()
|
||||
private var subscription: AnyCancellable?
|
||||
private var mutationSubscription: AnyCancellable?
|
||||
private var hasCompletedMutation = false
|
||||
private var hasStartedMutationNavigation = false
|
||||
|
||||
@Published private(set) var summary: SentGiftDetailDisplayModel?
|
||||
@Published private(set) var mailbox: GiftAddressDisplayModel?
|
||||
@Published private(set) var isLoading = false
|
||||
@Published private(set) var isSubmitting = false
|
||||
@Published private(set) var errorMessage = ""
|
||||
@Published private(set) var successMessage = "운송장이 등록되었습니다."
|
||||
@Published private(set) var shouldReturnToDetail = false
|
||||
@Published var isShowPopup = false
|
||||
@Published var isShowSuccessToast = false
|
||||
@Published var selectedCourier: String?
|
||||
@Published var trackingNumber = ""
|
||||
|
||||
static func acceptsTrackingEditing(_ value: String) -> Bool {
|
||||
value.utf8.count <= 14 && value.utf8.allSatisfy { (48...57).contains($0) }
|
||||
}
|
||||
|
||||
static func acceptsTrackingSubmission(_ value: String) -> Bool {
|
||||
value.utf8.count >= 10 && acceptsTrackingEditing(value)
|
||||
}
|
||||
|
||||
var canRegister: Bool {
|
||||
mailbox != nil && !isLoading && !shouldReturnToDetail
|
||||
}
|
||||
|
||||
var canSubmit: Bool {
|
||||
guard let selectedCourier else { return false }
|
||||
return canRegister
|
||||
&& !isSubmitting
|
||||
&& !hasCompletedMutation
|
||||
&& !selectedCourier.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
&& Self.acceptsTrackingSubmission(trackingNumber)
|
||||
}
|
||||
|
||||
var canCancel: Bool {
|
||||
canRegister && summary?.status == .received && !isSubmitting && !hasCompletedMutation
|
||||
}
|
||||
|
||||
init(
|
||||
applicationNo: String,
|
||||
onUnavailable: @escaping (String) -> Void = { _ in }
|
||||
) {
|
||||
self.applicationNo = applicationNo
|
||||
self.onUnavailable = onUnavailable
|
||||
self.onMutationCompleted = { _, completion in
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
applicationNo: String,
|
||||
onUnavailable: @escaping (String) -> Void,
|
||||
onMutationCompleted: @escaping (String, @escaping (Bool) -> Void) -> Void
|
||||
) {
|
||||
self.applicationNo = applicationNo
|
||||
self.onUnavailable = onUnavailable
|
||||
self.onMutationCompleted = onMutationCompleted
|
||||
}
|
||||
|
||||
func fetch() {
|
||||
guard !isLoading, !isSubmitting, !shouldReturnToDetail else { return }
|
||||
|
||||
summary = nil
|
||||
mailbox = nil
|
||||
errorMessage = ""
|
||||
isShowPopup = false
|
||||
isLoading = true
|
||||
|
||||
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 sentDetail = GiftDisplayPolicy.sentDetail(from: detail) else {
|
||||
self.showUnavailable(data: response.data)
|
||||
return
|
||||
}
|
||||
guard sentDetail.status != .canceled else {
|
||||
self.showUnavailable(data: response.data)
|
||||
return
|
||||
}
|
||||
guard detail.trackingRequired else {
|
||||
self.showUnavailable(data: response.data)
|
||||
return
|
||||
}
|
||||
guard let mailbox = detail.mailbox else {
|
||||
self.showMissingMailboxError()
|
||||
return
|
||||
}
|
||||
|
||||
self.summary = sentDetail
|
||||
self.mailbox = GiftAddressDisplayModel(
|
||||
name: mailbox.name,
|
||||
phoneNumber: mailbox.phoneNumber,
|
||||
address: mailbox.address
|
||||
)
|
||||
} catch {
|
||||
self.showError(data: response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerTracking() {
|
||||
guard canSubmit, let selectedCourier else { return }
|
||||
|
||||
let request = RegisterGiftTrackingRequest(
|
||||
courierCompanyName: selectedCourier,
|
||||
trackingNumber: trackingNumber.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
)
|
||||
beginMutation()
|
||||
|
||||
mutationSubscription = repository.registerTracking(applicationNo: applicationNo, request: request)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self, !self.hasCompletedMutation else { return }
|
||||
if case .failure(let error) = result {
|
||||
self.finishMutationFailure(data: error.response?.data)
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self, !self.hasCompletedMutation else { return }
|
||||
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data)
|
||||
guard decoded.success else {
|
||||
self.finishMutationFailure(data: response.data)
|
||||
return
|
||||
}
|
||||
self.finishRegistrationSuccess()
|
||||
} catch {
|
||||
self.finishMutationFailure(data: response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func confirmCancel() {
|
||||
guard canCancel else { return }
|
||||
|
||||
beginMutation()
|
||||
|
||||
mutationSubscription = repository.cancelGift(applicationNo: applicationNo)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self, !self.hasCompletedMutation else { return }
|
||||
if case .failure(let error) = result {
|
||||
self.finishMutationFailure(data: error.response?.data)
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self, !self.hasCompletedMutation else { return }
|
||||
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponseWithoutData.self, from: response.data)
|
||||
guard decoded.success else {
|
||||
self.finishMutationFailure(data: response.data)
|
||||
return
|
||||
}
|
||||
self.finishCancellationSuccess()
|
||||
} catch {
|
||||
self.finishMutationFailure(data: response.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func successToastDidDismiss() {
|
||||
guard hasCompletedMutation, !isShowSuccessToast else { return }
|
||||
|
||||
navigateAfterMutation()
|
||||
}
|
||||
|
||||
func errorToastDidDismiss() {
|
||||
guard !isShowPopup, shouldReturnToDetail else { return }
|
||||
|
||||
shouldReturnToDetail = false
|
||||
onUnavailable(applicationNo)
|
||||
}
|
||||
|
||||
private func beginMutation() {
|
||||
isSubmitting = true
|
||||
errorMessage = ""
|
||||
isShowPopup = false
|
||||
}
|
||||
|
||||
private func finishRegistrationSuccess() {
|
||||
hasCompletedMutation = true
|
||||
isShowSuccessToast = true
|
||||
}
|
||||
|
||||
private func finishCancellationSuccess() {
|
||||
hasCompletedMutation = true
|
||||
navigateAfterMutation()
|
||||
}
|
||||
|
||||
private func finishMutationFailure(data: Data?) {
|
||||
isSubmitting = false
|
||||
showError(data: data)
|
||||
}
|
||||
|
||||
private func navigateAfterMutation() {
|
||||
guard hasCompletedMutation, !hasStartedMutationNavigation else { return }
|
||||
|
||||
hasStartedMutationNavigation = true
|
||||
onMutationCompleted(applicationNo) { [weak self] didReplace in
|
||||
guard let self, !didReplace, self.isSubmitting else { return }
|
||||
|
||||
self.isSubmitting = false
|
||||
self.fetch()
|
||||
}
|
||||
}
|
||||
|
||||
private func showUnavailable(data: Data?) {
|
||||
showError(data: data)
|
||||
shouldReturnToDetail = true
|
||||
}
|
||||
|
||||
private func showMissingMailboxError() {
|
||||
errorMessage = "받을 주소가 등록되어 있지 않습니다."
|
||||
isShowPopup = true
|
||||
}
|
||||
|
||||
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