Compare commits
34 Commits
4f66ffb595
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
243da1eb7d | ||
|
|
39700d3b39 | ||
|
|
43c86a627b | ||
|
|
1ec56a1f15 | ||
|
|
c039931f34 | ||
|
|
a90996603b | ||
|
|
49e2487617 | ||
|
|
038d66e363 | ||
|
|
bceec46ebc | ||
|
|
540238eb48 | ||
|
|
201f4c8139 | ||
|
|
7285c5367d | ||
|
|
b53614836f | ||
|
|
25fccbaa07 | ||
|
|
9369a52ba2 | ||
|
|
4c170e0f97 | ||
|
|
3f61a08a04 | ||
|
|
9216db51da | ||
|
|
8e4fe7a534 | ||
|
|
b2f66cf408 | ||
|
|
47085dc1ca | ||
|
|
222520d5e9 | ||
|
|
136bfc8eee | ||
|
|
178e0849dc | ||
|
|
3a4df173d2 | ||
|
|
ec7e9cc71c | ||
|
|
8370f1ead1 | ||
|
|
e067531a3f | ||
|
|
d369bc11f7 | ||
|
|
f542191d46 | ||
|
|
1d120b58bd | ||
|
|
44daabdcae | ||
|
|
0844c6f4d7 | ||
|
|
c6a6b3c79e |
File diff suppressed because it is too large
Load Diff
@@ -74,6 +74,7 @@ class AppState: ObservableObject {
|
||||
|
||||
@Published var isShowErrorPopup = false
|
||||
@Published var errorMessage = ""
|
||||
@Published private var pendingContentSettingsGuideMessage: String? = nil
|
||||
@Published var liveDetailSheet: LiveDetailSheetState? = nil
|
||||
|
||||
private func syncStepWithNavigationPath() {
|
||||
@@ -180,6 +181,16 @@ class AppState: ObservableObject {
|
||||
pendingCommunityCommentCreatorId = 0
|
||||
pendingCommunityCommentPostId = 0
|
||||
}
|
||||
|
||||
func setPendingContentSettingsGuideMessage(_ message: String) {
|
||||
pendingContentSettingsGuideMessage = message
|
||||
}
|
||||
|
||||
func consumePendingContentSettingsGuideMessage() -> String? {
|
||||
let message = pendingContentSettingsGuideMessage
|
||||
pendingContentSettingsGuideMessage = nil
|
||||
return message
|
||||
}
|
||||
|
||||
// 언어 적용 직후 앱을 소프트 재시작(스플래시 -> 메인)하여 전역 UI를 새로고침
|
||||
func softRestart() {
|
||||
|
||||
@@ -75,6 +75,9 @@ final class AppViewModel: ObservableObject {
|
||||
UserDefaults.set(data.isAuth, forKey: .auth)
|
||||
UserDefaults.set(data.role.rawValue, forKey: .role)
|
||||
UserDefaults.set(data.auditionNotice ?? false, forKey: .isAuditionNotification)
|
||||
UserDefaults.set(data.countryCode, forKey: .countryCode)
|
||||
UserDefaults.set(data.isAdultContentVisible, forKey: .isAdultContentVisible)
|
||||
UserDefaults.set(data.contentType, forKey: .contentPreference)
|
||||
if data.followingChannelLiveNotice == nil && data.followingChannelUploadContentNotice == nil && data.messageNotice == nil {
|
||||
AppState.shared.isShowNotificationSettingsDialog = true
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ struct ApplyMethodView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("오디션 지원방식")
|
||||
Text(I18n.Audition.ApplyMethod.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.graybb)
|
||||
.padding(.top, 33.3)
|
||||
@@ -39,7 +39,7 @@ struct ApplyMethodView: View {
|
||||
HStack(spacing: 3) {
|
||||
Image("ic_upload")
|
||||
|
||||
Text("파일 업로드")
|
||||
Text(I18n.Audition.ApplyMethod.fileUpload)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ struct ApplyMethodView: View {
|
||||
HStack(spacing: 3) {
|
||||
Image("ic_mic_color_button")
|
||||
|
||||
Text("바로 녹음")
|
||||
Text(I18n.Audition.ApplyMethod.recordNow)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ struct ApplyMethodView: View {
|
||||
.padding(.top, 21.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("※ 파일은 mp3, aac만 업로드 가능")
|
||||
Text(I18n.Audition.ApplyMethod.fileFormatNotice)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
@@ -30,7 +30,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
VStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션 녹음")
|
||||
Text(I18n.Audition.Recording.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -76,7 +76,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 15.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb.opacity(0))
|
||||
|
||||
@@ -99,7 +99,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 15.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
.onTapGesture {
|
||||
@@ -113,7 +113,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
.padding(.vertical, 52.3)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Text("다시 녹음")
|
||||
Text(I18n.Audition.Recording.recordAgain)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.frame(width: (proxy.size.width - 40) / 3, height: 50)
|
||||
@@ -129,7 +129,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
soundManager.recordMode = .RECORD
|
||||
}
|
||||
|
||||
Text("녹음완료")
|
||||
Text(I18n.Audition.Recording.recordComplete)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: (proxy.size.width - 40) * 2 / 3, height: 50)
|
||||
@@ -140,7 +140,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
let soundData = try Data(contentsOf: soundManager.getAudioFileURL())
|
||||
onClickCompleteRecording(tempFileName, soundData)
|
||||
} catch {
|
||||
errorMessage = "녹음파일을 생성하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
errorMessage = I18n.Audition.Recording.createFileFailed
|
||||
isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ struct AuditionApplyView: View {
|
||||
if isShow {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션 지원")
|
||||
Text(I18n.Audition.Apply.title)
|
||||
.appFont(size: 18.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -45,7 +45,7 @@ struct AuditionApplyView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("녹음파일")
|
||||
Text(I18n.Audition.Apply.recordingFile)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.top, 20)
|
||||
@@ -53,7 +53,7 @@ struct AuditionApplyView: View {
|
||||
HStack(spacing: 4) {
|
||||
Image("ic_note_square")
|
||||
|
||||
Text(filename)
|
||||
Text(displayFileName)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.grayd2)
|
||||
|
||||
@@ -66,12 +66,12 @@ struct AuditionApplyView: View {
|
||||
.cornerRadius(5.3)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("연락처")
|
||||
Text(I18n.Audition.Apply.contact)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
TextField("합격시 받을 연락처를 남겨주세요", text: $phoneNumber)
|
||||
TextField(I18n.Audition.Apply.contactPlaceholder, text: $phoneNumber)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.decimalPad)
|
||||
@@ -89,7 +89,7 @@ struct AuditionApplyView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("보이스온 오디오 드라마 오디션 합격시 개인 연락을 위한 개인 정보(연락처) 수집 및 활용에 동의합니다.\n오디션 지원자는 개인정보 수집 및 활용 동의에 거부할 권리가 있으며 비동의시 오디션 지원은 취소 됩니다.")
|
||||
Text(I18n.Audition.Apply.privacyAgreement)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.lineSpacing(3)
|
||||
@@ -100,7 +100,7 @@ struct AuditionApplyView: View {
|
||||
isAgree.toggle()
|
||||
}
|
||||
|
||||
Text("오디션 지원하기")
|
||||
Text(I18n.Audition.Apply.submit)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.vertical, 13.3)
|
||||
@@ -110,7 +110,7 @@ struct AuditionApplyView: View {
|
||||
.padding(.top, 35)
|
||||
.onTapGesture {
|
||||
if !isAgree {
|
||||
errorMessage = "연락처 수집 및 활용에 동의하셔야 오디션 지원이 가능합니다."
|
||||
errorMessage = I18n.Audition.Apply.requireAgreement
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -137,6 +137,14 @@ struct AuditionApplyView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var displayFileName: String {
|
||||
if filename.hasPrefix("voiceon_now_voice_") {
|
||||
return I18n.Audition.Apply.recordedVoiceFileName
|
||||
}
|
||||
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@@ -24,7 +24,7 @@ struct AuditionView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("오디션")
|
||||
Text(I18n.Audition.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
@@ -43,13 +43,13 @@ struct AuditionView: View {
|
||||
.background(Color.black)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("보이스온 오디션 이용방법")
|
||||
Text(I18n.Audition.List.usageGuide)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("자세히>")
|
||||
Text(I18n.Audition.List.detail)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
@@ -73,17 +73,17 @@ struct AuditionView: View {
|
||||
if $0 == 0 && !item.isOff {
|
||||
VStack(alignment: .leading, spacing: 25) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션")
|
||||
Text(I18n.Audition.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(" ON")
|
||||
Text(I18n.Audition.List.onStatus)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.mainRed)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("총 \(viewModel.inProgressCount)개")
|
||||
Text(I18n.Audition.List.totalCount(viewModel.inProgressCount))
|
||||
.appFont(size: 11.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
}
|
||||
@@ -111,17 +111,17 @@ struct AuditionView: View {
|
||||
.padding(.top, 5)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션")
|
||||
Text(I18n.Audition.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(" OFF")
|
||||
Text(I18n.Audition.List.offStatus)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("총 \(viewModel.completedCount)개")
|
||||
Text(I18n.Audition.List.totalCount(viewModel.completedCount))
|
||||
.appFont(size: 11.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
}
|
||||
|
||||
@@ -66,13 +66,13 @@ final class AuditionViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct AuditionDetailView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.cornerRadius(6.7)
|
||||
|
||||
Text("오디션 정보")
|
||||
Text(I18n.Audition.Detail.informationTitle)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
@@ -38,7 +38,7 @@ struct AuditionDetailView: View {
|
||||
ExpandableTextView(text: response.information)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("오디션 캐릭터")
|
||||
Text(I18n.Audition.Detail.characterTitle)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
@@ -18,7 +18,7 @@ final class AuditionDetailViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
|
||||
@Published var response: GetAuditionDetailResponse? = nil
|
||||
@Published var title: String = "보이스온"
|
||||
@Published var title: String = I18n.Audition.defaultTitle
|
||||
|
||||
func getAuditionDetail(auditionId: Int, onFailure: @escaping () -> Void) {
|
||||
isLoading = true
|
||||
@@ -45,7 +45,7 @@ final class AuditionDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -54,7 +54,7 @@ final class AuditionDetailViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
onFailure()
|
||||
|
||||
@@ -64,7 +64,7 @@ class AuditionSoundManager: NSObject, ObservableObject {
|
||||
|
||||
private func setupPlayer(with url: String) {
|
||||
guard let url = URL(string: url) else {
|
||||
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Sound.playbackFailed
|
||||
self.isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -92,7 +92,7 @@ class AuditionSoundManager: NSObject, ObservableObject {
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Sound.playbackFailed
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ struct AuditionDetailRoleItemView: View {
|
||||
.opacity(item.isComplete ? 0.7 : 0.0)
|
||||
)
|
||||
|
||||
Text(item.isComplete ? "모집완료" : "모집중")
|
||||
Text(item.isComplete ? I18n.Audition.Detail.recruitmentClosed : I18n.Audition.Detail.recruiting)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 9)
|
||||
|
||||
@@ -45,7 +45,7 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
HStack(spacing: 14) {
|
||||
if let url = URL(string: roleDetail.originalWorkUrl), UIApplication.shared.canOpenURL(url) {
|
||||
Text("원작 보러가기")
|
||||
Text(I18n.Audition.Detail.viewOriginalWork)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 12)
|
||||
@@ -59,7 +59,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
if let url = URL(string: roleDetail.auditionScriptUrl), UIApplication.shared.canOpenURL(url) {
|
||||
Text("오디션 대본 확인")
|
||||
Text(I18n.Audition.Detail.checkScript)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 12)
|
||||
@@ -74,7 +74,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
Text("오디션 캐릭터 정보")
|
||||
Text(I18n.Audition.Detail.characterInfo)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -83,13 +83,13 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
if viewModel.applicantList.isEmpty {
|
||||
Text("지원자가 없습니다.")
|
||||
Text(I18n.Audition.Detail.noApplicants)
|
||||
.appFont(size: 13, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
Text("참여자")
|
||||
Text(I18n.Audition.Detail.participants)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
@@ -98,13 +98,13 @@ struct AuditionRoleDetailView: View {
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.leading, 2.3)
|
||||
|
||||
Text("명")
|
||||
Text(I18n.Audition.Detail.personUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Audition.Detail.sortNewest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
viewModel.sortType == .NEWEST ? Color.button : Color.graybb
|
||||
@@ -113,7 +113,7 @@ struct AuditionRoleDetailView: View {
|
||||
viewModel.setSortType(sortType: .NEWEST)
|
||||
}
|
||||
|
||||
Text("좋아요순")
|
||||
Text(I18n.Audition.Detail.sortLikes)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
viewModel.sortType == .LIKES ? Color.button : Color.graybb
|
||||
@@ -161,7 +161,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
if let roleDetail = viewModel.auditionRoleDetail {
|
||||
Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원")
|
||||
Text(roleDetail.isAlreadyApplicant ? I18n.Audition.Apply.reapply : I18n.Audition.Apply.apply)
|
||||
.appFont(size: 15.3, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(14)
|
||||
@@ -241,9 +241,9 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if isShowNoticeReapply {
|
||||
SodaDialog(
|
||||
title: "재지원 안내",
|
||||
desc: "재지원 시 이전 지원 내역은 삭제되며 받은 투표수는 무효 처리됩니다.",
|
||||
confirmButtonTitle: "확인"
|
||||
title: I18n.Audition.Apply.reapplyNoticeTitle,
|
||||
desc: I18n.Audition.Apply.reapplyNoticeDesc,
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
isShowNoticeReapply = false
|
||||
isShowApplyMethodView = true
|
||||
@@ -252,9 +252,9 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if isShowNoticeAuthView {
|
||||
SodaDialog(
|
||||
title: "- 본인인증 -",
|
||||
desc: "마이페이지에서 '본인인증'을 하고 다시 오디션에 지원해 주세요.",
|
||||
confirmButtonTitle: "확인"
|
||||
title: I18n.Audition.Apply.authRequiredTitle,
|
||||
desc: I18n.Audition.Apply.authRequiredDesc,
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
isShowNoticeAuthView = false
|
||||
}
|
||||
@@ -264,7 +264,7 @@ struct AuditionRoleDetailView: View {
|
||||
SodaDialog(
|
||||
title: viewModel.dialogTitle,
|
||||
desc: viewModel.dialogDesc,
|
||||
confirmButtonTitle: "확인"
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
viewModel.isShowVoteCompleteView = false
|
||||
viewModel.isShowNotifyVote = false
|
||||
@@ -300,17 +300,17 @@ struct AuditionRoleDetailView: View {
|
||||
viewModel.fileName = fileUrl.lastPathComponent
|
||||
isShowApplyView = true
|
||||
} else {
|
||||
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
|
||||
viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
} else {
|
||||
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
|
||||
viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
DEBUG_LOG("error: \(error.localizedDescription)")
|
||||
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
|
||||
viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
@Published var totalCount = 0
|
||||
@Published var applicantList = [GetAuditionRoleApplicantItem]()
|
||||
|
||||
@Published var name = "보이스온"
|
||||
@Published var name = I18n.Audition.defaultTitle
|
||||
@Published var auditionRoleDetail: GetAuditionRoleDetailResponse? = nil
|
||||
|
||||
@Published private(set) var sortType = AuditionApplicantSortType.NEWEST {
|
||||
@@ -93,13 +93,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = roleDetailDecoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = applicantListDecoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -127,7 +127,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
self.onFailure()
|
||||
@@ -172,13 +172,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -188,13 +188,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
|
||||
func applyAudition(onSuccess: @escaping () -> Void) {
|
||||
if phoneNumber.count != 11 {
|
||||
errorMessage = "잘못된 연락처 입니다.\n다시 입력해 주세요."
|
||||
errorMessage = I18n.Audition.Apply.invalidContact
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
guard let soundData = soundData else {
|
||||
errorMessage = "잘못된 녹음 파일 입니다.\n다시 선택해 주세요."
|
||||
errorMessage = I18n.Audition.Apply.invalidRecordingFile
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -248,19 +248,19 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Audition.Apply.applyFailed
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Audition.Apply.applyFailed
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Audition.Apply.applyFailed
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -287,8 +287,8 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
|
||||
if decoded.success {
|
||||
if self.isShowNotifyVote {
|
||||
self.dialogTitle = "[오디션 응원]"
|
||||
self.dialogDesc = "오디션을 응원하셨습니다\n(무료응원 : 1계정당 1일 1회)\n1캔으로 추가 응원을 해보세요."
|
||||
self.dialogTitle = I18n.Audition.Vote.cheerTitle
|
||||
self.dialogDesc = I18n.Audition.Vote.cheerDescription
|
||||
self.isShowVoteCompleteView = true
|
||||
}
|
||||
|
||||
@@ -302,20 +302,20 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
if message.contains("오늘 응원은 여기까지") {
|
||||
self.dialogTitle = "[오늘 응원 제한]"
|
||||
self.dialogDesc = "오늘 응원은 여기까지!\n하루 최대 10회까지 이용이 가능합니다.\n내일 다시 이용해주세요."
|
||||
self.dialogTitle = I18n.Audition.Vote.limitTitle
|
||||
self.dialogDesc = I18n.Audition.Vote.limitDescription
|
||||
self.isShowVoteCompleteView = true
|
||||
} else {
|
||||
self.errorMessage = message
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} else {
|
||||
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Vote.unknownError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Vote.unknownError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ struct CharacterItemView: View {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Text("N")
|
||||
Text(I18n.Chat.Character.newBadge)
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 30, height: 30)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CharacterSectionView: View {
|
||||
let title: LocalizedStringResource
|
||||
let title: String
|
||||
let items: [Character]
|
||||
let isShowRank: Bool
|
||||
var trailingTitle: String? = nil
|
||||
@@ -52,7 +52,7 @@ struct CharacterSectionView: View {
|
||||
|
||||
#Preview {
|
||||
CharacterSectionView(
|
||||
title: "신규 캐릭터",
|
||||
title: I18n.Chat.Character.newSectionTitle,
|
||||
items: [
|
||||
Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300", isNew: true),
|
||||
Character(characterId: 2, name: "데이지", description: "", imageUrl: "https://picsum.photos/300", isNew: false)
|
||||
|
||||
@@ -39,7 +39,7 @@ struct CharacterView: View {
|
||||
// 인기 캐릭터 섹션
|
||||
if !viewModel.popularCharacters.isEmpty {
|
||||
CharacterSectionView(
|
||||
title: "인기 캐릭터",
|
||||
title: I18n.Chat.Character.popularSectionTitle,
|
||||
items: viewModel.popularCharacters,
|
||||
isShowRank: true,
|
||||
onTap: { ch in
|
||||
@@ -51,7 +51,7 @@ struct CharacterView: View {
|
||||
// 신규 캐릭터 섹션
|
||||
if !viewModel.newCharacters.isEmpty {
|
||||
CharacterSectionView(
|
||||
title: "신규 캐릭터",
|
||||
title: I18n.Chat.Character.newSectionTitle,
|
||||
items: viewModel.newCharacters,
|
||||
isShowRank: false,
|
||||
trailingTitle: I18n.Common.viewAll,
|
||||
@@ -67,7 +67,7 @@ struct CharacterView: View {
|
||||
if !viewModel.recommendCharacters.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack {
|
||||
Text("추천 캐릭터")
|
||||
Text(I18n.Chat.Character.recommendSectionTitle)
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -62,7 +62,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
self.isLoading = false
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -101,11 +101,10 @@ final class CharacterViewModel: ObservableObject {
|
||||
self.isLoading = false
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct CharacterDetailView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "캐릭터 정보")) {
|
||||
DetailNavigationBar(title: I18n.Chat.Character.detailTitle) {
|
||||
if presentationMode.wrappedValue.isPresented {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} else {
|
||||
@@ -77,7 +77,7 @@ struct CharacterDetailView: View {
|
||||
if let others = viewModel.characterDetail?.others, !others.isEmpty {
|
||||
VStack(spacing: 16) {
|
||||
HStack {
|
||||
Text("장르의 다른 캐릭터")
|
||||
Text(I18n.Chat.Character.detailOtherCharactersTitle)
|
||||
.appFont(size: 26, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -178,6 +178,13 @@ extension CharacterDetailView {
|
||||
|
||||
// MARK: - Profile Section
|
||||
extension CharacterDetailView {
|
||||
private func isMaleGender(_ gender: String) -> Bool {
|
||||
let normalizedGender = gender
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.lowercased()
|
||||
return normalizedGender == "남성" || normalizedGender == "male"
|
||||
}
|
||||
|
||||
private var profileSection: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if viewModel.characterDetail?.mbti != nil ||
|
||||
@@ -189,7 +196,7 @@ extension CharacterDetailView {
|
||||
Text(viewModel.characterDetail?.translated?.gender ?? gender)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(
|
||||
gender == "남성" ?
|
||||
isMaleGender(gender) ?
|
||||
Color.button :
|
||||
Color.mainRed
|
||||
)
|
||||
@@ -201,7 +208,7 @@ extension CharacterDetailView {
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.stroke(lineWidth: 1)
|
||||
.foregroundColor(
|
||||
gender == "남성" ?
|
||||
isMaleGender(gender) ?
|
||||
Color.button :
|
||||
Color.mainRed
|
||||
)
|
||||
@@ -209,7 +216,7 @@ extension CharacterDetailView {
|
||||
}
|
||||
|
||||
if let age = viewModel.characterDetail?.age {
|
||||
Text("\(age)세")
|
||||
Text(I18n.Chat.Character.age(age))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 7)
|
||||
@@ -252,7 +259,7 @@ extension CharacterDetailView {
|
||||
|
||||
if let characterType = viewModel.characterDetail?.characterType {
|
||||
HStack(spacing: 8) {
|
||||
Text(characterType.rawValue)
|
||||
Text(characterType == .Clone ? I18n.Chat.Character.typeClone : I18n.Chat.Character.typeCharacter)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 5)
|
||||
@@ -282,7 +289,7 @@ extension CharacterDetailView {
|
||||
private func worldViewSection(backgrounds: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("[세계관 및 작품 소개]")
|
||||
Text(I18n.Chat.Character.detailWorldViewTitle)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -300,7 +307,7 @@ extension CharacterDetailView {
|
||||
private func originalWorkSection(title: String, link: String) -> some View {
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Text("원작")
|
||||
Text(I18n.Chat.Character.detailOriginalTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.white)
|
||||
@@ -321,7 +328,7 @@ extension CharacterDetailView {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
Text("원작 보러가기")
|
||||
Text(I18n.Chat.Character.detailOriginalLinkButton)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(hex: "3BB9F1"))
|
||||
@@ -342,7 +349,7 @@ extension CharacterDetailView {
|
||||
private func personalitySection(personalities: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("[성격 및 특징]")
|
||||
Text(I18n.Chat.Character.detailPersonalityTitle)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -354,24 +361,19 @@ extension CharacterDetailView {
|
||||
// 캐릭터톡 대화 가이드
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack {
|
||||
Text("⚠️ 캐릭터톡 대화 가이드")
|
||||
Text(I18n.Chat.Character.detailConversationGuideTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("""
|
||||
보이스온의 오픈월드 캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다. 세계관 속 연관 캐릭터가 되어 대화를 하거나 완전히 새로운 인물이 되어 캐릭터와 당신만의 스토리를 만들어 갈 수 있습니다.
|
||||
""")
|
||||
Text(I18n.Chat.Character.detailConversationGuideDescription1)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "AEAEB2"))
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
Text("""
|
||||
오픈월드 캐릭터톡은 캐릭터를 정교하게 설계하였지만, 대화가 어색하거나 불완전할 수도 있습니다.
|
||||
대화 도중 캐릭터의 대화가 이상하거나 새로운 캐릭터로 대화를 나누고 싶다면 대화를 초기화 하고 새롭게 캐릭터와 대화를 나눠보세요.
|
||||
""")
|
||||
Text(I18n.Chat.Character.detailConversationGuideDescription2)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "AEAEB2"))
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -393,7 +395,7 @@ extension CharacterDetailView {
|
||||
// MARK: - Chat Button
|
||||
extension CharacterDetailView {
|
||||
private var chatButton: some View {
|
||||
Text("대화하기")
|
||||
Text(I18n.Chat.Character.detailChatButton)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -450,7 +452,7 @@ struct CharacterExpandableTextView: View {
|
||||
.foregroundColor(Color(hex: "607D8B"))
|
||||
.rotationEffect(.degrees(isExpanded ? 180 : 0))
|
||||
|
||||
Text(isExpanded ? "간략히" : "더보기")
|
||||
Text(isExpanded ? I18n.Chat.Character.detailCollapse : I18n.Chat.Character.detailExpand)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "607D8B"))
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ struct CharacterDetailGalleryView: View {
|
||||
VStack(spacing: 8) {
|
||||
// 상단 정보 (계산된 % 보유중, 정보 아이콘, 개수)
|
||||
HStack {
|
||||
Text("\(viewModel.ownershipPercentage)% 보유중")
|
||||
Text(I18n.Chat.Character.DetailGallery.ownership(viewModel.ownershipPercentage))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -91,7 +91,7 @@ struct CharacterDetailGalleryView: View {
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("\(viewModel.totalCount)개")
|
||||
Text(I18n.Chat.Character.DetailGallery.totalCount(viewModel.totalCount))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ final class NewCharacterListViewModel: ObservableObject {
|
||||
} else {
|
||||
self?.isLoading = false
|
||||
}
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
@@ -93,7 +93,7 @@ final class NewCharacterListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
self.isShowPopup = true
|
||||
if isLoadMore {
|
||||
@@ -108,7 +108,7 @@ final class NewCharacterListViewModel: ObservableObject {
|
||||
} else {
|
||||
self.isLoading = false
|
||||
}
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,18 @@ struct NewCharacterListView: View {
|
||||
Group { BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 8) {
|
||||
// Toolbar
|
||||
DetailNavigationBar(title: String(localized: "신규 캐릭터 전체보기"))
|
||||
DetailNavigationBar(title: I18n.Chat.Character.NewList.title)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// 전체 n개
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Chat.Character.NewList.totalPrefix)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Text(" \(viewModel.totalCount)")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
Text("개")
|
||||
Text(I18n.Chat.Character.NewList.countUnit)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Spacer()
|
||||
|
||||
@@ -14,8 +14,8 @@ struct RecentCharacterSectionView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(spacing: 0) {
|
||||
Text("최근 대화한 캐릭터 ")
|
||||
HStack(spacing: 4) {
|
||||
Text(I18n.Chat.Character.recentSectionTitle)
|
||||
.appFont(size: 20, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -41,7 +41,14 @@ struct ChatTabView: View {
|
||||
AppState.shared.setAppStep(step: .login)
|
||||
return
|
||||
}
|
||||
if auth == false {
|
||||
|
||||
let normalizedCountryCode = UserDefaults
|
||||
.string(forKey: .countryCode)
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.uppercased()
|
||||
let isKoreanCountry = normalizedCountryCode.isEmpty || normalizedCountryCode == "KR"
|
||||
|
||||
if isKoreanCountry && auth == false {
|
||||
pendingAction = {
|
||||
AppState.shared
|
||||
.setAppStep(step: .characterDetail(characterId: characterId))
|
||||
@@ -49,8 +56,20 @@ struct ChatTabView: View {
|
||||
isShowAuthConfirmView = true
|
||||
return
|
||||
}
|
||||
|
||||
if !UserDefaults.isAdultContentVisible() {
|
||||
pendingAction = nil
|
||||
moveToContentSettingsWithGuideToast()
|
||||
return
|
||||
}
|
||||
|
||||
AppState.shared.setAppStep(step: .characterDetail(characterId: characterId))
|
||||
}
|
||||
|
||||
private func moveToContentSettingsWithGuideToast() {
|
||||
AppState.shared.setPendingContentSettingsGuideMessage(I18n.Settings.adultContentEnableGuide)
|
||||
AppState.shared.setAppStep(step: .contentViewSettings)
|
||||
}
|
||||
|
||||
private func handleCharacterSelection() {
|
||||
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -149,7 +168,7 @@ struct ChatTabView: View {
|
||||
isShowAuthView = false
|
||||
}
|
||||
.onError { _ in
|
||||
AppState.shared.errorMessage = "본인인증 중 오류가 발생했습니다."
|
||||
AppState.shared.errorMessage = I18n.Chat.Auth.authenticationError
|
||||
AppState.shared.isShowErrorPopup = true
|
||||
isShowAuthView = false
|
||||
}
|
||||
@@ -171,15 +190,14 @@ struct ChatTabView: View {
|
||||
|
||||
if isShowAuthConfirmView {
|
||||
SodaDialog(
|
||||
title: "본인인증",
|
||||
desc: "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" +
|
||||
"캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.",
|
||||
confirmButtonTitle: "본인인증 하러가기",
|
||||
title: I18n.Chat.Auth.dialogTitle,
|
||||
desc: I18n.Chat.Auth.dialogDescription,
|
||||
confirmButtonTitle: I18n.Chat.Auth.goToVerification,
|
||||
confirmButtonAction: {
|
||||
isShowAuthConfirmView = false
|
||||
isShowAuthView = true
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonTitle: I18n.Common.cancel,
|
||||
cancelButtonAction: {
|
||||
isShowAuthConfirmView = false
|
||||
pendingAction = nil
|
||||
|
||||
@@ -58,7 +58,7 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
}
|
||||
|
||||
if item.isAdult {
|
||||
Text("19+")
|
||||
Text(I18n.Chat.Original.adultBadge)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.horizontal, 7)
|
||||
|
||||
@@ -151,7 +151,7 @@ struct OriginalWorkInfoView: View {
|
||||
ZStack {
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("작품 소개")
|
||||
Text(I18n.Chat.Original.workIntroductionTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -170,7 +170,7 @@ struct OriginalWorkInfoView: View {
|
||||
.cornerRadius(16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("원작 보러 가기")
|
||||
Text(I18n.Chat.Original.viewOriginalLinksTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
@@ -197,26 +197,26 @@ struct OriginalWorkInfoView: View {
|
||||
.cornerRadius(16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("상세 정보")
|
||||
Text(I18n.Chat.Original.detailInfoTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let _ = response.writer {
|
||||
Text("작가")
|
||||
Text(I18n.Chat.Original.writerLabel)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.studio {
|
||||
Text("제작사")
|
||||
Text(I18n.Chat.Original.studioLabel)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.originalWork {
|
||||
Text("원작")
|
||||
Text(I18n.Chat.Original.originalLabel)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ final class OriginalWorkDetailViewModel: ObservableObject {
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
@@ -61,14 +61,14 @@ final class OriginalWorkDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ final class OriginalWorkViewModel: ObservableObject {
|
||||
} else {
|
||||
self?.isLoading = false
|
||||
}
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
@@ -92,7 +92,7 @@ final class OriginalWorkViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
self.isShowPopup = true
|
||||
if isLoadMore {
|
||||
@@ -107,7 +107,7 @@ final class OriginalWorkViewModel: ObservableObject {
|
||||
} else {
|
||||
self.isLoading = false
|
||||
}
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,11 @@ struct ChatRoomView: View {
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Text(viewModel.characterType.rawValue)
|
||||
Text(
|
||||
viewModel.characterType == .Clone
|
||||
? I18n.Chat.Character.typeClone
|
||||
: I18n.Chat.Character.typeCharacter
|
||||
)
|
||||
.appFont(size: 10, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 4)
|
||||
@@ -100,8 +104,8 @@ struct ChatRoomView: View {
|
||||
|
||||
Text(
|
||||
viewModel.characterType == .Character
|
||||
? "보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요.\n※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다."
|
||||
: "AI Clone은 크리에이터의 정보를 기반으로 대화하지만, 모든 정보를 완벽하게 반영하거나 실제 대화와 일치하지 않을 수 있습니다."
|
||||
? I18n.Chat.Room.noticeForCharacter
|
||||
: I18n.Chat.Room.noticeForClone
|
||||
)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
@@ -186,7 +190,7 @@ struct ChatRoomView: View {
|
||||
HStack(spacing: 0) {
|
||||
ZStack(alignment: .leading) {
|
||||
if viewModel.messageText.isEmpty {
|
||||
Text("메시지를 입력하세요.")
|
||||
Text(I18n.Chat.Room.messagePlaceholder)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
}
|
||||
@@ -289,7 +293,7 @@ struct ChatRoomView: View {
|
||||
ActivityIndicatorView()
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
Text("대화 초기화 중...")
|
||||
Text(I18n.Chat.Room.resettingMessage)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
|
||||
@@ -19,7 +19,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
@Published var chatRoomBgImageId: Int = 0
|
||||
@Published private(set) var characterId: Int64 = 0
|
||||
@Published private(set) var characterProfileUrl: String = ""
|
||||
@Published private(set) var characterName: String = "Character Name"
|
||||
@Published private(set) var characterName: String = I18n.Chat.Room.defaultCharacterName
|
||||
@Published private(set) var characterType: CharacterType = .Character
|
||||
@Published private(set) var chatRoomBgImageUrl: String? = nil
|
||||
@Published private(set) var roomId: Int = 0 {
|
||||
@@ -113,7 +113,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
self.showSendingMessage = false // 실패 시 복구
|
||||
self.errorMessage = error.localizedDescription
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
@@ -127,14 +127,13 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self.messages.append(contentsOf: data.messages)
|
||||
self.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
} else {
|
||||
self.errorMessage = decoded.message ??
|
||||
"다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = decoded.message ?? I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
self.showSendingMessage = false // 성공 시 종료
|
||||
} catch {
|
||||
self.showSendingMessage = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -183,7 +182,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -192,7 +191,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.isLoading = false
|
||||
} catch {
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -260,7 +259,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -269,7 +268,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.isLoading = false
|
||||
} catch {
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -304,7 +303,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -313,7 +312,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.isLoading = false
|
||||
} catch {
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -348,14 +347,14 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
ERROR_LOG(String(describing: error))
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -381,7 +380,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
|
||||
private func resetData() {
|
||||
characterProfileUrl = ""
|
||||
characterName = "Character Name"
|
||||
characterName = I18n.Chat.Room.defaultCharacterName
|
||||
characterType = .Character
|
||||
chatRoomBgImageUrl = nil
|
||||
roomId = 0
|
||||
@@ -427,7 +426,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -436,7 +435,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.isLoading = false
|
||||
} catch {
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ struct AiMessageItemView: View {
|
||||
.foregroundColor(.button)
|
||||
}
|
||||
|
||||
Text("눌러서 잠금해제")
|
||||
Text(I18n.Chat.Room.unlockImagePrompt)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ struct TypingIndicatorItemView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityLabel(Text("입력 중"))
|
||||
.accessibilityLabel(Text(I18n.Chat.Room.typingAccessibilityLabel))
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 8)
|
||||
|
||||
@@ -23,7 +23,7 @@ struct ChatQuotaNoticeItemView: View {
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("기다리면 무료 이용이 가능합니다.")
|
||||
Text(I18n.Chat.Room.quotaWaitForFreeNotice)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
@@ -39,7 +39,7 @@ struct ChatQuotaNoticeItemView: View {
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
|
||||
Text("(채팅 12개) 바로 대화 시작")
|
||||
Text(I18n.Chat.Room.quotaPurchaseAction(chatCount: 12))
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
.padding(.leading, 4)
|
||||
|
||||
@@ -26,7 +26,7 @@ struct ChatBgSelectionView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "배경 이미지 선택")) {
|
||||
DetailNavigationBar(title: I18n.Chat.Room.backgroundSelectionTitle) {
|
||||
isShowing = false
|
||||
}
|
||||
// 갤러리 그리드
|
||||
@@ -79,7 +79,7 @@ struct ChatBgSelectionView: View {
|
||||
}
|
||||
|
||||
if selectedBgImageId == item.id {
|
||||
Text("현재 배경")
|
||||
Text(I18n.Chat.Room.currentBackground)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 6)
|
||||
|
||||
@@ -75,14 +75,14 @@ final class ChatBgSelectionViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
ERROR_LOG(String(describing: error))
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ChatSettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "대화 설정")) {
|
||||
DetailNavigationBar(title: I18n.Chat.Room.settingsTitle) {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ChatSettingsView: View {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
Toggle(isOn: $isHideBg) {
|
||||
Text("배경 이미지 끄기")
|
||||
Text(I18n.Chat.Room.hideBackgroundImage)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
@@ -42,7 +42,7 @@ struct ChatSettingsView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text("배경 이미지 변경")
|
||||
Text(I18n.Chat.Room.changeBackgroundImage)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 24)
|
||||
@@ -61,16 +61,16 @@ struct ChatSettingsView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("대화 초기화")
|
||||
Text(I18n.Chat.Room.resetConversationTitle)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("⚠️ ")
|
||||
Text(I18n.Chat.Room.resetWarningPrefix)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
|
||||
Text("지금까지의 대화가 모두 초기화 되고, 이용자가 새로운 캐릭터가 되어 새롭게 대화를 시작합니다.")
|
||||
Text(I18n.Chat.Room.resetWarningDescription)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -14,7 +14,7 @@ struct TalkView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
if viewModel.talkRooms.isEmpty {
|
||||
Text("대화 중인 톡이 없습니다")
|
||||
Text(I18n.Chat.Talk.emptyMessage)
|
||||
.appFont(size: 20, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
|
||||
@@ -61,7 +61,7 @@ final class TalkViewModel: ObservableObject {
|
||||
if case let .failure(error) = completion {
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
DispatchQueue.main.async {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} else {
|
||||
@@ -90,16 +90,15 @@ final class TalkViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ContentAllByThemeView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -34,7 +34,7 @@ struct ContentAllByThemeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
Text(I18n.Content.Sort.priceHigh)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -46,7 +46,7 @@ struct ContentAllByThemeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
Text(I18n.Content.Sort.priceLow)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -64,7 +64,7 @@ struct ContentAllByThemeView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -73,7 +73,7 @@ struct ContentAllByThemeView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -78,13 +78,13 @@ final class ContentAllByThemeViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@ struct ContentAllView: View {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: isFree ? String(localized: "무료 콘텐츠 전체") : isPointAvailableOnly ? String(localized: "포인트 대여 전체") : String(localized: "콘텐츠 전체"))
|
||||
DetailNavigationBar(
|
||||
title: isFree ?
|
||||
I18n.Content.All.freeTitle :
|
||||
isPointAvailableOnly ? I18n.Content.All.pointRentalTitle : I18n.Content.All.title
|
||||
)
|
||||
|
||||
if !viewModel.themeList.isEmpty {
|
||||
ContentMainContentThemeView(
|
||||
@@ -32,7 +36,7 @@ struct ContentAllView: View {
|
||||
HStack(spacing: 12) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -44,7 +48,7 @@ struct ContentAllView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("인기순")
|
||||
Text(I18n.Content.Sort.popularity)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
|
||||
@@ -41,7 +41,7 @@ struct ContentNewAllItemView: View {
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ struct ContentNewAllView: View {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
DetailNavigationBar(title: isFree ? "최신 무료 콘텐츠" : "최신 콘텐츠")
|
||||
DetailNavigationBar(title: isFree ? I18n.Content.New.freeTitle : I18n.Content.New.title)
|
||||
|
||||
Text("※ 최근 2주간 등록된 새로운 콘텐츠 입니다.")
|
||||
Text(I18n.Content.New.recentTwoWeeksNotice)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(.graybb)
|
||||
.padding(.horizontal, 13.3)
|
||||
@@ -37,7 +37,7 @@ struct ContentNewAllView: View {
|
||||
)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -46,7 +46,7 @@ struct ContentNewAllView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -17,14 +17,14 @@ struct ContentRankingAllView: View {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "인기 콘텐츠")
|
||||
DetailNavigationBar(title: I18n.Content.Ranking.title)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text("\(viewModel.dateString)")
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.")
|
||||
Text(I18n.Content.Ranking.weeklyUpdateNotice)
|
||||
.appFont(size: 13.3, weight: .light)
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
}
|
||||
@@ -82,7 +82,7 @@ struct ContentRankingAllView: View {
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
Text(I18n.Common.points)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
@@ -116,7 +116,7 @@ struct ContentRankingAllView: View {
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.horizontal, 5.3)
|
||||
|
||||
@@ -71,13 +71,13 @@ final class ContentRankingAllViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -109,13 +109,13 @@ final class ContentRankingAllViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ struct ContentListCategoryView: View {
|
||||
|
||||
ContentListCategoryView(
|
||||
categoryList: [
|
||||
GetCategoryListResponse(categoryId: 0, category: "전체"),
|
||||
GetCategoryListResponse(categoryId: 0, category: I18n.Category.all),
|
||||
GetCategoryListResponse(categoryId: 1, category: "test"),
|
||||
GetCategoryListResponse(categoryId: 0, category: "test2")
|
||||
],
|
||||
selectCategory: { _ in },
|
||||
selectedCategory: .constant("전체")
|
||||
selectedCategory: .constant(I18n.Category.all)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import Foundation
|
||||
import Moya
|
||||
|
||||
enum ContentApi {
|
||||
case getAudioContentList(userId: Int, categoryId: Int, isAdultContentVisible: Bool, page: Int, size: Int, sort: ContentListViewModel.Sort)
|
||||
case getAudioContentList(userId: Int, categoryId: Int, page: Int, size: Int, sort: ContentListViewModel.Sort)
|
||||
case getAudioContentDetail(audioContentId: Int)
|
||||
case likeContent(request: PutAudioContentLikeRequest)
|
||||
case registerComment(request: RegisterAudioContentCommentRequest)
|
||||
@@ -25,51 +25,51 @@ enum ContentApi {
|
||||
case getNewContentUploadCreatorList
|
||||
case getMainBannerList
|
||||
case getMainOrderList
|
||||
case getNewContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getCurationList(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int)
|
||||
case getNewContentOfTheme(theme: String)
|
||||
case getCurationList(page: Int, size: Int)
|
||||
case donation(request: AudioContentDonationRequest)
|
||||
case modifyComment(request: ModifyCommentRequest)
|
||||
case getNewContentThemeList(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getNewContentAllOfTheme(isFree: Bool, theme: String, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int)
|
||||
case getAudioContentListByCurationId(curationId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
|
||||
case getContentRanking(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sortType: String)
|
||||
case getNewContentThemeList
|
||||
case getNewContentAllOfTheme(isFree: Bool, theme: String, page: Int, size: Int)
|
||||
case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
|
||||
case getContentRanking(page: Int, size: Int, sortType: String)
|
||||
case getContentRankingSortType
|
||||
case pinContent(contentId: Int)
|
||||
case unpinContent(contentId: Int)
|
||||
case getAudioContentByTheme(themeId: Int, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort)
|
||||
case getAudioContentByTheme(themeId: Int, page: Int, size: Int, sort: ContentAllByThemeViewModel.Sort)
|
||||
case generateUrl(contentId: Int)
|
||||
|
||||
case getContentMainHome(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getPopularContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainHomeContentRanking(isAdultContentVisible: Bool, contentType: ContentType, sortType: String)
|
||||
case getContentMainHome
|
||||
case getPopularContentByCreator(creatorId: Int)
|
||||
case getContentMainHomeContentRanking(sortType: String)
|
||||
|
||||
case getContentMainSeries(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getRecommendSeriesListByGenre(genreId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getRecommendSeriesByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getCompletedSeries(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int)
|
||||
case getContentMainSeries
|
||||
case getRecommendSeriesListByGenre(genreId: Int)
|
||||
case getRecommendSeriesByCreator(creatorId: Int)
|
||||
case getCompletedSeries(page: Int, size: Int)
|
||||
|
||||
case getContentMainContent(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainNewContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getDailyContentRanking(sortType: String, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getRecommendContentByTag(tag: String, contentType: ContentType)
|
||||
case getContentMainContentPopularContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainContent
|
||||
case getContentMainNewContentOfTheme(theme: String)
|
||||
case getDailyContentRanking(sortType: String)
|
||||
case getRecommendContentByTag(tag: String)
|
||||
case getContentMainContentPopularContentByCreator(creatorId: Int)
|
||||
|
||||
case getContentMainAlarm(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainAlarmAll(theme: String, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int)
|
||||
case getContentMainAlarm
|
||||
case getContentMainAlarmAll(theme: String, page: Int, size: Int)
|
||||
|
||||
case getContentMainAsmr(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getPopularAsmrContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainAsmr
|
||||
case getPopularAsmrContentByCreator(creatorId: Int)
|
||||
|
||||
case getContentMainReplay(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getPopularReplayContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainReplay
|
||||
case getPopularReplayContentByCreator(creatorId: Int)
|
||||
|
||||
case getContentMainFree(isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getIntroduceCreatorList(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int)
|
||||
case getNewFreeContentOfTheme(theme: String, isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int)
|
||||
case getPopularFreeContentByCreator(creatorId: Int, isAdultContentVisible: Bool, contentType: ContentType)
|
||||
case getContentMainFree
|
||||
case getIntroduceCreatorList(page: Int, size: Int)
|
||||
case getNewFreeContentOfTheme(theme: String, page: Int, size: Int)
|
||||
case getPopularFreeContentByCreator(creatorId: Int)
|
||||
|
||||
case getAllAudioContents(isAdultContentVisible: Bool, contentType: ContentType, page: Int, size: Int, isFree: Bool?, isPointAvailableOnly: Bool?, sortType: ContentAllViewModel.Sort = .NEWEST, theme: String? = nil)
|
||||
case getAudioContentActiveThemeList(isAdultContentVisible: Bool, contentType: ContentType, isFree: Bool?, isPointAvailableOnly: Bool?)
|
||||
case getAllAudioContents(page: Int, size: Int, isFree: Bool?, isPointAvailableOnly: Bool?, sortType: ContentAllViewModel.Sort = .NEWEST, theme: String? = nil)
|
||||
case getAudioContentActiveThemeList(isFree: Bool?, isPointAvailableOnly: Bool?)
|
||||
}
|
||||
|
||||
extension ContentApi: TargetType {
|
||||
@@ -145,7 +145,7 @@ extension ContentApi: TargetType {
|
||||
case .getNewContentAllOfTheme:
|
||||
return "/audio-content/main/new/all"
|
||||
|
||||
case .getAudioContentListByCurationId(let curationId, _, _, _, _, _):
|
||||
case .getAudioContentListByCurationId(let curationId, _, _, _):
|
||||
return "/audio-content/curation/\(curationId)"
|
||||
|
||||
case .getContentRanking:
|
||||
@@ -160,7 +160,7 @@ extension ContentApi: TargetType {
|
||||
case .unpinContent(let contentId):
|
||||
return "/audio-content/unpin-at-the-top/\(contentId)"
|
||||
|
||||
case .getAudioContentByTheme(let themeId, _, _, _, _, _):
|
||||
case .getAudioContentByTheme(let themeId, _, _, _):
|
||||
return "/audio-content/theme/\(themeId)/content"
|
||||
|
||||
case .generateUrl(let contentId):
|
||||
@@ -273,11 +273,10 @@ extension ContentApi: TargetType {
|
||||
|
||||
var task: Moya.Task {
|
||||
switch self {
|
||||
case .getAudioContentList(let userId, let categoryId, let isAdultContentVisible, let page, let size, let sort):
|
||||
case .getAudioContentList(let userId, let categoryId, let page, let size, let sort):
|
||||
let parameters = [
|
||||
"creator-id": userId,
|
||||
"category-id": categoryId,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"page": page - 1,
|
||||
"size": size,
|
||||
"sort-type": sort
|
||||
@@ -339,11 +338,9 @@ extension ContentApi: TargetType {
|
||||
case .deleteAudioContent:
|
||||
return .requestPlain
|
||||
|
||||
case .getNewContentOfTheme(let theme, let isAdultContentVisible, let contentType):
|
||||
case .getNewContentOfTheme(let theme):
|
||||
let parameters = [
|
||||
"theme": theme,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"theme": theme
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
@@ -354,30 +351,21 @@ extension ContentApi: TargetType {
|
||||
case .modifyComment(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .getNewContentThemeList(let isAdultContentVisible, let contentType):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
] as [String : Any]
|
||||
case .getNewContentThemeList:
|
||||
return .requestPlain
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getNewContentAllOfTheme(let isFree, let theme, let isAdultContentVisible, let contentType, let page, let size):
|
||||
case .getNewContentAllOfTheme(let isFree, let theme, let page, let size):
|
||||
let parameters = [
|
||||
"isFree": isFree,
|
||||
"theme": theme,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getAudioContentListByCurationId(_, let isAdultContentVisible, let contentType, let page, let size, let sort):
|
||||
case .getAudioContentListByCurationId(_, let page, let size, let sort):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size,
|
||||
"sort-type": sort
|
||||
@@ -385,10 +373,8 @@ extension ContentApi: TargetType {
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentRanking(let isAdultContentVisible, let contentType, let page, let size, let sortType):
|
||||
case .getContentRanking(let page, let size, let sortType):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size,
|
||||
"sort-type": sortType
|
||||
@@ -399,10 +385,8 @@ extension ContentApi: TargetType {
|
||||
case .getContentRankingSortType:
|
||||
return .requestPlain
|
||||
|
||||
case .getCurationList(let isAdultContentVisible, let contentType, let page, let size):
|
||||
case .getCurationList(let page, let size):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
@@ -412,10 +396,8 @@ extension ContentApi: TargetType {
|
||||
case .pinContent, .unpinContent:
|
||||
return .requestPlain
|
||||
|
||||
case .getAudioContentByTheme(_, let isAdultContentVisible, let contentType, let page, let size, let sort):
|
||||
case .getAudioContentByTheme(_, let page, let size, let sort):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size,
|
||||
"sort-type": sort
|
||||
@@ -423,141 +405,109 @@ extension ContentApi: TargetType {
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentMainHome(let isAdultContentVisible, let contentType),
|
||||
.getContentMainSeries(let isAdultContentVisible, let contentType),
|
||||
.getContentMainContent(let isAdultContentVisible, let contentType),
|
||||
.getContentMainAlarm(let isAdultContentVisible, let contentType),
|
||||
.getContentMainAsmr(let isAdultContentVisible, let contentType),
|
||||
.getContentMainReplay(let isAdultContentVisible, let contentType),
|
||||
.getContentMainFree(let isAdultContentVisible, let contentType):
|
||||
case .getContentMainHome,
|
||||
.getContentMainSeries,
|
||||
.getContentMainContent,
|
||||
.getContentMainAlarm,
|
||||
.getContentMainAsmr,
|
||||
.getContentMainReplay,
|
||||
.getContentMainFree:
|
||||
return .requestPlain
|
||||
|
||||
case .getRecommendSeriesListByGenre(let genreId):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"genreId": genreId
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getRecommendSeriesListByGenre(let genreId, let isAdultContentVisible, let contentType):
|
||||
case .getPopularContentByCreator(let creatorId):
|
||||
let parameters = [
|
||||
"genreId": genreId,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"creatorId": creatorId
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getPopularContentByCreator(let creatorId, let isAdultContentVisible, let contentType):
|
||||
case .getContentMainHomeContentRanking(let sortType):
|
||||
let parameters = [
|
||||
"creatorId": creatorId,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"sort-type": sortType
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentMainHomeContentRanking(let isAdultContentVisible, let contentType, let sortType):
|
||||
case .getRecommendSeriesByCreator(let creatorId):
|
||||
let parameters = [
|
||||
"sort-type": sortType,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"creatorId": creatorId
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getRecommendSeriesByCreator(let creatorId, let isAdultContentVisible, let contentType):
|
||||
case .getContentMainNewContentOfTheme(let theme):
|
||||
let parameters = [
|
||||
"creatorId": creatorId,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"theme": theme
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getDailyContentRanking(let sortType):
|
||||
let parameters = [
|
||||
"sort-type": sortType
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getRecommendContentByTag(let tag):
|
||||
let parameters = [
|
||||
"tag": tag
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentMainContentPopularContentByCreator(let creatorId):
|
||||
let parameters = [
|
||||
"creatorId": creatorId
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentMainNewContentOfTheme(let theme, let isAdultContentVisible, let contentType):
|
||||
case .getNewFreeContentOfTheme(let theme, let page, let size):
|
||||
let parameters = [
|
||||
"theme": theme,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getDailyContentRanking(let sortType, let isAdultContentVisible, let contentType):
|
||||
let parameters = [
|
||||
"sort-type": sortType,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getRecommendContentByTag(let tag, let contentType):
|
||||
let parameters = [
|
||||
"tag": tag,
|
||||
"contentType": contentType
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentMainContentPopularContentByCreator(let creatorId, let isAdultContentVisible, let contentType):
|
||||
let parameters = [
|
||||
"creatorId": creatorId,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getNewFreeContentOfTheme(let theme, let isAdultContentVisible, let contentType, let page, let size):
|
||||
case .getContentMainAlarmAll(let theme, let page, let size):
|
||||
let parameters = [
|
||||
"theme": theme,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentMainAlarmAll(let theme, let isAdultContentVisible, let contentType, let page, let size):
|
||||
case .getIntroduceCreatorList(let page, let size):
|
||||
let parameters = [
|
||||
"theme": theme,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getIntroduceCreatorList(let isAdultContentVisible, let contentType, let page, let size):
|
||||
case .getPopularAsmrContentByCreator(let creatorId),
|
||||
.getPopularReplayContentByCreator(let creatorId),
|
||||
.getPopularFreeContentByCreator(let creatorId):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
"creatorId": creatorId
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getPopularAsmrContentByCreator(let creatorId, let isAdultContentVisible, let contentType),
|
||||
.getPopularReplayContentByCreator(let creatorId, let isAdultContentVisible, let contentType),
|
||||
.getPopularFreeContentByCreator(let creatorId, let isAdultContentVisible, let contentType):
|
||||
case .getCompletedSeries(let page, let size):
|
||||
let parameters = [
|
||||
"creatorId": creatorId,
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType
|
||||
] as [String : Any]
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getCompletedSeries(let isAdultContentVisible, let contentType, let page, let size):
|
||||
let parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getAllAudioContents(let isAdultContentVisible, let contentType, let page, let size, let isFree, let isPointAvailableOnly, let sortType, let theme):
|
||||
case .getAllAudioContents(let page, let size, let isFree, let isPointAvailableOnly, let sortType, let theme):
|
||||
var parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
"sort-type": sortType,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
@@ -577,11 +527,8 @@ extension ContentApi: TargetType {
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getAudioContentActiveThemeList(let isAdultContentVisible, let contentType, let isFree, let isPointAvailableOnly):
|
||||
var parameters = [
|
||||
"isAdultContentVisible": isAdultContentVisible,
|
||||
"contentType": contentType,
|
||||
] as [String : Any]
|
||||
case .getAudioContentActiveThemeList(let isFree, let isPointAvailableOnly):
|
||||
var parameters = [String : Any]()
|
||||
|
||||
if let isFree = isFree {
|
||||
parameters["isFree"] = isFree
|
||||
|
||||
@@ -29,7 +29,7 @@ struct ContentListItemView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 8) {
|
||||
if item.isScheduledToOpen {
|
||||
Text("오픈예정")
|
||||
Text(I18n.Common.openScheduled)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(2.6)
|
||||
@@ -52,7 +52,7 @@ struct ContentListItemView: View {
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
Text(I18n.Common.points)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
@@ -98,7 +98,7 @@ struct ContentListItemView: View {
|
||||
Spacer()
|
||||
|
||||
if item.isOwned {
|
||||
Text("소장중")
|
||||
Text(I18n.Content.Status.owned)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.gray11)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -106,7 +106,7 @@ struct ContentListItemView: View {
|
||||
.background(Color(hex: "b1ef2c"))
|
||||
.cornerRadius(2.6)
|
||||
} else if item.isRented {
|
||||
Text("대여중")
|
||||
Text(I18n.Content.Status.rented)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -114,7 +114,7 @@ struct ContentListItemView: View {
|
||||
.background(Color(hex: "660fd4"))
|
||||
.cornerRadius(2.6)
|
||||
} else if item.isSoldOut {
|
||||
Text("Sold Out")
|
||||
Text(I18n.Content.Status.soldOut)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -135,7 +135,7 @@ struct ContentListItemView: View {
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ContentListView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("콘텐츠 전체보기")
|
||||
Text(I18n.Content.List.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
@@ -46,7 +46,7 @@ struct ContentListView: View {
|
||||
}
|
||||
|
||||
if userId == UserDefaults.int(forKey: .userId) {
|
||||
Text("새로운 콘텐츠 등록하기")
|
||||
Text(I18n.Content.List.createNewContentAction)
|
||||
.appFont(size: 15, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 17)
|
||||
@@ -61,7 +61,7 @@ struct ContentListView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -73,7 +73,7 @@ struct ContentListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
Text(I18n.Content.Sort.priceHigh)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -85,7 +85,7 @@ struct ContentListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
Text(I18n.Content.Sort.priceLow)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -103,7 +103,7 @@ struct ContentListView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -112,7 +112,7 @@ struct ContentListView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -200,7 +200,7 @@ extension ContentPlayManager {
|
||||
}
|
||||
|
||||
private func showError() {
|
||||
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Content.Playback.playFailed
|
||||
self.isShowPopup = true
|
||||
self.resetAudioData()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ final class ContentRepository {
|
||||
.getAudioContentList(
|
||||
userId: userId,
|
||||
categoryId: categoryId,
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
page: page,
|
||||
size: size,
|
||||
sort: sort)
|
||||
@@ -89,19 +88,13 @@ final class ContentRepository {
|
||||
|
||||
func getNewContentOfTheme(theme: String) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(
|
||||
.getNewContentOfTheme(
|
||||
theme: theme,
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL
|
||||
)
|
||||
.getNewContentOfTheme(theme: theme)
|
||||
)
|
||||
}
|
||||
|
||||
func getCurationList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(
|
||||
.getCurationList(
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
page: page,
|
||||
size: size
|
||||
)
|
||||
@@ -117,12 +110,7 @@ final class ContentRepository {
|
||||
}
|
||||
|
||||
func getNewContentThemeList() -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(
|
||||
.getNewContentThemeList(
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL
|
||||
)
|
||||
)
|
||||
return api.requestPublisher(.getNewContentThemeList)
|
||||
}
|
||||
|
||||
func getNewContentAllOfTheme(isFree: Bool, theme: String, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
@@ -130,8 +118,6 @@ final class ContentRepository {
|
||||
.getNewContentAllOfTheme(
|
||||
isFree: isFree,
|
||||
theme: theme,
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
page: page,
|
||||
size: size
|
||||
)
|
||||
@@ -142,8 +128,6 @@ final class ContentRepository {
|
||||
return api.requestPublisher(
|
||||
.getAudioContentListByCurationId(
|
||||
curationId: curationId,
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
page: page,
|
||||
size: size,
|
||||
sort: sort
|
||||
@@ -158,8 +142,6 @@ final class ContentRepository {
|
||||
func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(
|
||||
.getContentRanking(
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
page: page,
|
||||
size: size,
|
||||
sortType: sortType
|
||||
@@ -183,8 +165,6 @@ final class ContentRepository {
|
||||
return api.requestPublisher(
|
||||
.getAudioContentByTheme(
|
||||
themeId: themeId,
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
page: page,
|
||||
size: size,
|
||||
sort: sort
|
||||
@@ -206,8 +186,6 @@ final class ContentRepository {
|
||||
) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(
|
||||
.getAllAudioContents(
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
page: page,
|
||||
size: size,
|
||||
isFree: isFree,
|
||||
@@ -221,8 +199,6 @@ final class ContentRepository {
|
||||
func getAudioContentActiveThemeList(isFree: Bool, isPointAvailableOnly: Bool) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(
|
||||
.getAudioContentActiveThemeList(
|
||||
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
|
||||
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL,
|
||||
isFree: isFree,
|
||||
isPointAvailableOnly: isPointAvailableOnly
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ struct ContentCreateSelectThemeView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("테마 선택")
|
||||
Text(I18n.CreateContent.selectTheme)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@ final class ContentCreateSelectThemeViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ContentCreateView: View {
|
||||
@Environment(\.locale) private var locale
|
||||
|
||||
@StateObject var keyboardHandler = KeyboardHandler()
|
||||
@StateObject private var viewModel = ContentCreateViewModel()
|
||||
@@ -24,15 +25,23 @@ struct ContentCreateView: View {
|
||||
@State private var isShowSelectTimeView = false
|
||||
|
||||
var body: some View {
|
||||
let normalizedCountryCode = UserDefaults
|
||||
.string(forKey: .countryCode)
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.uppercased()
|
||||
let isKoreanCountry = normalizedCountryCode.isEmpty || normalizedCountryCode == "KR"
|
||||
let isAdultContentVisible = UserDefaults.isAdultContentVisible()
|
||||
let shouldShowAdultSetting = isAdultContentVisible && (!isKoreanCountry || UserDefaults.bool(forKey: .auth))
|
||||
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
GeometryReader { proxy in
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "콘텐츠 등록"))
|
||||
DetailNavigationBar(title: I18n.CreateContent.registerTitle)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
Text("썸네일")
|
||||
Text(I18n.CreateContent.thumbnail)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -65,7 +74,7 @@ struct ContentCreateView: View {
|
||||
.frame(alignment: .bottomTrailing)
|
||||
.onTapGesture { isShowPhotoPicker = true }
|
||||
|
||||
Text("등록")
|
||||
Text(I18n.CreateContent.registerSectionTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -95,12 +104,12 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 26.7)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("제목")
|
||||
Text(I18n.CreateContent.titleLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
TextField("제목을 입력하세요", text: $viewModel.title)
|
||||
TextField(I18n.CreateContent.titlePlaceholder, text: $viewModel.title)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -113,16 +122,16 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("내용")
|
||||
Text(I18n.CreateContent.contentLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.detail.count)자")
|
||||
Text(I18n.CreateContent.characterCount(viewModel.detail.count))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.mainRed)
|
||||
Text(" / 최대 500자")
|
||||
Text(I18n.CreateContent.max500CharactersSuffix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
@@ -138,7 +147,7 @@ struct ContentCreateView: View {
|
||||
.cornerRadius(6.7)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("테마")
|
||||
Text(I18n.CreateContent.themeLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -180,13 +189,13 @@ struct ContentCreateView: View {
|
||||
hideKeyboard()
|
||||
}
|
||||
|
||||
Text("태그")
|
||||
Text(I18n.CreateContent.tagLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 26.7)
|
||||
|
||||
TextField("예: #연애 #커버곡", text: $viewModel.hashtags)
|
||||
TextField(I18n.CreateContent.tagPlaceholderExample, text: $viewModel.hashtags)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -207,7 +216,7 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 26.7)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("가격 설정")
|
||||
Text(I18n.CreateContent.priceSettingsTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -228,7 +237,7 @@ struct ContentCreateView: View {
|
||||
|
||||
if !viewModel.isFree {
|
||||
VStack(spacing: 13.3) {
|
||||
Text("소장 설정")
|
||||
Text(I18n.CreateContent.ownershipSettingsTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -256,13 +265,13 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text(viewModel.purchaseOption == .RENT_ONLY ? "대여 가격" : "소장 가격")
|
||||
Text(viewModel.purchaseOption == .RENT_ONLY ? I18n.CreateContent.rentPriceLabel : I18n.CreateContent.purchasePriceLabel)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("가격을 입력하세요(5캔 이상)", text: $viewModel.priceString)
|
||||
TextField(I18n.CreateContent.priceInputPlaceholder, text: $viewModel.priceString)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
@@ -273,7 +282,7 @@ struct ContentCreateView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("캔")
|
||||
Text(I18n.CreateContent.canUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
@@ -288,18 +297,18 @@ struct ContentCreateView: View {
|
||||
.frame(height: 1)
|
||||
.padding(.top, 11)
|
||||
|
||||
Text("※ 이용기간 대여 (5일) | 소장 (서비스종료시까지)")
|
||||
Text(I18n.CreateContent.rentalPeriodNotice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("※ 대여가격은 소장가격의 70%로 자동 반영")
|
||||
Text(I18n.CreateContent.rentalPriceAutoNotice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text("※ 콘텐츠의 최소금액은 5캔 입니다")
|
||||
Text(I18n.CreateContent.minimumPriceNotice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -308,7 +317,7 @@ struct ContentCreateView: View {
|
||||
|
||||
if viewModel.price > 0 && viewModel.purchaseOption != .RENT_ONLY {
|
||||
VStack(spacing: 13.3) {
|
||||
Text("한정판 설정")
|
||||
Text(I18n.CreateContent.limitedEditionSettingsTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -328,7 +337,7 @@ struct ContentCreateView: View {
|
||||
}
|
||||
|
||||
if viewModel.isLimited {
|
||||
TextField("한정판 개수를 입력하세요", text: $viewModel.limitedString)
|
||||
TextField(I18n.CreateContent.limitedCountPlaceholder, text: $viewModel.limitedString)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
@@ -345,7 +354,7 @@ struct ContentCreateView: View {
|
||||
}
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("포인트 사용")
|
||||
Text(I18n.CreateContent.pointUsageTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -367,7 +376,7 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 26.7)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("미리듣기")
|
||||
Text(I18n.CreateContent.previewTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -390,19 +399,19 @@ struct ContentCreateView: View {
|
||||
|
||||
if viewModel.isGeneratePreview {
|
||||
VStack(spacing: 10) {
|
||||
Text("미리듣기 시간 설정")
|
||||
Text(I18n.CreateContent.previewTimeSettingsTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text("미리듣기 시간을 직접 설정하지 않으면 콘텐츠 앞부분 15초가 자동으로 설정됩니다. 미리듣기의 시간제한은 없습니다.")
|
||||
Text(I18n.CreateContent.previewTimeGuide)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
VStack(spacing: 5.3) {
|
||||
Text("시작 시간")
|
||||
Text(I18n.CreateContent.previewStartTimeLabel)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -421,7 +430,7 @@ struct ContentCreateView: View {
|
||||
}
|
||||
|
||||
VStack(spacing: 5.3) {
|
||||
Text("종료 시간")
|
||||
Text(I18n.CreateContent.previewEndTimeLabel)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -448,37 +457,39 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("연령 제한")
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(title: I18n.CreateContent.allAges, isChecked: !viewModel.isAdult) {
|
||||
if viewModel.isAdult {
|
||||
viewModel.isAdult = false
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(title: I18n.CreateContent.over19, isChecked: viewModel.isAdult) {
|
||||
if !viewModel.isAdult {
|
||||
viewModel.isAdult = true
|
||||
if shouldShowAdultSetting {
|
||||
VStack(spacing: 13.3) {
|
||||
Text(I18n.CreateContent.ageRestrictionTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(title: I18n.CreateContent.allAges, isChecked: !viewModel.isAdult) {
|
||||
if viewModel.isAdult {
|
||||
viewModel.isAdult = false
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(title: I18n.CreateContent.over19, isChecked: viewModel.isAdult) {
|
||||
if !viewModel.isAdult {
|
||||
viewModel.isAdult = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(I18n.CreateContent.adultLegalNotice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.mainRed3)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 13.3)
|
||||
}
|
||||
|
||||
Text("성인콘텐츠를 전체관람가로 등록할 시 발생하는 법적 책임은 회사와 상관없이 콘텐츠를 등록한 본인에게 있습니다.\n콘텐츠 내용은 물론 제목도 19금 여부를 체크해 주시기 바랍니다.")
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.mainRed3)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 13.3)
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("댓글 가능 여부")
|
||||
Text(I18n.CreateContent.commentAvailabilityTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -523,7 +534,7 @@ struct ContentCreateView: View {
|
||||
if viewModel.isActiveReservation {
|
||||
HStack(spacing: 13.3) {
|
||||
VStack(alignment: .leading, spacing: 6.7) {
|
||||
Text("예약 날짜")
|
||||
Text(I18n.CreateContent.reservationDateLabel)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -531,7 +542,7 @@ struct ContentCreateView: View {
|
||||
hideKeyboard()
|
||||
self.isShowSelectDateView = true
|
||||
}) {
|
||||
Text(viewModel.releaseDateString)
|
||||
Text(viewModel.releaseDate.convertDateFormat(dateFormat: "yyyy.MM.dd", locale: locale))
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -544,7 +555,7 @@ struct ContentCreateView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 6.7) {
|
||||
Text("예약 시간")
|
||||
Text(I18n.CreateContent.reservationTimeLabel)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -552,7 +563,7 @@ struct ContentCreateView: View {
|
||||
hideKeyboard()
|
||||
self.isShowSelectTimeView = true
|
||||
}) {
|
||||
Text(viewModel.releaseTimeString)
|
||||
Text(viewModel.releaseTime.convertDateFormat(dateFormat: "a hh:mm", locale: locale))
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -576,7 +587,7 @@ struct ContentCreateView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("등록")
|
||||
Text(I18n.CreateContent.registerButton)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.frame(height: 50)
|
||||
|
||||
@@ -154,7 +154,7 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
mimeType: "image/*")
|
||||
)
|
||||
} else {
|
||||
errorMessage = "커버이미지를 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.coverImageUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
@@ -176,19 +176,19 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
)
|
||||
)
|
||||
} else {
|
||||
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.contentFileUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
} else {
|
||||
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.contentFileUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
} else {
|
||||
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.contentFileUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
@@ -219,19 +219,19 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -240,37 +240,37 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
|
||||
private func validateData() -> Bool {
|
||||
if title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
errorMessage = "제목을 입력해 주세요."
|
||||
errorMessage = I18n.CreateContent.titleRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5 {
|
||||
errorMessage = "내용을 5자 이상 입력해 주세요."
|
||||
errorMessage = I18n.CreateContent.detailMinLengthRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if theme == nil {
|
||||
errorMessage = "테마를 선택해 주세요."
|
||||
errorMessage = I18n.CreateContent.themeRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if coverImage == nil {
|
||||
errorMessage = "커버이미지를 선택해 주세요."
|
||||
errorMessage = I18n.CreateContent.coverImageRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if selectedFileUrl == nil {
|
||||
errorMessage = "오디오 콘텐츠를 선택해 주세요."
|
||||
errorMessage = I18n.CreateContent.audioContentRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if !isFree && price < 5 {
|
||||
errorMessage = "콘텐츠의 최소금액은 5캔 입니다."
|
||||
errorMessage = I18n.CreateContent.minimumPriceRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
@@ -278,14 +278,14 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
if previewStartTime.count > 0 && previewEndTime.count > 0 {
|
||||
let startTimeArray = previewStartTime.split(separator: ":")
|
||||
if startTimeArray.count != 3 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
for time in startTimeArray {
|
||||
if time.count != 2 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
@@ -293,14 +293,14 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
|
||||
let endTimeArray = previewStartTime.split(separator: ":")
|
||||
if endTimeArray.count != 3 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
for time in endTimeArray {
|
||||
if time.count != 2 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
@@ -308,13 +308,13 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
|
||||
let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime)
|
||||
if timeDifference < 15.0 {
|
||||
errorMessage = "미리 듣기의 최소 시간은 15초 입니다"
|
||||
errorMessage = I18n.CreateContent.previewMinimumDurationError
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if previewStartTime.count > 0 || previewEndTime.count > 0 {
|
||||
errorMessage = "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다."
|
||||
errorMessage = I18n.CreateContent.previewStartEndBothOrNone
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct QuarterTimePickerView: View {
|
||||
@Environment(\.locale) private var locale
|
||||
|
||||
@Binding var selectedTime: Date
|
||||
@Binding var isShowing: Bool
|
||||
@@ -28,7 +29,7 @@ struct QuarterTimePickerView: View {
|
||||
)
|
||||
.datePickerStyle(WheelDatePickerStyle())
|
||||
.labelsHidden()
|
||||
.environment(\.locale, Locale.init(identifier: "ko"))
|
||||
.environment(\.locale, locale)
|
||||
.frame(width: proxy.size.width - 53.4)
|
||||
.onAppear {
|
||||
UIDatePicker.appearance().minuteInterval = 15
|
||||
@@ -38,7 +39,7 @@ struct QuarterTimePickerView: View {
|
||||
}
|
||||
|
||||
Button(action: { self.isShowing = false }) {
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 16)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 10)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SelectDatePicker: View {
|
||||
@Environment(\.locale) private var locale
|
||||
|
||||
@Binding var selectedDate: Date
|
||||
@Binding var isShowing: Bool
|
||||
@@ -24,11 +25,11 @@ struct SelectDatePicker: View {
|
||||
DatePicker("", selection: $selectedDate, in: Date()..., displayedComponents: .date)
|
||||
.datePickerStyle(WheelDatePickerStyle())
|
||||
.labelsHidden()
|
||||
.environment(\.locale, Locale.init(identifier: "ko"))
|
||||
.environment(\.locale, locale)
|
||||
.frame(width: proxy.size.width)
|
||||
|
||||
Button(action: { self.isShowing = false }) {
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 16)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 10)
|
||||
|
||||
@@ -30,7 +30,7 @@ struct ContentCurationView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -42,7 +42,7 @@ struct ContentCurationView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
Text(I18n.Content.Sort.priceHigh)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -54,7 +54,7 @@ struct ContentCurationView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
Text(I18n.Content.Sort.priceLow)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -72,7 +72,7 @@ struct ContentCurationView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -81,7 +81,7 @@ struct ContentCurationView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -77,13 +77,13 @@ final class ContentCurationViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ struct AudioContentDeleteDialogView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text("콘텐츠 삭제")
|
||||
Text(I18n.ContentDetail.DeleteDialog.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("[\(title)]을 삭제하시겠습니까?")
|
||||
Text(I18n.ContentDetail.DeleteDialog.confirmQuestion(title))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.top, 21.3)
|
||||
@@ -36,7 +36,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
isAgree.toggle()
|
||||
}
|
||||
|
||||
Text("삭제된 콘텐츠는 되돌릴 수 없음을 알고 있습니다.")
|
||||
Text(I18n.ContentDetail.DeleteDialog.irreversibleAcknowledgement)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.onTapGesture {
|
||||
@@ -48,7 +48,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
.cornerRadius(6.7)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("콘텐츠를 삭제하더라도 이미 구매한\n사용자는 콘텐츠를 이용할 수 있습니다.")
|
||||
Text(I18n.ContentDetail.DeleteDialog.purchasedUserNotice)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "dd4500"))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -56,7 +56,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Text("취소")
|
||||
Text(I18n.Common.cancel)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(.horizontal, 55)
|
||||
@@ -70,7 +70,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.horizontal, 55)
|
||||
|
||||
@@ -13,15 +13,7 @@ struct AudioContentReportDialogView: View {
|
||||
let confirmAction: (String) -> Void
|
||||
|
||||
@State private var selectedIndex: Int? = nil
|
||||
let reasons = [
|
||||
"괴롭힘 및 사이버 폭력",
|
||||
"개인정보 침해",
|
||||
"명의도용",
|
||||
"폭력적 위협",
|
||||
"아동학대",
|
||||
"보호대상 집단에 대한 증오심 표현",
|
||||
"스팸 및 사기"
|
||||
]
|
||||
let reasons = I18n.ContentDetail.ReportDialog.reasons
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -31,7 +23,7 @@ struct AudioContentReportDialogView: View {
|
||||
.onTapGesture { isShowing = false }
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("콘텐츠 신고")
|
||||
Text(I18n.ContentDetail.ReportDialog.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
@@ -59,13 +51,13 @@ struct AudioContentReportDialogView: View {
|
||||
.cornerRadius(6.7)
|
||||
.padding(.vertical, 21.3)
|
||||
|
||||
Text("신고한 콘텐츠를 관리자가 확인 후, 서비스정책을\n위반한 경우 삭제 조치할 예정입니다.")
|
||||
Text(I18n.ContentDetail.ReportDialog.notice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "dd4500"))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Text("취소")
|
||||
Text(I18n.Common.cancel)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(.vertical, 16)
|
||||
@@ -79,7 +71,7 @@ struct AudioContentReportDialogView: View {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
Text("신고")
|
||||
Text(I18n.ContentDetail.ReportDialog.reportAction)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 16)
|
||||
|
||||
@@ -51,7 +51,7 @@ struct AudioContentCommentItemView: View {
|
||||
.foregroundColor(Color.gray90)
|
||||
|
||||
if commentItem.isSecret {
|
||||
Text("비밀댓글")
|
||||
Text(I18n.ContentDetail.Comment.secretComment)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.horizontal, 4)
|
||||
@@ -104,7 +104,7 @@ struct AudioContentCommentItemView: View {
|
||||
HStack(spacing: 0) {
|
||||
if isModeModify {
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -150,8 +150,12 @@ struct AudioContentCommentItemView: View {
|
||||
audioContentId: audioContentId,
|
||||
parentComment: commentItem
|
||||
)
|
||||
) {
|
||||
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
|
||||
) {
|
||||
Text(
|
||||
commentItem.replyCount > 0 ?
|
||||
I18n.ContentDetail.Comment.replyCount(commentItem.replyCount) :
|
||||
I18n.ContentDetail.Comment.writeReply
|
||||
)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
@@ -172,7 +176,7 @@ struct AudioContentCommentItemView: View {
|
||||
if isShowPopupMenu {
|
||||
VStack(spacing: 10) {
|
||||
if commentItem.writerId == UserDefaults.int(forKey: .userId) {
|
||||
Text("수정")
|
||||
Text(I18n.ContentDetail.Comment.edit)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.onTapGesture {
|
||||
@@ -184,7 +188,7 @@ struct AudioContentCommentItemView: View {
|
||||
if contentCreatorId == UserDefaults.int(forKey: .userId) ||
|
||||
commentItem.writerId == UserDefaults.int(forKey: .userId)
|
||||
{
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.onTapGesture {
|
||||
|
||||
@@ -29,7 +29,7 @@ struct AudioContentCommentListView: View {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("댓글")
|
||||
Text(I18n.ContentDetail.Comment.title)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(.leading, 13.3)
|
||||
@@ -65,7 +65,7 @@ struct AudioContentCommentListView: View {
|
||||
viewModel.isSecret.toggle()
|
||||
}
|
||||
|
||||
Text("비밀댓글")
|
||||
Text(I18n.ContentDetail.Comment.secretComment)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(viewModel.isSecret ? Color.button : Color.grayee)
|
||||
.onTapGesture {
|
||||
@@ -85,7 +85,7 @@ struct AudioContentCommentListView: View {
|
||||
.clipShape(Circle())
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $viewModel.comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $viewModel.comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
|
||||
@@ -64,13 +64,13 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -115,13 +115,13 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -135,13 +135,13 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
isActive: Bool? = nil
|
||||
) {
|
||||
if comment == nil && isActive == nil {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
errorMessage = I18n.ContentDetail.Comment.noChanges
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
errorMessage = "내용을 입력하세요."
|
||||
errorMessage = I18n.ContentDetail.Comment.inputContent
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -187,14 +187,14 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ struct AudioContentListReplyView: View {
|
||||
HStack(spacing: 6.7) {
|
||||
Image("ic_back")
|
||||
|
||||
Text("답글")
|
||||
Text(I18n.ContentDetail.Comment.replyTitle)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -55,7 +55,7 @@ struct AudioContentListReplyView: View {
|
||||
.clipShape(Circle())
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $viewModel.comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $viewModel.comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
|
||||
@@ -62,13 +62,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -133,13 +133,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
isActive: Bool? = nil
|
||||
) {
|
||||
if comment == nil && isActive == nil {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
errorMessage = I18n.ContentDetail.Comment.noChanges
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
errorMessage = "내용을 입력하세요."
|
||||
errorMessage = I18n.ContentDetail.Comment.inputContent
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -185,14 +185,14 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ContentDetailCommentView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10.3) {
|
||||
HStack(spacing: 5.3) {
|
||||
Text("댓글")
|
||||
Text(I18n.ContentDetail.Comment.title)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -38,7 +38,7 @@ struct ContentDetailCommentView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("비밀댓글")
|
||||
Text(I18n.ContentDetail.Comment.secretComment)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(isSecret ? Color.button : Color.grayee)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ struct ContentDetailCommentView: View {
|
||||
.padding(.leading, 3)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
|
||||
@@ -18,7 +18,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
HStack(spacing: 0) {
|
||||
Text("한정판")
|
||||
Text(I18n.ContentDetail.LimitedEdition.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
|
||||
@@ -40,7 +40,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
.foregroundColor(Color.grayd2)
|
||||
.padding(.leading, 2.3)
|
||||
} else if (remainingContentCount <= 0) {
|
||||
Text("Sold Out")
|
||||
Text(I18n.Content.Status.soldOut)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -51,7 +51,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
.foregroundColor(Color.grayd2)
|
||||
)
|
||||
} else {
|
||||
Text("잔여수량")
|
||||
Text(I18n.ContentDetail.LimitedEdition.remainingCount)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
|
||||
@@ -69,7 +69,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
if !buyerList.isEmpty {
|
||||
Text("구매자")
|
||||
Text(I18n.ContentDetail.LimitedEdition.buyers)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ContentDetailInfoView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 5.3) {
|
||||
if let _ = audioContent.releaseDate {
|
||||
Text("오픈예정")
|
||||
Text(I18n.Common.openScheduled)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -62,7 +62,7 @@ struct ContentDetailInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text(orderType == .KEEP ? "소장중" : "대여중")
|
||||
Text(orderType == .KEEP ? I18n.Content.Status.owned : I18n.Content.Status.rented)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(
|
||||
orderType == .KEEP ?
|
||||
@@ -113,7 +113,7 @@ struct ContentDetailInfoView: View {
|
||||
HStack(spacing: 4) {
|
||||
Image("ic_audio_content_share")
|
||||
|
||||
Text("공유")
|
||||
Text(I18n.ContentDetail.Info.share)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
}
|
||||
@@ -129,7 +129,7 @@ struct ContentDetailInfoView: View {
|
||||
.resizable()
|
||||
.frame(width: 13.3, height: 13.3)
|
||||
|
||||
Text("후원")
|
||||
Text(I18n.ContentDetail.Info.donation)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
}
|
||||
@@ -174,7 +174,7 @@ struct ContentDetailInfoView: View {
|
||||
|
||||
if isShowingPreviewAlert() {
|
||||
HStack(spacing: 0) {
|
||||
Text("미리듣기 중입니다.\n콘텐츠 구매 후 전체를 감상해 보세요.")
|
||||
Text(I18n.ContentDetail.Info.previewAlertMessage)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
.lineSpacing(5)
|
||||
|
||||
@@ -34,7 +34,7 @@ struct ContentDetailMenuView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Image(isPin ? "ic_pin_cancel" : "ic_pin")
|
||||
|
||||
Text(isPin ? "내 채널에 고정 취소" : "내 채널에 고정")
|
||||
Text(isPin ? I18n.ContentDetail.Menu.unpinFromMyChannel : I18n.ContentDetail.Menu.pinToMyChannel)
|
||||
.appFont(size: 16.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -51,7 +51,7 @@ struct ContentDetailMenuView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Image("ic_make_message")
|
||||
|
||||
Text("수정")
|
||||
Text(I18n.ContentDetail.Menu.edit)
|
||||
.appFont(size: 16.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -68,7 +68,7 @@ struct ContentDetailMenuView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Image("ic_trash_can")
|
||||
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 16.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -83,7 +83,7 @@ struct ContentDetailMenuView: View {
|
||||
}
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
Text("신고")
|
||||
Text(I18n.ContentDetail.Menu.report)
|
||||
.appFont(size: 16.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ struct ContentDetailMosaicView: View {
|
||||
VStack(spacing: 0) {
|
||||
Image("ic_notice_exclamation_mark")
|
||||
|
||||
Text("본 콘텐츠는 만 19세 미만의 청소년이\n이용할 수 없습니다.\n본인인증 후 콘텐츠를 이용해 주세요.")
|
||||
Text(I18n.ContentDetail.Mosaic.adultRestrictionNotice)
|
||||
.appFont(size: 18.7, weight: .medium)
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.padding(.top, 21.7)
|
||||
|
||||
Text("본인인증")
|
||||
Text(I18n.ContentDetail.Mosaic.verifyIdentity)
|
||||
.appFont(size: 18.7, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
@@ -36,7 +36,7 @@ struct ContentDetailOtherContentView: View {
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
|
||||
Text("\(title)를 준비중입니다.\n조금만 기다려주세요.")
|
||||
Text(I18n.ContentDetail.OtherContent.preparingMessage(title))
|
||||
.multilineTextAlignment(.center)
|
||||
.appFont(size: 10.7, weight: .medium)
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
|
||||
@@ -62,7 +62,7 @@ struct ContentDetailPlayView: View {
|
||||
)
|
||||
|
||||
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0, audioContent.creator.creatorId != UserDefaults.int(forKey: .userId), !audioContent.existOrdered {
|
||||
Text("Sold Out")
|
||||
Text(I18n.Content.Status.soldOut)
|
||||
.appFont(size: 36.7, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(
|
||||
|
||||
@@ -32,7 +32,7 @@ struct ContentDetailPreviousNextContentButtonView: View {
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
|
||||
Text("이전화")
|
||||
Text(I18n.ContentDetail.Navigation.previousEpisode)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.button)
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -71,7 +71,7 @@ struct ContentDetailPreviousNextContentButtonView: View {
|
||||
.multilineTextAlignment(.leading)
|
||||
.lineLimit(2)
|
||||
|
||||
Text("다음화")
|
||||
Text(I18n.ContentDetail.Navigation.nextEpisode)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.button)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
@@ -26,7 +26,11 @@ struct ContentDetailPurchaseButton: View {
|
||||
.foregroundColor(.white)
|
||||
.padding(.leading, 5.3)
|
||||
|
||||
Text(UserDefaults.int(forKey: .userId) == 17958 ? "원으로": "캔으로")
|
||||
Text(
|
||||
UserDefaults.int(forKey: .userId) == 17958 ?
|
||||
I18n.ContentDetail.Purchase.withWon :
|
||||
I18n.ContentDetail.Purchase.withCan
|
||||
)
|
||||
.appFont(size: 12, weight: .light)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ struct ContentDetailView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("콘텐츠 상세")
|
||||
Text(I18n.ContentDetail.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
@@ -120,7 +120,7 @@ struct ContentDetailView: View {
|
||||
audioContent.orderType == nil &&
|
||||
audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
|
||||
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0 {
|
||||
Text("해당 콘텐츠가 매진되었습니다.")
|
||||
Text(I18n.ContentDetail.soldOutNotice)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -365,7 +365,7 @@ struct ContentDetailView: View {
|
||||
}
|
||||
},
|
||||
showToast: {
|
||||
viewModel.errorMessage = "동의하셔야 삭제할 수 있습니다."
|
||||
viewModel.errorMessage = I18n.ContentDetail.deleteAgreementRequired
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
)
|
||||
|
||||
@@ -69,14 +69,14 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -148,13 +148,13 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -186,13 +186,13 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -212,7 +212,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
self.shareMessage = shareUrl
|
||||
self.isShowShareView = true
|
||||
} else {
|
||||
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
|
||||
self.errorMessage = I18n.ContentDetail.shareLinkCreateFailed
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -248,13 +248,13 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -286,26 +286,26 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
AppState.shared.purchasedContentOrderType = .KEEP
|
||||
|
||||
self.orderType = nil
|
||||
self.errorMessage = orderType == .RENTAL ? "대여가 완료되었습니다." : "구매가 완료되었습니다."
|
||||
self.errorMessage = orderType == .RENTAL ? I18n.ContentDetail.rentalCompleted : I18n.ContentDetail.purchaseCompleted
|
||||
self.isShowPopup = true
|
||||
self.getAudioContentDetail()
|
||||
ContentPlayManager.shared.conditionalStopAudio(contentId: contentId)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
if message.contains("캔이 부족합니다") {
|
||||
if isInsufficientCanError(message: message, errorProperty: decoded.errorProperty) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
AppState.shared.setAppStep(step: .canCharge(refresh: {}, afterCompletionToGoBack: true))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -335,12 +335,12 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -368,20 +368,20 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
|
||||
if decoded.success {
|
||||
self.orderType = nil
|
||||
self.errorMessage = "삭제되었습니다"
|
||||
self.errorMessage = I18n.ContentDetail.deleteCompleted
|
||||
self.isShowPopup = true
|
||||
onSuccess()
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -390,10 +390,10 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
|
||||
func donation(can: Int, comment: String) {
|
||||
if can <= 0 {
|
||||
self.errorMessage = "1캔 이상 후원하실 수 있습니다."
|
||||
self.errorMessage = I18n.LiveRoom.atLeastOneCanDonationMessage
|
||||
self.isShowPopup = true
|
||||
} else if comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
self.errorMessage = "함께 보낼 메시지를 입력하세요."
|
||||
self.errorMessage = I18n.ContentDetail.donationMessageRequired
|
||||
self.isShowPopup = true
|
||||
} else {
|
||||
isLoading = true
|
||||
@@ -415,7 +415,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
|
||||
if decoded.success {
|
||||
UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can)
|
||||
self.errorMessage = "\(can)캔을 후원하셨습니다."
|
||||
self.errorMessage = I18n.ContentDetail.donationCompleted(can)
|
||||
self.isShowPopup = true
|
||||
|
||||
self.getAudioContentDetail()
|
||||
@@ -423,13 +423,13 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -456,7 +456,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.errorMessage = "고정되었습니다"
|
||||
self.errorMessage = I18n.ContentDetail.pinCompleted
|
||||
self.isShowPopup = true
|
||||
|
||||
self.getAudioContentDetail()
|
||||
@@ -464,13 +464,13 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -496,7 +496,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.errorMessage = "해제되었습니다"
|
||||
self.errorMessage = I18n.ContentDetail.unpinCompleted
|
||||
self.isShowPopup = true
|
||||
|
||||
self.getAudioContentDetail()
|
||||
@@ -504,16 +504,40 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func isInsufficientCanError(message: String, errorProperty: String?) -> Bool {
|
||||
let normalizedErrorProperty = errorProperty?.lowercased() ?? ""
|
||||
|
||||
if normalizedErrorProperty.contains("not_enough_can") ||
|
||||
normalizedErrorProperty.contains("insufficient_can") ||
|
||||
normalizedErrorProperty.contains("lack_of_can")
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
let normalizedMessage = message.lowercased()
|
||||
let insufficientCanPatterns = [
|
||||
"캔이 부족",
|
||||
"캔 부족",
|
||||
"not enough can",
|
||||
"insufficient can",
|
||||
"not enough cans",
|
||||
"insufficient cans",
|
||||
"canが不足"
|
||||
]
|
||||
|
||||
return insufficientCanPatterns.contains { normalizedMessage.contains($0) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ struct ContentOrderConfirmDialogView: View {
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("구매확인")
|
||||
Text(I18n.ContentDetail.OrderConfirmDialog.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -100,7 +100,11 @@ struct ContentOrderConfirmDialogView: View {
|
||||
.cornerRadius(5.3)
|
||||
.padding(.top, 21.3)
|
||||
|
||||
Text("콘텐츠를 \(orderType == .RENTAL ? "대여" : "소장")하시겠습니까?")
|
||||
Text(
|
||||
orderType == .RENTAL ?
|
||||
I18n.ContentDetail.OrderConfirmDialog.rentQuestion :
|
||||
I18n.ContentDetail.OrderConfirmDialog.buyQuestion
|
||||
)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -108,7 +112,7 @@ struct ContentOrderConfirmDialogView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
if UserDefaults.int(forKey: .userId) != 17958 {
|
||||
Text("아래 금액이 차감됩니다.")
|
||||
Text(I18n.ContentDetail.OrderConfirmDialog.deductionNotice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -150,7 +154,7 @@ struct ContentOrderConfirmDialogView: View {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("\(price * 110)원")
|
||||
Text("\(price * 110)\(I18n.ContentDetail.Purchase.wonUnit)")
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
@@ -168,7 +172,7 @@ struct ContentOrderConfirmDialogView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Text("취소")
|
||||
Text(I18n.Common.cancel)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 15.7)
|
||||
@@ -181,7 +185,7 @@ struct ContentOrderConfirmDialogView: View {
|
||||
)
|
||||
.onTapGesture { isShowing = false }
|
||||
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.vertical, 15.7)
|
||||
|
||||
@@ -27,11 +27,11 @@ struct ContentOrderDialogView: View {
|
||||
VStack(spacing: 26.7) {
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 5.3) {
|
||||
Text("대여")
|
||||
Text(I18n.ContentDetail.OrderDialog.rent)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("(이용기간 5일)")
|
||||
Text(I18n.ContentDetail.OrderDialog.rentPeriod)
|
||||
.appFont(size: 12, weight: .light)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
@@ -56,7 +56,7 @@ struct ContentOrderDialogView: View {
|
||||
}
|
||||
|
||||
if UserDefaults.int(forKey: .userId) == 17958 {
|
||||
Text("원")
|
||||
Text(I18n.ContentDetail.Purchase.wonUnit)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
@@ -73,11 +73,11 @@ struct ContentOrderDialogView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 5.3) {
|
||||
Text("소장")
|
||||
Text(I18n.ContentDetail.OrderDialog.buy)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("(서비스 종료시까지)")
|
||||
Text(I18n.ContentDetail.OrderDialog.buyPeriod)
|
||||
.appFont(size: 12, weight: .light)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
@@ -102,7 +102,7 @@ struct ContentOrderDialogView: View {
|
||||
}
|
||||
|
||||
if UserDefaults.int(forKey: .userId) == 17958 {
|
||||
Text("원")
|
||||
Text(I18n.ContentDetail.Purchase.wonUnit)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ struct LiveRoomDonationDialogView: View {
|
||||
.resizable()
|
||||
.frame(width: 26.7, height: 26.7)
|
||||
|
||||
Text("후원하기")
|
||||
Text(I18n.ContentDetail.DonationDialog.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -80,7 +80,7 @@ struct LiveRoomDonationDialogView: View {
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text("충전")
|
||||
Text(I18n.ContentDetail.DonationDialog.charge)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.main)
|
||||
.padding(.horizontal, 13.3)
|
||||
@@ -126,7 +126,12 @@ struct LiveRoomDonationDialogView: View {
|
||||
.padding(.top, 16)
|
||||
}
|
||||
|
||||
TextField(isSecret ? "10캔 이상 입력하세요" : "1캔 이상 입력하세요", text: $donationCan)
|
||||
TextField(
|
||||
isSecret ?
|
||||
I18n.ContentDetail.DonationDialog.minimumTenCanPlaceholder :
|
||||
I18n.ContentDetail.DonationDialog.minimumOneCanPlaceholder,
|
||||
text: $donationCan
|
||||
)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(13.3)
|
||||
@@ -227,7 +232,10 @@ struct LiveRoomDonationDialogView: View {
|
||||
)
|
||||
|
||||
TextField(
|
||||
"함께 보낼 \((isSecret && shouldPrefixSecretInMessagePlaceholder) ? "비밀 " : "")메시지 입력(최대 \(messageLimit)자)",
|
||||
I18n.ContentDetail.DonationDialog.messagePlaceholder(
|
||||
isSecret: isSecret && shouldPrefixSecretInMessagePlaceholder,
|
||||
limit: messageLimit
|
||||
),
|
||||
text: $donationMessage
|
||||
)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -242,7 +250,7 @@ struct LiveRoomDonationDialogView: View {
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Text("취소")
|
||||
Text(I18n.Common.cancel)
|
||||
.appFont(size: 15, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 16)
|
||||
@@ -258,7 +266,7 @@ struct LiveRoomDonationDialogView: View {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
Text("후원하기")
|
||||
Text(I18n.ContentDetail.DonationDialog.donateAction)
|
||||
.appFont(size: 15, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.vertical, 16)
|
||||
@@ -271,14 +279,14 @@ struct LiveRoomDonationDialogView: View {
|
||||
errorMessage = secretMinimumCanMessage
|
||||
isShowErrorPopup = true
|
||||
} else if can < 1 {
|
||||
errorMessage = "1캔 이상 후원하실 수 있습니다."
|
||||
errorMessage = I18n.LiveRoom.atLeastOneCanDonationMessage
|
||||
isShowErrorPopup = true
|
||||
} else {
|
||||
onClickDonation(can, donationMessage, isSecret)
|
||||
isShowing = false
|
||||
}
|
||||
} else {
|
||||
errorMessage = "1캔 이상 후원하실 수 있습니다."
|
||||
errorMessage = I18n.LiveRoom.atLeastOneCanDonationMessage
|
||||
isShowErrorPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ final class ContentMainBannerViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "배너를 불러오지 못했습니다. 다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Content.Banner.loadFailed
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "배너를 불러오지 못했습니다. 다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Content.Banner.loadFailed
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ struct ContentModifyView: View {
|
||||
GeometryReader { proxy in
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "콘텐츠 수정")
|
||||
DetailNavigationBar(title: I18n.CreateContent.modifyTitle)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
Text("썸네일")
|
||||
Text(I18n.CreateContent.thumbnail)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -82,12 +82,12 @@ struct ContentModifyView: View {
|
||||
.padding(.top, 26.7)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("제목")
|
||||
Text(I18n.CreateContent.titleLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
TextField("제목을 입력하세요", text: $viewModel.title)
|
||||
TextField(I18n.CreateContent.titlePlaceholder, text: $viewModel.title)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -100,16 +100,16 @@ struct ContentModifyView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("내용")
|
||||
Text(I18n.CreateContent.contentLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.detail.count)자")
|
||||
Text(I18n.CreateContent.characterCount(viewModel.detail.count))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
Text(" / 최대 500자")
|
||||
Text(I18n.CreateContent.max500CharactersSuffix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
}
|
||||
@@ -125,13 +125,13 @@ struct ContentModifyView: View {
|
||||
.cornerRadius(6.7)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("태그")
|
||||
Text(I18n.CreateContent.tagLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 26.7)
|
||||
|
||||
TextField("예: #연애 #커버곡", text: $viewModel.hashtags)
|
||||
TextField(I18n.CreateContent.tagPlaceholderExample, text: $viewModel.hashtags)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -152,19 +152,19 @@ struct ContentModifyView: View {
|
||||
.padding(.top, 26.7)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("포인트 사용")
|
||||
Text(I18n.CreateContent.pointUsageTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(title: "가능", isChecked: viewModel.isPointAvailable) {
|
||||
SelectButtonView(title: I18n.CreateContent.available, isChecked: viewModel.isPointAvailable) {
|
||||
if !viewModel.isPointAvailable {
|
||||
viewModel.isPointAvailable = true
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(title: "불가능", isChecked: !viewModel.isPointAvailable) {
|
||||
SelectButtonView(title: I18n.CreateContent.unavailable, isChecked: !viewModel.isPointAvailable) {
|
||||
if viewModel.isPointAvailable {
|
||||
viewModel.isPointAvailable = false
|
||||
}
|
||||
@@ -176,26 +176,26 @@ struct ContentModifyView: View {
|
||||
|
||||
if viewModel.isAdultShowUi {
|
||||
VStack(spacing: 13.3) {
|
||||
Text("연령 제한")
|
||||
Text(I18n.CreateContent.ageRestrictionTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(title: "전체 연령", isChecked: !viewModel.isAdult) {
|
||||
SelectButtonView(title: I18n.CreateContent.allAges, isChecked: !viewModel.isAdult) {
|
||||
if viewModel.isAdult {
|
||||
viewModel.isAdult = false
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(title: "19세 이상", isChecked: viewModel.isAdult) {
|
||||
SelectButtonView(title: I18n.CreateContent.over19, isChecked: viewModel.isAdult) {
|
||||
if !viewModel.isAdult {
|
||||
viewModel.isAdult = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text("성인콘텐츠를 전체관람가로 등록할 시 발생하는 법적 책임은 회사와 상관없이 콘텐츠를 등록한 본인에게 있습니다.\n콘텐츠 내용은 물론 제목도 19금 여부를 체크해 주시기 바랍니다.")
|
||||
Text(I18n.CreateContent.adultLegalNotice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "DD4500"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -206,19 +206,19 @@ struct ContentModifyView: View {
|
||||
}
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("댓글 가능 여부")
|
||||
Text(I18n.CreateContent.commentAvailabilityTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(title: "댓글 가능", isChecked: viewModel.isAvailableComment) {
|
||||
SelectButtonView(title: I18n.CreateContent.commentAllowed, isChecked: viewModel.isAvailableComment) {
|
||||
if !viewModel.isAvailableComment {
|
||||
viewModel.isAvailableComment = true
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(title: "댓글 불가", isChecked: !viewModel.isAvailableComment) {
|
||||
SelectButtonView(title: I18n.CreateContent.commentNotAllowed, isChecked: !viewModel.isAvailableComment) {
|
||||
if viewModel.isAvailableComment {
|
||||
viewModel.isAvailableComment = false
|
||||
}
|
||||
@@ -230,7 +230,7 @@ struct ContentModifyView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("수정")
|
||||
Text(I18n.CreateContent.modifyAction)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.frame(height: 50)
|
||||
|
||||
@@ -31,7 +31,7 @@ final class ContentModifyViewModel: ObservableObject {
|
||||
@Published var isAdultShowUi = false
|
||||
|
||||
var contentId: Int = 0
|
||||
var placeholder = "내용을 입력하세요"
|
||||
var placeholder = I18n.CreateContent.uploadContentDescriptionHint
|
||||
|
||||
func getAudioContentDetail(onFailure: (() -> Void)? = nil) {
|
||||
audioContent = nil
|
||||
@@ -68,13 +68,13 @@ final class ContentModifyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ final class ContentModifyViewModel: ObservableObject {
|
||||
mimeType: "image/*")
|
||||
)
|
||||
} else {
|
||||
errorMessage = "커버이미지를 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.coverImageUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
@@ -139,26 +139,26 @@ final class ContentModifyViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.errorMessage = "콘텐츠가 수정되었습니다."
|
||||
self.errorMessage = I18n.CreateContent.modifySuccess
|
||||
self.isShowPopup = true
|
||||
onSuccess()
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -167,13 +167,13 @@ final class ContentModifyViewModel: ObservableObject {
|
||||
|
||||
private func validateData() -> Bool {
|
||||
if title != audioContent!.title && title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
errorMessage = "제목을 입력해 주세요."
|
||||
errorMessage = I18n.CreateContent.titleRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if detail != audioContent!.detail && (detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5) {
|
||||
errorMessage = "내용을 5자 이상 입력해 주세요."
|
||||
errorMessage = I18n.CreateContent.detailMinLengthRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ struct ContentPlaylistItemView: View {
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
Text("총 \(item.contentCount)개")
|
||||
Text(I18n.Content.Playlist.itemCount(item.contentCount))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray90)
|
||||
.lineLimit(1)
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ContentPlaylistListView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 13.3) {
|
||||
Text("+ 새 재생목록 만들기")
|
||||
Text(I18n.Content.Playlist.createNewAction)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.vertical, 13.3)
|
||||
@@ -31,11 +31,11 @@ struct ContentPlaylistListView: View {
|
||||
|
||||
if viewModel.playlists.isEmpty {
|
||||
VStack(spacing: 13.3) {
|
||||
Text("재생목록이 비어있습니다.")
|
||||
Text(I18n.Content.Playlist.emptyTitle)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text("자주 듣는 콘텐츠를\n재생목록으로 만들어 보세요.")
|
||||
Text(I18n.Content.Playlist.emptyDescription)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -45,11 +45,11 @@ struct ContentPlaylistListView: View {
|
||||
.cornerRadius(4.7)
|
||||
} else {
|
||||
HStack(spacing: 5.3) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Playlist.totalLabel)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
|
||||
Text("\(viewModel.totalCount)개")
|
||||
Text("\(viewModel.totalCount)\(I18n.Content.Count.countUnit)")
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray90)
|
||||
|
||||
|
||||
@@ -41,18 +41,18 @@ final class ContentPlaylistListViewModel: ObservableObject {
|
||||
self.totalCount = data.totalCount
|
||||
self.playlists.append(contentsOf: data.items)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@ struct ContentPlaylistCreateView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("새 재생목록 만들기")
|
||||
Text(I18n.Content.Playlist.createTitle)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("저장")
|
||||
Text(I18n.Content.Playlist.createSave)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(minHeight: 48)
|
||||
@@ -50,7 +50,7 @@ struct ContentPlaylistCreateView: View {
|
||||
.background(Color.black)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("재생목록 제목")
|
||||
Text(I18n.Content.Playlist.titleLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -83,7 +83,7 @@ struct ContentPlaylistCreateView: View {
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("재생목록 설명을 입력해 주세요")
|
||||
Text(I18n.Content.Playlist.descriptionLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -118,7 +118,7 @@ struct ContentPlaylistCreateView: View {
|
||||
HStack(spacing: 8) {
|
||||
Image("btn_plus_round")
|
||||
|
||||
Text("새로운 콘텐츠 추가/제거")
|
||||
Text(I18n.Content.Playlist.addContentAction)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ final class ContentPlaylistCreateViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -74,13 +74,13 @@ final class ContentPlaylistCreateViewModel: ObservableObject {
|
||||
|
||||
private func validate() -> Bool {
|
||||
if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) {
|
||||
errorMessage = "제목을 3자 이상 입력하세요"
|
||||
errorMessage = I18n.Content.Playlist.titleValidation
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if (contentList.isEmpty) {
|
||||
errorMessage = "콘텐츠를 1개 이상 추가하세요"
|
||||
errorMessage = I18n.Content.Playlist.contentValidation
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ struct PlaylistAddContentView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
ZStack {
|
||||
Text("새로운 콘텐츠 추가/제거")
|
||||
Text(I18n.Content.Playlist.addContentAction)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Text("닫기")
|
||||
Text(I18n.Content.Playlist.close)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(minHeight: 48)
|
||||
@@ -36,11 +36,11 @@ struct PlaylistAddContentView: View {
|
||||
.background(Color.black)
|
||||
|
||||
HStack(alignment: .center, spacing: 5.3) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Playlist.totalLabel)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
|
||||
Text("\(viewModel.totalCount)개")
|
||||
Text(I18n.Content.Playlist.selectionCount(viewModel.totalCount))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray90)
|
||||
}
|
||||
|
||||
@@ -125,13 +125,13 @@ struct ContentPlaylistDetailView: View {
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("만든 날짜 \(response.createdDate)")
|
||||
Text(I18n.Content.Playlist.createdDate(response.createdDate))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray90)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(response.contentCount)개")
|
||||
Text(I18n.Content.Playlist.contentCount(response.contentCount))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
@@ -141,7 +141,7 @@ struct ContentPlaylistDetailView: View {
|
||||
HStack(spacing: 5.3) {
|
||||
Image("ic_playlist_play")
|
||||
|
||||
Text("Play")
|
||||
Text(I18n.Content.Playlist.play)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
@@ -159,7 +159,7 @@ struct ContentPlaylistDetailView: View {
|
||||
HStack(spacing: 5.3) {
|
||||
Image("ic_playlist_shuffle")
|
||||
|
||||
Text("Shuffle")
|
||||
Text(I18n.Content.Playlist.shuffle)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
@@ -255,7 +255,7 @@ struct ContentPlaylistDetailView: View {
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
|
||||
@@ -50,13 +50,13 @@ final class ContentPlaylistDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ final class ContentPlaylistDetailViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.errorMessage = "삭제되었습니다."
|
||||
self.errorMessage = I18n.Playlist.deleteCompleted
|
||||
self.isShowPopup = true
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
@@ -94,13 +94,13 @@ final class ContentPlaylistDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ struct ContentPlaylistModifyView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("재생목록 수정")
|
||||
Text(I18n.Content.Playlist.modifyTitle)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("수정")
|
||||
Text(I18n.Content.Playlist.modifyAction)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(minHeight: 48)
|
||||
@@ -51,7 +51,7 @@ struct ContentPlaylistModifyView: View {
|
||||
.background(Color.black)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("재생목록 제목")
|
||||
Text(I18n.Content.Playlist.titleLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -84,7 +84,7 @@ struct ContentPlaylistModifyView: View {
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("재생목록 설명을 입력해 주세요")
|
||||
Text(I18n.Content.Playlist.descriptionLabel)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -119,7 +119,7 @@ struct ContentPlaylistModifyView: View {
|
||||
HStack(spacing: 8) {
|
||||
Image("btn_plus_round")
|
||||
|
||||
Text("새로운 콘텐츠 추가/제거")
|
||||
Text(I18n.Content.Playlist.addContentAction)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
|
||||
@@ -57,13 +57,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -112,13 +112,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -129,13 +129,13 @@ final class ContentPlaylistModifyViewModel: ObservableObject {
|
||||
|
||||
private func validate() -> Bool {
|
||||
if (title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || title.count < 3) {
|
||||
errorMessage = "제목을 3자 이상 입력하세요"
|
||||
errorMessage = I18n.Content.Playlist.titleValidation
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if (contentList.isEmpty) {
|
||||
errorMessage = "콘텐츠를 1개 이상 추가하세요"
|
||||
errorMessage = I18n.Content.Playlist.contentValidation
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -18,12 +18,12 @@ struct SeriesContentAllView: View {
|
||||
VStack(spacing: 0) {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "\(seriesTitle) - 전체회차 듣기")
|
||||
DetailNavigationBar(title: I18n.Series.allEpisodesTitle(seriesTitle))
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color.graye2
|
||||
@@ -35,7 +35,7 @@ struct SeriesContentAllView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("등록순")
|
||||
Text(I18n.Series.registeredOrder)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color.graye2
|
||||
|
||||
@@ -66,13 +66,13 @@ final class SeriesContentAllViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ struct SeriesContentListItemView: View {
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
Text(I18n.Series.point)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
@@ -50,7 +50,7 @@ struct SeriesContentListItemView: View {
|
||||
Spacer()
|
||||
|
||||
if item.isOwned {
|
||||
Text("소장중")
|
||||
Text(I18n.Content.Status.owned)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray11)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -58,7 +58,7 @@ struct SeriesContentListItemView: View {
|
||||
.background(Color(hex: "b1ef2c"))
|
||||
.cornerRadius(2.6)
|
||||
} else if item.isRented {
|
||||
Text("대여중")
|
||||
Text(I18n.Content.Status.rented)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -74,7 +74,7 @@ struct SeriesContentListItemView: View {
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.Series.free)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 5.3)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct DayOfWeek {
|
||||
let dayOfWeekStr: LocalizedStringResource
|
||||
let dayOfWeekStr: String
|
||||
let dayOfWeek: SeriesPublishedDaysOfWeek
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ struct DayOfWeekSeriesView: View {
|
||||
@State private var dayOfWeek: SeriesPublishedDaysOfWeek = .FRI
|
||||
|
||||
private let dayOfWeekItems: [DayOfWeek] = [
|
||||
DayOfWeek(dayOfWeekStr: "월", dayOfWeek: .MON),
|
||||
DayOfWeek(dayOfWeekStr: "화", dayOfWeek: .TUE),
|
||||
DayOfWeek(dayOfWeekStr: "수", dayOfWeek: .WED),
|
||||
DayOfWeek(dayOfWeekStr: "목", dayOfWeek: .THU),
|
||||
DayOfWeek(dayOfWeekStr: "금", dayOfWeek: .FRI),
|
||||
DayOfWeek(dayOfWeekStr: "토", dayOfWeek: .SAT),
|
||||
DayOfWeek(dayOfWeekStr: "일", dayOfWeek: .SUN),
|
||||
DayOfWeek(dayOfWeekStr: "랜덤", dayOfWeek: .RANDOM),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.monday, dayOfWeek: .MON),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.tuesday, dayOfWeek: .TUE),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.wednesday, dayOfWeek: .WED),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.thursday, dayOfWeek: .THU),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.friday, dayOfWeek: .FRI),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.saturday, dayOfWeek: .SAT),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.sunday, dayOfWeek: .SUN),
|
||||
DayOfWeek(dayOfWeekStr: I18n.Series.random, dayOfWeek: .RANDOM),
|
||||
]
|
||||
|
||||
// 요일 숫자에 맞춰 배열
|
||||
@@ -45,13 +45,13 @@ struct DayOfWeekSeriesView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(spacing: 0) {
|
||||
Text("요일별 시리즈")
|
||||
Text(I18n.Series.byDaySectionTitle)
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("전체보기")
|
||||
Text(I18n.Common.viewAll)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.init(hex: "78909C"))
|
||||
.onTapGesture {
|
||||
@@ -93,7 +93,7 @@ struct DayOfWeekSeriesView: View {
|
||||
}
|
||||
|
||||
struct DayOfWeekDayView: View {
|
||||
let dayOfWeek: LocalizedStringResource
|
||||
let dayOfWeek: String
|
||||
let isSelected: Bool
|
||||
|
||||
var body: some View {
|
||||
|
||||
@@ -17,7 +17,7 @@ struct SeriesDetailHomeView: View {
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("전체회차 듣기")
|
||||
Text(I18n.Series.allEpisodesListen)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ struct SeriesDetailView: View {
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if seriesDetail.isAdult {
|
||||
Text("19세")
|
||||
Text(I18n.Series.age19Badge)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "f1291c"))
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -98,7 +98,7 @@ struct SeriesDetailView: View {
|
||||
.background(Color(hex: "312827"))
|
||||
.cornerRadius(2.6)
|
||||
} else {
|
||||
Text("전체연령가")
|
||||
Text(I18n.SeriesDetail.ageAll)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -107,7 +107,7 @@ struct SeriesDetailView: View {
|
||||
.cornerRadius(2.6)
|
||||
}
|
||||
|
||||
Text("\(seriesDetail.publishedDaysOfWeek) 연재")
|
||||
Text(I18n.Series.publishing(seriesDetail.publishedDaysOfWeek))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ final class SeriesDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -91,13 +91,13 @@ final class SeriesDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -131,13 +131,13 @@ final class SeriesDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user