투표기능 추가
This commit is contained in:
parent
a2b7fef39e
commit
7e13689763
|
@ -11,6 +11,7 @@ import Kingfisher
|
||||||
struct AuditionApplicantItemView: View {
|
struct AuditionApplicantItemView: View {
|
||||||
|
|
||||||
let item: GetAuditionRoleApplicantItem
|
let item: GetAuditionRoleApplicantItem
|
||||||
|
let onClickVote: (Int) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 5.3) {
|
VStack(spacing: 5.3) {
|
||||||
|
@ -47,6 +48,9 @@ struct AuditionApplicantItemView: View {
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color.gray77)
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
onClickVote(item.applicantId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, 18.7)
|
.padding(.vertical, 18.7)
|
||||||
|
|
||||||
|
@ -67,6 +71,7 @@ struct AuditionApplicantItemView: View {
|
||||||
profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
voiceUrl: "",
|
voiceUrl: "",
|
||||||
voteCount: 777
|
voteCount: 777
|
||||||
)
|
),
|
||||||
|
onClickVote: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,5 @@ struct GetAuditionRoleApplicantItem: Decodable {
|
||||||
let nickname: String
|
let nickname: String
|
||||||
let profileImageUrl: String
|
let profileImageUrl: String
|
||||||
let voiceUrl: String
|
let voiceUrl: String
|
||||||
let voteCount: Int
|
var voteCount: Int
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ enum AuditionApi {
|
||||||
case getAuditionRoleDetail(auditionRoleId: Int)
|
case getAuditionRoleDetail(auditionRoleId: Int)
|
||||||
case getAuditionApplicantList(auditionRoleId: Int, sortType: AuditionApplicantSortType, page: Int, size: Int)
|
case getAuditionApplicantList(auditionRoleId: Int, sortType: AuditionApplicantSortType, page: Int, size: Int)
|
||||||
case applyAudition(parameters: [MultipartFormData])
|
case applyAudition(parameters: [MultipartFormData])
|
||||||
|
case voteApplicant(applicantId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AuditionApi: TargetType {
|
extension AuditionApi: TargetType {
|
||||||
|
@ -37,6 +38,9 @@ extension AuditionApi: TargetType {
|
||||||
|
|
||||||
case .applyAudition:
|
case .applyAudition:
|
||||||
return "/audition/applicant"
|
return "/audition/applicant"
|
||||||
|
|
||||||
|
case .voteApplicant:
|
||||||
|
return "/audition/vote"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +50,7 @@ extension AuditionApi: TargetType {
|
||||||
case .getAuditionList, .getAuditionDetail, . getAuditionRoleDetail, .getAuditionApplicantList:
|
case .getAuditionList, .getAuditionDetail, . getAuditionRoleDetail, .getAuditionApplicantList:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .applyAudition:
|
case .applyAudition, .voteApplicant:
|
||||||
return .post
|
return .post
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,6 +81,10 @@ extension AuditionApi: TargetType {
|
||||||
|
|
||||||
case .applyAudition(let parameters):
|
case .applyAudition(let parameters):
|
||||||
return .uploadMultipart(parameters)
|
return .uploadMultipart(parameters)
|
||||||
|
|
||||||
|
case .voteApplicant(let applicantId):
|
||||||
|
let request = VoteAuditionApplicantRequest(applicantId: applicantId)
|
||||||
|
return .requestJSONEncodable(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,4 +39,8 @@ final class AuditionRepository {
|
||||||
func applyAudition(parameters: [MultipartFormData]) -> AnyPublisher<Response, MoyaError> {
|
func applyAudition(parameters: [MultipartFormData]) -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.applyAudition(parameters: parameters))
|
return api.requestPublisher(.applyAudition(parameters: parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func voteApplicant(applicantId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.voteApplicant(applicantId: applicantId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,8 +87,12 @@ struct AuditionRoleDetailView: View {
|
||||||
ForEach(0..<viewModel.applicantList.count, id: \.self) {
|
ForEach(0..<viewModel.applicantList.count, id: \.self) {
|
||||||
let applicant = viewModel.applicantList[$0]
|
let applicant = viewModel.applicantList[$0]
|
||||||
|
|
||||||
AuditionApplicantItemView(item: applicant)
|
AuditionApplicantItemView(
|
||||||
.padding(.bottom, $0 == viewModel.applicantList.count - 1 ? 33 : 0)
|
item: applicant,
|
||||||
|
onClickVote: {
|
||||||
|
viewModel.voteApplicant(applicantId: $0)
|
||||||
|
}
|
||||||
|
).padding(.bottom, $0 == viewModel.applicantList.count - 1 ? 33 : 0)
|
||||||
|
|
||||||
if $0 == viewModel.applicantList.count - 1 {
|
if $0 == viewModel.applicantList.count - 1 {
|
||||||
Color.clear
|
Color.clear
|
||||||
|
@ -193,6 +197,17 @@ struct AuditionRoleDetailView: View {
|
||||||
viewModel.deleteAllRecordingFilesWithNamePrefix("voiceon_now_voice")
|
viewModel.deleteAllRecordingFilesWithNamePrefix("voiceon_now_voice")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowSodaDialog {
|
||||||
|
SodaDialog(
|
||||||
|
title: viewModel.dialogTitle,
|
||||||
|
desc: viewModel.dialogDesc,
|
||||||
|
confirmButtonTitle: "확인"
|
||||||
|
) {
|
||||||
|
viewModel.isShowSodaDialog = false
|
||||||
|
viewModel.isShowNotifyVote = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,11 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||||
@Published var soundData: Data? = nil
|
@Published var soundData: Data? = nil
|
||||||
@Published var phoneNumber = ""
|
@Published var phoneNumber = ""
|
||||||
|
|
||||||
|
@Published var isShowNotifyVote = true
|
||||||
|
@Published var isShowSodaDialog = false
|
||||||
|
@Published var dialogTitle = ""
|
||||||
|
@Published var dialogDesc = ""
|
||||||
|
|
||||||
var page = 1
|
var page = 1
|
||||||
var isLast = false
|
var isLast = false
|
||||||
private var pageSize = 10
|
private var pageSize = 10
|
||||||
|
@ -262,6 +267,62 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func voteApplicant(applicantId: Int) {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.voteApplicant(applicantId: applicantId)
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||||
|
|
||||||
|
if decoded.success {
|
||||||
|
if self.isShowNotifyVote {
|
||||||
|
self.dialogTitle = "[오디션 응원]"
|
||||||
|
self.dialogDesc = "오디션을 응원하셨습니다\n(무료응원 : 1계정당 1일 1회)\n1캔으로 추가 응원을 해보세요."
|
||||||
|
self.isShowSodaDialog = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = self.applicantList.firstIndex(where: { $0.applicantId == applicantId }) {
|
||||||
|
var applicant = self.applicantList[index]
|
||||||
|
applicant.voteCount += 1
|
||||||
|
|
||||||
|
self.applicantList.remove(at: index)
|
||||||
|
self.applicantList.insert(applicant, at: index)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
if message.contains("오늘 응원은 여기까지") {
|
||||||
|
self.dialogTitle = "[오늘 응원 제한]"
|
||||||
|
self.dialogDesc = "오늘 응원은 여기까지!\n하루 최대 10회까지 이용이 가능합니다.\n내일 다시 이용해주세요."
|
||||||
|
self.isShowSodaDialog = true
|
||||||
|
} else {
|
||||||
|
self.errorMessage = message
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
func deleteAllRecordingFilesWithNamePrefix(_ prefix: String) {
|
func deleteAllRecordingFilesWithNamePrefix(_ prefix: String) {
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
let documentsURL = getDocumentsDirectory()
|
let documentsURL = getDocumentsDirectory()
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// VoteAuditionApplicantRequest.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 1/7/25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct VoteAuditionApplicantRequest: Encodable {
|
||||||
|
let applicantId: Int
|
||||||
|
let container: String = "ios"
|
||||||
|
let timezone: String = TimeZone.current.identifier
|
||||||
|
}
|
Loading…
Reference in New Issue