From 557a4421e723c7c15da9a0d95264d6c51c76df3d Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Fri, 5 Sep 2025 17:59:26 +0900 Subject: [PATCH] =?UTF-8?q?feat(character-gallery):=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=EB=A7=A4=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Chat/Character/CharacterApi.swift | 17 +++++- .../CharacterDetailGalleryRepository.swift | 4 ++ .../CharacterDetailGalleryViewModel.swift | 55 ++++++++++++++++++- .../Gallery/CharacterImageListResponse.swift | 4 +- .../CharacterImagePurchaseRequest.swift | 11 ++++ .../CharacterImagePurchaseResponse.swift | 10 ++++ 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseRequest.swift create mode 100644 SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseResponse.swift diff --git a/SodaLive/Sources/Chat/Character/CharacterApi.swift b/SodaLive/Sources/Chat/Character/CharacterApi.swift index a4724f1..b55b749 100644 --- a/SodaLive/Sources/Chat/Character/CharacterApi.swift +++ b/SodaLive/Sources/Chat/Character/CharacterApi.swift @@ -13,6 +13,7 @@ enum CharacterApi { case getCharacterDetail(characterId: Int) case getCharacterImageList(characterId: Int, page: Int, size: Int) case getMyCharacterImageList(characterId: Int64, page: Int, size: Int) + case purchaseCharacterImage(imageId: Int) } extension CharacterApi: TargetType { @@ -31,10 +32,21 @@ extension CharacterApi: TargetType { case .getMyCharacterImageList: return "/api/chat/character/image/list" + + case .purchaseCharacterImage: + return "/api/chat/character/image/purchase" } } - var method: Moya.Method { .get } + var method: Moya.Method { + switch self { + case .purchaseCharacterImage: + return .post + + default: + return .get + } + } var task: Moya.Task { switch self { @@ -60,6 +72,9 @@ extension CharacterApi: TargetType { ], encoding: URLEncoding.queryString ) + + case .purchaseCharacterImage(let imageId): + return .requestJSONEncodable(CharacterImagePurchaseRequest(imageId: imageId)) } } diff --git a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryRepository.swift b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryRepository.swift index d20387a..ba2014c 100644 --- a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryRepository.swift +++ b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryRepository.swift @@ -40,4 +40,8 @@ final class CharacterDetailGalleryRepository { ) ) } + + func purchaseCharacterImage(imageId: Int) -> AnyPublisher { + return characterApi.requestPublisher(.purchaseCharacterImage(imageId: imageId)) + } } diff --git a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryViewModel.swift b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryViewModel.swift index 7cf9a99..0632c26 100644 --- a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryViewModel.swift +++ b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterDetailGalleryViewModel.swift @@ -73,14 +73,65 @@ final class CharacterDetailGalleryViewModel: ObservableObject { isShowImageViewer = true } else { // 미소유 이미지 - 구매 다이얼로그 표시 + selectedImageIndex = index isShowPurchaseDialog = true } } func onPurchaseConfirm() { - // TODO: 구매 API 연동 구현 예정 - print("구매 확인: \(selectedItem?.id ?? 0)") isShowPurchaseDialog = false + if isLoading { + return + } + + if let imageItem = selectedItem { + isLoading = true + repository.purchaseCharacterImage(imageId: imageItem.id) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { [unowned self] response in + let responseData = response.data + self.isLoading = false + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if let data = decoded.data, decoded.success { + var copyGalleryItems = self.galleryItems + copyGalleryItems[self.selectedImageIndex].imageUrl = data.imageUrl + copyGalleryItems[self.selectedImageIndex].isOwned = true + self.galleryItems = copyGalleryItems + self.ownedCount += 1 + } else { + if let message = decoded.message { + self.errorMessage = message + } else { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + } + + self.isShowPopup = true + } + + self.selectedImageIndex = 0 + self.selectedItem = nil + } catch { + self.selectedImageIndex = 0 + self.selectedItem = nil + ERROR_LOG(String(describing: error)) + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.isShowPopup = true + } + } + .store(in: &subscription) + } else { + isShowPurchaseDialog = false + } } func onPurchaseCancel() { diff --git a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImageListResponse.swift b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImageListResponse.swift index 54ab0f9..1a319ff 100644 --- a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImageListResponse.swift +++ b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImageListResponse.swift @@ -13,7 +13,7 @@ struct CharacterImageListResponse: Decodable { struct CharacterImageListItemResponse: Decodable, Hashable { let id: Int - let imageUrl: String - let isOwned: Bool + var imageUrl: String + var isOwned: Bool let imagePriceCan: Int } diff --git a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseRequest.swift b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseRequest.swift new file mode 100644 index 0000000..30fbaa6 --- /dev/null +++ b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseRequest.swift @@ -0,0 +1,11 @@ +// +// CharacterImagePurchaseRequest.swift +// SodaLive +// +// Created by klaus on 9/5/25. +// + +struct CharacterImagePurchaseRequest: Encodable { + let imageId: Int + let container: String = "ios" +} diff --git a/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseResponse.swift b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseResponse.swift new file mode 100644 index 0000000..9c90a71 --- /dev/null +++ b/SodaLive/Sources/Chat/Character/Detail/Gallery/CharacterImagePurchaseResponse.swift @@ -0,0 +1,10 @@ +// +// CharacterImagePurchaseResponse.swift +// SodaLive +// +// Created by klaus on 9/5/25. +// + +struct CharacterImagePurchaseResponse: Decodable { + let imageUrl: String +}