34 Commits

Author SHA1 Message Date
Yu Sung
243da1eb7d fix(i18n): 예약 날짜와 시간 표시 언어를 앱 설정에 맞춘다 2026-04-01 18:44:05 +09:00
Yu Sung
39700d3b39 chore(i18n): 미사용 String Catalog를 정리한다 2026-04-01 18:09:29 +09:00
Yu Sung
43c86a627b feat(i18n): 온보딩 시작하기 문구를 I18n 키로 통일한다 2026-04-01 17:53:03 +09:00
Yu Sung
1ec56a1f15 feat(i18n): 시리즈/재생목록 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 17:33:13 +09:00
Yu Sung
c039931f34 feat(i18n): 콘텐츠 상세/댓글 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 16:50:56 +09:00
Yu Sung
a90996603b feat(i18n): 콘텐츠 모듈 그룹2 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 16:08:26 +09:00
Yu Sung
49e2487617 feat(i18n): 콘텐츠 모듈 그룹1 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 15:36:39 +09:00
Yu Sung
038d66e363 feat(i18n): 탐색 프로필 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 14:40:37 +09:00
Yu Sung
bceec46ebc feat(i18n): 라이브 룸 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 13:52:02 +09:00
Yu Sung
540238eb48 feat(i18n): 라이브 모듈 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 11:58:41 +09:00
Yu Sung
201f4c8139 feat(i18n): 홈 화면 하드코딩 문구를 I18n 키로 통일한다 2026-04-01 11:33:26 +09:00
Yu Sung
7285c5367d feat(i18n): 마이페이지 그룹 3~5 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 23:32:05 +09:00
Yu Sung
b53614836f feat(i18n): 마이페이지 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 23:04:33 +09:00
Yu Sung
25fccbaa07 feat(i18n): 음성 메시지 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 22:21:41 +09:00
Yu Sung
9369a52ba2 feat(i18n): 메시지 모듈 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 22:01:30 +09:00
Yu Sung
4c170e0f97 feat(i18n): 설정 화면 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 19:46:21 +09:00
Yu Sung
3f61a08a04 feat(i18n): 시리즈 리스트 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 18:50:22 +09:00
Yu Sung
9216db51da docs(i18n): 미완료 체크리스트를 10개 단위로 재배치한다 2026-03-31 18:35:04 +09:00
Yu Sung
8e4fe7a534 feat(i18n): 사용자 화면 문구를 I18n 키로 통일한다 2026-03-31 17:37:29 +09:00
Yu Sung
b2f66cf408 feat(i18n): 주요 UI 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 17:09:01 +09:00
Yu Sung
47085dc1ca feat(chat): 채팅 모듈 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 16:30:48 +09:00
Yu Sung
222520d5e9 feat(i18n): 오디션 화면 하드코딩 문구를 I18n 키로 통일한다 2026-03-31 15:39:57 +09:00
Yu Sung
136bfc8eee docs(i18n): 하드코딩 텍스트 I18n 통일 계획을 추가한다 2026-03-31 14:48:11 +09:00
Yu Sung
178e0849dc feat(live-room): 라이브 캡쳐 녹화 허용 설정을 생성 시청 흐름에 반영한다 2026-03-30 21:49:34 +09:00
Yu Sung
3a4df173d2 fix(live-room): 스탭 해제 후 방 정보 갱신 타이밍을 보정한다 2026-03-30 18:24:40 +09:00
Yu Sung
ec7e9cc71c fix(live-room): 스탭 캡쳐 녹화 보호 예외를 반영한다 2026-03-30 16:58:44 +09:00
Yu Sung
8370f1ead1 fix(home): 라이브 19금 안내 토스트를 설정 화면에서 표시한다 2026-03-28 23:25:17 +09:00
Yu Sung
e067531a3f fix(live-room): 방장 캡쳐 보호를 해제하고 비방장 보호를 유지한다 2026-03-28 20:04:41 +09:00
Yu Sung
d369bc11f7 fix(api): 콘텐츠 설정 PATCH 제외 API 파라미터를 제거한다 2026-03-27 22:28:21 +09:00
Yu Sung
f542191d46 fix(age-setting): 국가별 연령제한 노출 조건을 통일한다 2026-03-27 18:22:53 +09:00
Yu Sung
1d120b58bd fix(content): 성인 콘텐츠 설정 동기화와 국가별 인증 분기를 적용한다 2026-03-27 17:34:02 +09:00
Yu Sung
44daabdcae fix(live-room): 캡쳐 보호 음소거 동기화 2026-03-24 19:19:29 +09:00
Yu Sung
0844c6f4d7 fix(live-room): 라이브 상세 SNS 링크 아이콘 매핑을 신규 필드에 맞춘다 2026-03-24 13:36:10 +09:00
Yu Sung
c6a6b3c79e fix(live-room): 라이브 상세 복귀 시 DIM만 보이는 상태를 방지한다 2026-03-24 12:05:25 +09:00
346 changed files with 8656 additions and 12320 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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() {
@@ -181,6 +182,16 @@ class AppState: ObservableObject {
pendingCommunityCommentPostId = 0
}
func setPendingContentSettingsGuideMessage(_ message: String) {
pendingContentSettingsGuideMessage = message
}
func consumePendingContentSettingsGuideMessage() -> String? {
let message = pendingContentSettingsGuideMessage
pendingContentSettingsGuideMessage = nil
return message
}
// ( -> ) UI
func softRestart() {
isRestartApp = true

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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"))
}

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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)

View File

@@ -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,9 +56,21 @@ 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)
guard !trimmed.isEmpty else {
@@ -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

View File

@@ -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)

View File

@@ -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"))
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -130,7 +130,7 @@ struct AiMessageItemView: View {
.foregroundColor(.button)
}
Text("눌러서 잠금해제")
Text(I18n.Chat.Room.unlockImagePrompt)
.appFont(size: 18, weight: .bold)
.foregroundColor(.white)
}

View File

@@ -59,7 +59,7 @@ struct TypingIndicatorItemView: View {
}
}
}
.accessibilityLabel(Text("입력 중"))
.accessibilityLabel(Text(I18n.Chat.Room.typingAccessibilityLabel))
}
.padding(.horizontal, 10)
.padding(.vertical, 8)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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)
)
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -200,7 +200,7 @@ extension ContentPlayManager {
}
private func showError() {
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
self.errorMessage = I18n.Content.Playback.playFailed
self.isShowPopup = true
self.resetAudioData()
}

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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,8 +457,9 @@ struct ContentCreateView: View {
.padding(.top, 26.7)
.padding(.horizontal, 13.3)
if shouldShowAdultSetting {
VStack(spacing: 13.3) {
Text("연령 제한")
Text(I18n.CreateContent.ageRestrictionTitle)
.appFont(size: 16.7, weight: .bold)
.foregroundColor(Color.grayee)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -468,7 +478,7 @@ struct ContentCreateView: View {
}
}
Text("성인콘텐츠를 전체관람가로 등록할 시 발생하는 법적 책임은 회사와 상관없이 콘텐츠를 등록한 본인에게 있습니다.\n콘텐츠 내용은 물론 제목도 19금 여부를 체크해 주시기 바랍니다.")
Text(I18n.CreateContent.adultLegalNotice)
.appFont(size: 13.3, weight: .medium)
.foregroundColor(Color.mainRed3)
.frame(maxWidth: .infinity, alignment: .leading)
@@ -476,9 +486,10 @@ struct ContentCreateView: View {
}
.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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
@@ -151,7 +151,11 @@ struct AudioContentCommentItemView: View {
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 {

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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(

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}
)

View File

@@ -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) }
}
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -44,13 +44,13 @@ final class ContentPlaylistListViewModel: 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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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"))
}

View File

@@ -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