Files
sodalive-ios/SodaLive/Sources/Content/Create/ContentCreateViewModel.swift
Yu Sung 8efa89d564 콘텐츠 작성 및 라이브 메뉴 다국어화
콘텐츠 등록 화면 텍스트와 버튼을 I18n 기반 번역 문자열로 교체

룰렛 설정과 미션 메뉴 버튼 라벨을 다국어 문자열로 통일

신규 텍스트를 String Catalog에 추가하여 네비게이션 타이틀 번역
2025-12-17 18:47:46 +09:00

337 lines
12 KiB
Swift

//
// ContentCreateViewModel.swift
// SodaLive
//
// Created by klaus on 2023/08/11.
//
import UIKit
import Moya
import Combine
final class ContentCreateViewModel: ObservableObject {
private let repository = ContentRepository()
private var subscription = Set<AnyCancellable>()
@Published var isLoading = false
@Published var isShowCompletePopup = false
@Published var errorMessage = ""
@Published var isShowPopup = false
@Published var fileName = ""
@Published var title = ""
@Published var detail = ""
@Published var hashtags: String = ""
@Published var theme: GetAudioContentThemeResponse? = nil
@Published var coverImage: UIImage? = nil
@Published var selectedFileUrl: URL? = nil {
didSet {
if let fileUrl = selectedFileUrl {
fileName = fileUrl.lastPathComponent
}
}
}
@Published var isAvailableComment = true
@Published var isActiveReservation = false
@Published var isAdult = false
@Published var priceString = "0" {
didSet {
if priceString.count > 5 {
priceString = String(priceString.prefix(5))
} else {
if let price = Int(priceString) {
self.price = price
} else {
self.price = 0
}
}
}
}
@Published var price = 0
@Published var isFree = true {
didSet {
if isFree {
priceString = "0"
isLimited = false
isGeneratePreview = true
isPointAvailable = false
purchaseOption = PurchaseOption.BOTH
}
}
}
@Published var purchaseOption = PurchaseOption.BOTH {
didSet {
if purchaseOption == .RENT_ONLY {
isLimited = false
}
}
}
@Published var isGeneratePreview = true
@Published var isPointAvailable = false
@Published var isLimited = false {
didSet {
if !isLimited {
limitedString = ""
}
}
}
@Published var limited: Int? = nil {
didSet {
if let limited = limited, limited <= 0 {
limitedString = ""
}
}
}
@Published var limitedString: String = "" {
didSet {
if limitedString == "" {
limited = nil
} else {
limited = Int(limitedString)
}
}
}
@Published var previewStartTime: String = ""
@Published var previewEndTime: String = ""
@Published var releaseDateString: String = Date().convertDateFormat(dateFormat: "yyyy.MM.dd")
@Published var releaseTimeString: String = Date().convertDateFormat(dateFormat: "a hh:mm")
var releaseDate = Date() {
willSet {
releaseDateString = newValue.convertDateFormat(dateFormat: "yyyy.MM.dd")
}
}
var releaseTime = Date() {
willSet {
releaseTimeString = newValue.convertDateFormat(dateFormat: "a hh:mm")
}
}
func uploadAudioContent() {
if !isLoading && validateData() {
isLoading = true
let request = CreateAudioContentRequest(
title: title,
detail: detail,
tags: hashtags,
price: price,
purchaseOption: purchaseOption,
limited: limited,
releaseDate: isActiveReservation ? "\(releaseDate.convertDateFormat(dateFormat: "yyyy-MM-dd")) \(releaseTime.convertDateFormat(dateFormat: "HH:mm"))" : nil,
timezone: TimeZone.current.identifier,
themeId: theme!.id,
isAdult: isAdult,
isGeneratePreview: isGeneratePreview,
isPointAvailable: isPointAvailable,
isCommentAvailable: isAvailableComment,
previewStartTime: isGeneratePreview && previewStartTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewStartTime : nil,
previewEndTime: isGeneratePreview && previewEndTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewEndTime : nil
)
var multipartData = [MultipartFormData]()
let encoder = JSONEncoder()
encoder.outputFormatting = .withoutEscapingSlashes
let jsonData = try? encoder.encode(request)
if let jsonData = jsonData {
if let coverImage = coverImage, let imageData = coverImage.jpegData(compressionQuality: 0.8) {
multipartData.append(
MultipartFormData(
provider: .data(imageData),
name: "coverImage",
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg",
mimeType: "image/*")
)
} else {
errorMessage = "커버이미지를 업로드 하지 못했습니다.\n다시 선택해 주세요"
isShowPopup = true
isLoading = false
return
}
if let selectedFileUrl = selectedFileUrl {
if selectedFileUrl.startAccessingSecurityScopedResource() {
defer {
selectedFileUrl.stopAccessingSecurityScopedResource()
}
if let data = try? Data(contentsOf: selectedFileUrl) {
multipartData.append(
MultipartFormData(
provider: .data(data),
name: "contentFile",
fileName: selectedFileUrl.lastPathComponent,
mimeType: "audio/*"
)
)
} else {
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
isShowPopup = true
isLoading = false
return
}
} else {
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
isShowPopup = true
isLoading = false
return
}
} else {
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
isShowPopup = true
isLoading = false
return
}
multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request"))
repository
.uploadAudioContent(parameters: multipartData)
.sink { result in
switch result {
case .finished:
DEBUG_LOG("finish")
case .failure(let error):
ERROR_LOG(error.localizedDescription)
}
} receiveValue: { [unowned self] 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 {
self.isShowCompletePopup = true
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
} catch {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
self.isLoading = false
}
}
}
private func validateData() -> Bool {
if title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
errorMessage = "제목을 입력해 주세요."
isShowPopup = true
return false
}
if detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5 {
errorMessage = "내용을 5자 이상 입력해 주세요."
isShowPopup = true
return false
}
if theme == nil {
errorMessage = "테마를 선택해 주세요."
isShowPopup = true
return false
}
if coverImage == nil {
errorMessage = "커버이미지를 선택해 주세요."
isShowPopup = true
return false
}
if selectedFileUrl == nil {
errorMessage = "오디오 콘텐츠를 선택해 주세요."
isShowPopup = true
return false
}
if !isFree && price < 5 {
errorMessage = "콘텐츠의 최소금액은 5캔 입니다."
isShowPopup = true
return false
}
if previewStartTime.count > 0 && previewEndTime.count > 0 {
let startTimeArray = previewStartTime.split(separator: ":")
if startTimeArray.count != 3 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
isShowPopup = true
return false
}
for time in startTimeArray {
if time.count != 2 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
isShowPopup = true
return false
}
}
let endTimeArray = previewStartTime.split(separator: ":")
if endTimeArray.count != 3 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
isShowPopup = true
return false
}
for time in endTimeArray {
if time.count != 2 {
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
isShowPopup = true
return false
}
}
let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime)
if timeDifference < 15.0 {
errorMessage = "미리 듣기의 최소 시간은 15초 입니다"
isShowPopup = true
return false
}
} else {
if previewStartTime.count > 0 || previewEndTime.count > 0 {
errorMessage = "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다."
isShowPopup = true
return false
}
}
return true
}
private func timeDifference(startTime: String, endTime: String) -> Double {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
if let date1 = dateFormatter.date(from: startTime), let date2 = dateFormatter.date(from: endTime) {
return date2.timeIntervalSince(date1)
}
return 0
}
}