Compare commits
50 Commits
178e0849dc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
942c581eaf | ||
|
|
71edcf8bf9 | ||
|
|
d247bb4958 | ||
|
|
1a5df53edb | ||
|
|
270332d7c4 | ||
|
|
389f82fa82 | ||
|
|
44b8633e59 | ||
|
|
c217581d1d | ||
|
|
51ffe09125 | ||
|
|
5823f6ddb2 | ||
|
|
714ad459b0 | ||
|
|
69a7d291d1 | ||
|
|
0f5cd8a904 | ||
|
|
e5a9d4c307 | ||
|
|
88dac10c9f | ||
|
|
14f719fcc4 | ||
|
|
b269a356e1 | ||
|
|
ddd82b6b8f | ||
|
|
8baae71317 | ||
|
|
503468f713 | ||
|
|
0813b64bc9 | ||
|
|
120d961456 | ||
|
|
7db825cd41 | ||
|
|
3e524f121d | ||
|
|
4f427fc146 | ||
|
|
8b04952a4e | ||
|
|
13187070b5 | ||
|
|
243da1eb7d | ||
|
|
39700d3b39 | ||
|
|
43c86a627b | ||
|
|
1ec56a1f15 | ||
|
|
c039931f34 | ||
|
|
a90996603b | ||
|
|
49e2487617 | ||
|
|
038d66e363 | ||
|
|
bceec46ebc | ||
|
|
540238eb48 | ||
|
|
201f4c8139 | ||
|
|
7285c5367d | ||
|
|
b53614836f | ||
|
|
25fccbaa07 | ||
|
|
9369a52ba2 | ||
|
|
4c170e0f97 | ||
|
|
3f61a08a04 | ||
|
|
9216db51da | ||
|
|
8e4fe7a534 | ||
|
|
b2f66cf408 | ||
|
|
47085dc1ca | ||
|
|
222520d5e9 | ||
|
|
136bfc8eee |
115
.opencode/package-lock.json
generated
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"name": ".opencode",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@opencode-ai/plugin": "1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@opencode-ai/plugin": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.4.3.tgz",
|
||||
"integrity": "sha512-Ob/3tVSIeuMRJBr2O23RtrnC5djRe01Lglx+TwGEmjrH9yDBJ2tftegYLnNEjRoMuzITgq9LD8168p4pzv+U/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "1.4.3",
|
||||
"zod": "4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.1.97",
|
||||
"@opentui/solid": ">=0.1.97"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentui/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentui/solid": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@opencode-ai/sdk": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.4.3.tgz",
|
||||
"integrity": "sha512-X0CAVbwoGAjTY2iecpWkx2B+GAa2jSaQKYpJ+xILopeF/OGKZUN15mjqci+L7cEuwLHV5wk3x2TStUOVCa5p0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "7.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.1.8",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
242
AGENTS.md
@@ -1,6 +1,87 @@
|
||||
# AGENTS.md
|
||||
`SodaLive` 저장소에서 작업하는 에이전트 실행 가이드다.
|
||||
|
||||
## CORE EXECUTION PRINCIPLES (andrej-karpathy-skills)
|
||||
These principles override plugin behavior, skill behavior, workflow behavior, and default model behavior unless the user's direct instruction explicitly says otherwise.
|
||||
|
||||
The following content is taken from the official `andrej-karpathy-skills` `CLAUDE.md` source and is intentionally kept in English.
|
||||
|
||||
Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
|
||||
|
||||
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
||||
|
||||
### 1. Think Before Coding
|
||||
|
||||
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
||||
|
||||
Before implementing:
|
||||
- State your assumptions explicitly. If uncertain, ask.
|
||||
- If multiple interpretations exist, present them - don't pick silently.
|
||||
- If a simpler approach exists, say so. Push back when warranted.
|
||||
- If something is unclear, stop. Name what's confusing. Ask.
|
||||
|
||||
### 2. Simplicity First
|
||||
|
||||
**Minimum code that solves the problem. Nothing speculative.**
|
||||
|
||||
- No features beyond what was asked.
|
||||
- No abstractions for single-use code.
|
||||
- No "flexibility" or "configurability" that wasn't requested.
|
||||
- No error handling for impossible scenarios.
|
||||
- If you write 200 lines and it could be 50, rewrite it.
|
||||
|
||||
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||
|
||||
### 3. Surgical Changes
|
||||
|
||||
**Touch only what you must. Clean up only your own mess.**
|
||||
|
||||
When editing existing code:
|
||||
- Don't "improve" adjacent code, comments, or formatting.
|
||||
- Don't refactor things that aren't broken.
|
||||
- Match existing style, even if you'd do it differently.
|
||||
- If you notice unrelated dead code, mention it - don't delete it.
|
||||
|
||||
When your changes create orphans:
|
||||
- Remove imports/variables/functions that YOUR changes made unused.
|
||||
- Don't remove pre-existing dead code unless asked.
|
||||
|
||||
The test: Every changed line should trace directly to the user's request.
|
||||
|
||||
### 4. Goal-Driven Execution
|
||||
|
||||
**Define success criteria. Loop until verified.**
|
||||
|
||||
Transform tasks into verifiable goals:
|
||||
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
||||
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
||||
- "Refactor X" → "Ensure tests pass before and after"
|
||||
|
||||
For multi-step tasks, state a brief plan:
|
||||
```
|
||||
1. [Step] → verify: [check]
|
||||
2. [Step] → verify: [check]
|
||||
3. [Step] → verify: [check]
|
||||
```
|
||||
|
||||
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||
|
||||
---
|
||||
|
||||
**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
|
||||
|
||||
## 지시 우선순위
|
||||
충돌 시 항상 더 높은 우선순위의 지시를 따른다.
|
||||
|
||||
1. 사용자 직접 지시
|
||||
2. `AGENTS.md`
|
||||
3. 프로젝트별 제약 조건
|
||||
4. `oh-my-openagent` 플러그인의 `agents` / `workflows` / `hooks`
|
||||
5. `superpowers` skills
|
||||
6. 기본 모델 동작
|
||||
|
||||
사용자 직접 지시가 명확할 경우 사용자 지시가 최우선이다. plugin / skill / workflow 지시가 `CORE EXECUTION PRINCIPLES`와 충돌하면 `CORE EXECUTION PRINCIPLES`를 따른다. 불확실하거나 모호한 경우 추측하지 말고 확인하거나, 가능한 최소 범위의 안전한 조치를 취한다.
|
||||
|
||||
## 커뮤니케이션 규칙
|
||||
- **"질문에 대한 답변과 설명은 한국어로 한다."**
|
||||
- 사용자에게 전달하는 설명, 진행 상황, 결과 보고는 한국어로 작성한다.
|
||||
@@ -16,145 +97,58 @@
|
||||
|
||||
### 수정 우선순위
|
||||
- 기능 변경은 `SodaLive/Sources/**`에서 해결한다.
|
||||
- 기존 로직 수정이 아닌 신규 `View`, `ViewModel`, `Repository` 및 그와 연결된 하위 코드는 `SodaLive/Sources/V2/**` 아래에 작성한다.
|
||||
- 프로젝트 설정 변경은 필요한 경우에만 수행한다.
|
||||
- `Pods/**`, `generated/**`는 직접 수정하지 않는다.
|
||||
- `build/**`는 빌드 산출물로 간주하며 수정 대상이 아니다.
|
||||
|
||||
## 빌드/테스트/검증 명령
|
||||
아래 명령은 현재 저장소에서 확인된 공식 진입점이다.
|
||||
## 실행 모드
|
||||
### 기본 모드: 보수적 실행
|
||||
- 최소 변경
|
||||
- 단순한 구현
|
||||
- 검증 가능한 결과
|
||||
|
||||
### 1) 의존성 설치
|
||||
- `pod install`
|
||||
- 근거: `Podfile`에 CocoaPods 타깃(`SodaLive`, `SodaLive-dev`) 정의.
|
||||
### 확장 모드
|
||||
- 사용자가 명시적으로 요청한 경우에만 사용한다.
|
||||
- 대규모 리팩토링, 브레인스토밍, 다중 에이전트 실행, 병렬 workflow를 허용한다.
|
||||
|
||||
### 2) 스킴/타깃 확인
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -list`
|
||||
- 근거: 공유 스킴 `SodaLive`, `SodaLive-dev` 존재.
|
||||
## oh-my-openagent 제어 정책
|
||||
- `oh-my-openagent`는 opencode의 플러그인 기반 실행 오케스트레이션 계층이다.
|
||||
- `oh-my-openagent`는 의사결정 권한이 아니라 실행 보조 권한만 가진다.
|
||||
- 작은 작업에는 multi-agent 실행이나 과도한 workflow를 사용하지 않는다.
|
||||
- 병렬 실행은 명확한 이득이 있을 때만 사용한다.
|
||||
- 모든 `oh-my-openagent` 동작은 `CORE EXECUTION PRINCIPLES`를 따라야 한다.
|
||||
|
||||
### 3) 빌드
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -configuration Debug build`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`
|
||||
|
||||
### 4) 테스트(전체)
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" test`
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" test`
|
||||
|
||||
### 5) 단일 테스트 실행
|
||||
- 일반 형식(테스트 타깃이 있는 경우):
|
||||
- `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive" -only-testing:"SodaLiveTests/TestClass/testMethod" test`
|
||||
- **현재 주의사항**:
|
||||
- `SodaLive.xcodeproj/project.pbxproj` 기준으로 앱 타깃 중심 구성이고 테스트 번들 타깃이 확인되지 않는다.
|
||||
- 따라서 현재 상태에서는 단일 테스트 지정이 실질적으로 동작하지 않을 수 있다.
|
||||
|
||||
### 6) 린트/포맷
|
||||
- 저장소에 공식 `swiftlint`/`swiftformat` 실행 스크립트는 확인되지 않았다.
|
||||
- `generated/*.generated.swift`에 `swiftlint:disable all` 주석은 존재하나, 이는 생성 코드 보호 목적이다.
|
||||
- 린트 도구를 도입/추가하면 본 문서 명령 섹션을 즉시 갱신한다.
|
||||
|
||||
## 코드 스타일 가이드
|
||||
|
||||
### 아키텍처/레이어
|
||||
- 기본 흐름은 `View -> ViewModel -> Repository -> Api(TargetType)`를 따른다.
|
||||
- API는 `enum ...Api: TargetType`, 저장소는 `final class ...Repository` 형태를 우선 사용한다.
|
||||
- 상태 모델은 `struct`/`enum` 중심으로 두고, 화면 상태는 `ObservableObject`에서 관리한다.
|
||||
|
||||
### 임포트 규칙
|
||||
- 시스템 프레임워크(`Foundation`, `SwiftUI`, `Combine`)를 먼저 배치한다.
|
||||
- 서드파티(`Moya`, `CombineMoya`, SDK들)는 이후 배치한다.
|
||||
- import 그룹 사이에는 한 줄 공백으로 의미 단위를 분리한다.
|
||||
|
||||
### 포맷/구조
|
||||
- 들여쓰기는 4칸 스페이스를 사용한다.
|
||||
- 프로퍼티 선언, 비즈니스 로직, 헬퍼 메서드는 공백 줄로 구획한다.
|
||||
- 클로저 체인은 줄바꿈해 가독성을 유지한다.
|
||||
|
||||
### 타입/상태 관리
|
||||
- ViewModel은 `final class ...: ObservableObject` 패턴을 우선한다.
|
||||
- View가 소유하는 객체는 `@StateObject`, 외부 주입 객체는 `@ObservedObject`를 사용한다.
|
||||
- 네트워크 반환은 `AnyPublisher<Response, MoyaError>` 패턴을 기본으로 따른다.
|
||||
|
||||
### 네이밍 규칙
|
||||
- 타입명은 PascalCase (`HomeViewModel`, `UserRepository`, `UserApi`).
|
||||
- 변수/함수는 camelCase (`errorMessage`, `getMemberInfo`).
|
||||
- 역할을 이름에 반영한다 (`*View`, `*ViewModel`, `*Repository`, `*Api`, `*Request`, `*Response`).
|
||||
|
||||
### 비동기/Combine 규칙
|
||||
- 비동기 처리는 Combine의 `sink`, `receiveValue`, `.store(in: &subscription)` 패턴을 따른다.
|
||||
- `sink` 완료 블록에서 `.failure`를 반드시 처리한다.
|
||||
- 클로저 캡처는 상황에 맞게 `[weak self]` 또는 `[unowned self]`를 선택한다.
|
||||
|
||||
### 에러 처리 규칙
|
||||
- 사용자 노출 오류는 `errorMessage`와 팝업 플래그(`isShowPopup`)로 일관되게 처리한다.
|
||||
- JSON 파싱은 `do/catch + JSONDecoder` 패턴을 따른다.
|
||||
- **신규 코드에서 빈 `catch`는 금지**하고, 최소한 로깅 또는 명시적 무시 사유를 남긴다.
|
||||
|
||||
### 로깅 규칙
|
||||
- 디버그 로그는 `DEBUG_LOG`, 오류 로그는 `ERROR_LOG`를 사용한다.
|
||||
- `print`는 임시 디버깅 목적 외 신규 코드에서 지양한다.
|
||||
|
||||
### 네트워크/헤더 규칙
|
||||
- 공통 Moya 플러그인(`AuthPlugin`, `AcceptLanguagePlugin`) 흐름을 유지한다.
|
||||
- 언어 헤더는 `LanguageHeaderProvider.current`를 기준으로 사용한다.
|
||||
|
||||
### 문자열/다국어
|
||||
- 신규 사용자 노출 문자열은 가능하면 `I18n` 경로를 우선 사용한다.
|
||||
- 다국어 기능 수정 시 `Settings/Language` 모듈과 `Accept-Language` 헤더 흐름을 함께 점검한다.
|
||||
|
||||
### 주석/문서화
|
||||
- 자명한 코드에는 주석을 남기지 않는다.
|
||||
- 복잡한 분기, 외부 제약, 부작용이 있는 로직에만 주석을 추가한다.
|
||||
|
||||
## Cursor/Copilot 규칙 반영
|
||||
- 아래 파일 존재 여부를 확인해 AGENTS와 함께 유지한다.
|
||||
- `.cursor/rules/**`
|
||||
- `.cursorrules`
|
||||
- `.github/copilot-instructions.md`
|
||||
- 현재 저장소에서는 위 파일들이 확인되지 않았다.
|
||||
- 추후 파일이 추가되면 AGENTS.md에 요약 규칙을 동기화한다.
|
||||
- 충돌 우선순위 기본값:
|
||||
- 범위가 더 구체적인 규칙이 우선한다(경로 특화 > 저장소 전역).
|
||||
- Cursor: `.cursor/rules/**` > `.cursorrules` > `AGENTS.md`
|
||||
- Copilot: `.github/instructions/**`(존재 시) > `.github/copilot-instructions.md` > `AGENTS.md`
|
||||
|
||||
## 커밋 메시지 규칙 (표준 Conventional Commits)
|
||||
- 커밋 상세 가이드/절차는 `.opencode/skills/commit-policy/SKILL.md`를 단일 기준으로 사용한다.
|
||||
- 커밋 작업 시작 시 `skill` 도구로 `commit-policy`를 먼저 로드한다.
|
||||
- 기본 형식은 `<type>(scope): <description>`를 사용한다.
|
||||
- `type`은 소문자(`feat`, `fix`, `chore`, `docs`, `refactor`, `test`)를 사용한다.
|
||||
- 제목(description)은 한글로 작성하고, 명령형/간결한 현재형으로 작성한다.
|
||||
- 이슈 참조 footer는 `Refs: #123` 또는 `Refs: #123, #456` 형식을 사용한다.
|
||||
|
||||
### 커밋 메시지 검증 절차
|
||||
- `git commit` 직후 `work/scripts/check-commit-message-rules.sh`를 실행해 규칙 준수 여부를 확인한다.
|
||||
- 스크립트 결과가 `[FAIL]`이면 커밋 메시지를 수정한 뒤 다시 검증한다.
|
||||
|
||||
## 작업 절차 체크리스트
|
||||
- 변경 전: 유사 기능 코드를 먼저 찾아 네이밍/예외/응답 패턴을 맞춘다.
|
||||
- 변경 중: 공개 API 스키마를 임의 변경하지 말고 작은 단위로 안전하게 수정한다.
|
||||
- 변경 후: 영향 범위 파일에 대해 빌드/테스트/로그/다국어 키를 점검한다.
|
||||
- 커밋 직후: `commit-policy` 스킬을 로드하고 메시지 검증 스크립트를 실행한다.
|
||||
|
||||
## 작업 계획 문서 규칙 (docs)
|
||||
- 모든 작업 시작 전에 `docs` 폴더 아래 계획 문서를 먼저 생성하고, 해당 문서를 기준으로 구현한다.
|
||||
- 계획 문서 파일명은 `[날짜]_구현할내용한글.md` 형식을 사용한다.
|
||||
- 날짜는 `YYYYMMDD` 8자리 숫자를 사용한다.
|
||||
- 구현 항목은 기능/작업 단위 체크박스(`- [ ]`)로 작성하고 완료 즉시 `- [x]`로 갱신한다.
|
||||
- 작업 도중 범위가 변경되면 계획 문서 체크리스트를 먼저 업데이트한 뒤 구현한다.
|
||||
- 결과 보고 시 문서 하단에 검증 기록(무엇/왜/어떻게, 실행 명령, 결과)을 한국어로 남긴다.
|
||||
- 후속 수정이 발생해도 기존 검증 기록은 삭제/덮어쓰기 없이 누적한다.
|
||||
|
||||
## 문서 유지보수 규칙
|
||||
- 불확실한 규칙은 추측으로 채우지 말고 근거 파일 경로를 먼저 확인한다.
|
||||
- 에이전트 안내 문구는 한국어 중심으로 유지한다.
|
||||
- 명령/경로/타깃명이 바뀌면 본 문서를 즉시 업데이트한다.
|
||||
## superpowers 사용 정책
|
||||
- `superpowers`는 선택적 스킬 계층이다.
|
||||
- `superpowers` skill은 필요한 경우에만 사용한다.
|
||||
- `superpowers`가 과도한 리팩토링, 불필요한 범위 확장, 가정 기반 실행을 유도하면 따르지 않는다.
|
||||
- `superpowers`를 사용할 때도 최소 변경, 단순성, 검증 가능성을 우선한다.
|
||||
- 모든 `superpowers` 동작은 `CORE EXECUTION PRINCIPLES`를 따라야 한다.
|
||||
|
||||
## 에이전트 동작 원칙
|
||||
- 추측하지 말고 근거 파일을 읽고 결정한다.
|
||||
- 기존 관례를 깨는 변경은 이유가 명확할 때만 수행한다.
|
||||
- 불필요한 리팩터링 확장은 피하고 요청 범위를 우선 충족한다.
|
||||
- 결과 보고 시 무엇을, 왜, 어떻게 검증했는지 한국어로 간단히 남긴다.
|
||||
- 상세 실행 정책은 `docs/agent-guides/agent-execution-policy.md`를 참조한다.
|
||||
|
||||
## 개발 세부 가이드
|
||||
- 빌드/테스트/검증 명령은 `docs/agent-guides/build-test-verification.md`를 참조한다.
|
||||
- iOS 코드 스타일은 `docs/agent-guides/code-style.md`를 참조한다.
|
||||
- Cursor/Copilot 규칙은 `docs/agent-guides/sodalive-ios-development.md`를 참조한다.
|
||||
- 커밋 상세 가이드/절차는 `.opencode/skills/commit-policy/SKILL.md`를 단일 기준으로 사용한다.
|
||||
- 커밋 작업 시작 시 `skill` 도구로 `commit-policy`를 먼저 로드한다.
|
||||
- 기본 커밋 형식은 `<type>(scope): <description>`를 사용하고, 제목(description)은 한글 명령형/간결한 현재형으로 작성한다.
|
||||
- `git commit` 직후 `work/scripts/check-commit-message-rules.sh`를 실행해 규칙 준수 여부를 확인한다.
|
||||
|
||||
## 설정/보안 유의사항
|
||||
- 토큰/키/개인정보를 코드/로그/문서에 하드코딩하지 않는다.
|
||||
- 인증 관련 헤더/토큰 처리 로직(`AuthPlugin`, `UserDefaultsKey.token`) 수정 시 회귀 위험을 함께 점검한다.
|
||||
- 외부 SDK 키 변경 시 빌드 설정과 런타임 초기화 지점을 함께 검토한다.
|
||||
|
||||
## 문서 작성 규칙
|
||||
- 구현 전 PRD 작성, 사용자 인터뷰, 계획/TASK 문서 작성, 체크리스트 갱신, 검증 기록 누적, 문서 분리 기준은 `docs/agent-guides/documentation-policy.md`를 따른다.
|
||||
|
||||
## 문서 유지보수 규칙
|
||||
- 상세 문서 유지보수 규칙은 `docs/agent-guides/documentation-policy.md`를 참조한다.
|
||||
|
||||
2
Podfile
@@ -10,6 +10,7 @@ target 'SodaLive' do
|
||||
pod 'AgoraRtm', '2.2.4'
|
||||
pod 'GoogleSignIn'
|
||||
pod 'GoogleSignInSwiftSupport'
|
||||
pod 'YandexMobileAds', '8.0.0'
|
||||
|
||||
end
|
||||
|
||||
@@ -22,6 +23,7 @@ target 'SodaLive-dev' do
|
||||
pod 'AgoraRtm', '2.2.4'
|
||||
pod 'GoogleSignIn'
|
||||
pod 'GoogleSignInSwiftSupport'
|
||||
pod 'YandexMobileAds', '8.0.0'
|
||||
|
||||
end
|
||||
|
||||
|
||||
158
Podfile.lock
@@ -15,6 +15,74 @@ PODS:
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- AppMetricaAdSupport (6.0.0):
|
||||
- AppMetricaCore (= 6.0.0)
|
||||
- AppMetricaCoreExtension (= 6.0.0)
|
||||
- AppMetricaCore (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaEncodingUtils (= 6.0.0)
|
||||
- AppMetricaFMDB (= 6.0.0)
|
||||
- AppMetricaHostState (= 6.0.0)
|
||||
- AppMetricaIdentifiers (= 6.0.0)
|
||||
- AppMetricaKeychain (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaNetwork (= 6.0.0)
|
||||
- AppMetricaPlatform (= 6.0.0)
|
||||
- AppMetricaProtobuf (= 6.0.0)
|
||||
- AppMetricaProtobufUtils (= 6.0.0)
|
||||
- AppMetricaStorageUtils (= 6.0.0)
|
||||
- AppMetricaCoreExtension (6.0.0):
|
||||
- AppMetricaCore (= 6.0.0)
|
||||
- AppMetricaStorageUtils (= 6.0.0)
|
||||
- AppMetricaCoreUtils (6.0.0):
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaEncodingUtils (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaPlatform (= 6.0.0)
|
||||
- AppMetricaFMDB (6.0.0)
|
||||
- AppMetricaHostState (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaIdentifiers (6.0.0):
|
||||
- AppMetricaKeychain (= 6.0.0)
|
||||
- AppMetricaLogSwift (= 6.0.0)
|
||||
- AppMetricaPlatform (= 6.0.0)
|
||||
- AppMetricaStorageUtils (= 6.0.0)
|
||||
- AppMetricaSynchronization (= 6.0.0)
|
||||
- AppMetricaIDSync (6.0.0):
|
||||
- AppMetricaCore (= 6.0.0)
|
||||
- AppMetricaCoreExtension (= 6.0.0)
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaNetwork (= 6.0.0)
|
||||
- AppMetricaPlatform (= 6.0.0)
|
||||
- AppMetricaStorageUtils (= 6.0.0)
|
||||
- AppMetricaKeychain (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaStorageUtils (= 6.0.0)
|
||||
- AppMetricaLibraryAdapter (6.0.0):
|
||||
- AppMetricaCore (= 6.0.0)
|
||||
- AppMetricaCoreExtension (= 6.0.0)
|
||||
- AppMetricaLog (6.0.0)
|
||||
- AppMetricaLogSwift (6.0.0):
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaNetwork (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaPlatform (= 6.0.0)
|
||||
- AppMetricaPlatform (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaProtobuf (6.0.0)
|
||||
- AppMetricaProtobufUtils (6.0.0):
|
||||
- AppMetricaProtobuf (= 6.0.0)
|
||||
- AppMetricaStorageUtils (6.0.0):
|
||||
- AppMetricaCoreUtils (= 6.0.0)
|
||||
- AppMetricaLog (= 6.0.0)
|
||||
- AppMetricaSynchronization (6.0.0):
|
||||
- AppMetricaLogSwift (= 6.0.0)
|
||||
- Bootpay (4.4.6):
|
||||
- CryptoSwift
|
||||
- NVActivityIndicatorView
|
||||
@@ -27,6 +95,19 @@ PODS:
|
||||
- SnapKit
|
||||
- SwiftyJSON
|
||||
- CryptoSwift (1.8.4)
|
||||
- DivKit (32.46.0):
|
||||
- DivKit_LayoutKit (= 32.46.0)
|
||||
- DivKit_Serialization (= 32.46.0)
|
||||
- VGSL (~> 7.21)
|
||||
- DivKit_LayoutKit (32.46.0):
|
||||
- DivKit_LayoutKitInterface (= 32.46.0)
|
||||
- VGSL (~> 7.21)
|
||||
- DivKit_LayoutKitInterface (32.46.0):
|
||||
- VGSL (~> 7.21)
|
||||
- DivKit_Serialization (32.46.0):
|
||||
- VGSL (~> 7.21)
|
||||
- DivKitBinaryCompatibilityFacade (5.3.0):
|
||||
- DivKit (~> 32.14)
|
||||
- GoogleSignIn (9.1.0):
|
||||
- AppAuth (~> 2.0)
|
||||
- AppCheckCore (~> 11.0)
|
||||
@@ -54,12 +135,29 @@ PODS:
|
||||
- PromisesObjC (2.4.0)
|
||||
- SnapKit (5.7.1)
|
||||
- SwiftyJSON (5.0.2)
|
||||
- VGSL (7.21.0):
|
||||
- VGSLFundamentals (= 7.21.0)
|
||||
- VGSLNetworking (= 7.21.0)
|
||||
- VGSLUI (= 7.21.0)
|
||||
- VGSLFundamentals (7.21.0)
|
||||
- VGSLNetworking (7.21.0):
|
||||
- VGSLFundamentals (= 7.21.0)
|
||||
- VGSLUI (= 7.21.0)
|
||||
- VGSLUI (7.21.0):
|
||||
- VGSLFundamentals (= 7.21.0)
|
||||
- YandexMobileAds (8.0.0):
|
||||
- AppMetricaAdSupport (~> 6.0.0)
|
||||
- AppMetricaCore (~> 6.0.0)
|
||||
- AppMetricaIDSync (~> 6.0.0)
|
||||
- AppMetricaLibraryAdapter (~> 6.0.0)
|
||||
- DivKitBinaryCompatibilityFacade (~> 5.3.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AgoraRtm (= 2.2.4)
|
||||
- BootpayUI (= 4.4.10)
|
||||
- GoogleSignIn
|
||||
- GoogleSignInSwiftSupport
|
||||
- YandexMobileAds (= 8.0.0)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -67,9 +165,33 @@ SPEC REPOS:
|
||||
- Alamofire
|
||||
- AppAuth
|
||||
- AppCheckCore
|
||||
- AppMetricaAdSupport
|
||||
- AppMetricaCore
|
||||
- AppMetricaCoreExtension
|
||||
- AppMetricaCoreUtils
|
||||
- AppMetricaEncodingUtils
|
||||
- AppMetricaFMDB
|
||||
- AppMetricaHostState
|
||||
- AppMetricaIdentifiers
|
||||
- AppMetricaIDSync
|
||||
- AppMetricaKeychain
|
||||
- AppMetricaLibraryAdapter
|
||||
- AppMetricaLog
|
||||
- AppMetricaLogSwift
|
||||
- AppMetricaNetwork
|
||||
- AppMetricaPlatform
|
||||
- AppMetricaProtobuf
|
||||
- AppMetricaProtobufUtils
|
||||
- AppMetricaStorageUtils
|
||||
- AppMetricaSynchronization
|
||||
- Bootpay
|
||||
- BootpayUI
|
||||
- CryptoSwift
|
||||
- DivKit
|
||||
- DivKit_LayoutKit
|
||||
- DivKit_LayoutKitInterface
|
||||
- DivKit_Serialization
|
||||
- DivKitBinaryCompatibilityFacade
|
||||
- GoogleSignIn
|
||||
- GoogleSignInSwiftSupport
|
||||
- GoogleUtilities
|
||||
@@ -80,15 +202,44 @@ SPEC REPOS:
|
||||
- PromisesObjC
|
||||
- SnapKit
|
||||
- SwiftyJSON
|
||||
- VGSL
|
||||
- VGSLFundamentals
|
||||
- VGSLNetworking
|
||||
- VGSLUI
|
||||
- YandexMobileAds
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AgoraRtm: 534144434383d41b3b0ebfae2a961ef0f51b0645
|
||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||
AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
AppMetricaAdSupport: 43a4d1509cdbfe712fb3f009fe60d3a6481816a0
|
||||
AppMetricaCore: dae62fe7f95cd665b142218f3d94cf63262c195b
|
||||
AppMetricaCoreExtension: 72da13ba849d4676f276ab86ff429bdf700eadc3
|
||||
AppMetricaCoreUtils: 5e7c91cbafe0225dec2ded2bb3a806256e2ef791
|
||||
AppMetricaEncodingUtils: a67df57f752dbbb174beea18f1de52d24853d834
|
||||
AppMetricaFMDB: a3d8e45a5a85bec23a997be4469b92ab355a4df5
|
||||
AppMetricaHostState: 1ac2ab5880aa30a358f55a044e57354dd8f9062f
|
||||
AppMetricaIdentifiers: 00061e0cdcb371b74343a5491f54adfc1d470b25
|
||||
AppMetricaIDSync: 91b403172ad78da3574c691ed34c25f69c2296ee
|
||||
AppMetricaKeychain: 9ec64d877b8a3f6e823f3bbeef5e4086aa456686
|
||||
AppMetricaLibraryAdapter: 56fa0f988850051d10f9ac3b6b9ace7bb6aa8fd7
|
||||
AppMetricaLog: edd74df81c7557439c36c566989982f4c735e4d6
|
||||
AppMetricaLogSwift: 3d2d4a3cbc33a680389b416a1c6a82ef4134da10
|
||||
AppMetricaNetwork: 3dc6d768d4e932c3697c9c2c9a768de54e62059b
|
||||
AppMetricaPlatform: 041a7b251ea1689e26626d9db8f331af1afe5bad
|
||||
AppMetricaProtobuf: 01b141a164fa7277f641a29f30d5e571ea3d471a
|
||||
AppMetricaProtobufUtils: dc48c7b84f3a1ef86ea218bbd97480ea9da4d3bc
|
||||
AppMetricaStorageUtils: 78071115b9f5468d9e3b8184c456428cd9ee1dbd
|
||||
AppMetricaSynchronization: 909bab97c61c0c147a435ce1620e4c8069e2d6b3
|
||||
Bootpay: cd7f0542b096ab0af0b09a6e12a6b87f2cbbb531
|
||||
BootpayUI: beec5b0bba002b4dbced8c0ecace571ed6a017bc
|
||||
CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90
|
||||
DivKit: c66e0fa88b4671f832fb9ca3f142d6f56a56919d
|
||||
DivKit_LayoutKit: e30d4d345034c2dfb356e5a891dd359ac79a5aff
|
||||
DivKit_LayoutKitInterface: 320f0ef8c4f95bb8212b13400502adf0259c0b21
|
||||
DivKit_Serialization: c5ba4f12034eca16960e80f369b689fd8cda95b0
|
||||
DivKitBinaryCompatibilityFacade: fc2284a2edea4d65aa0966006ea63274eb9f721b
|
||||
GoogleSignIn: fcee2257188d5eda57a5e2b6a715550ffff9206d
|
||||
GoogleSignInSwiftSupport: aca902e4e15b234611ecac74ef5c8f61278f774e
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
@@ -99,7 +250,12 @@ SPEC CHECKSUMS:
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
|
||||
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
|
||||
VGSL: 0573c2b82b05aadcba4836398ce3778d271bfd13
|
||||
VGSLFundamentals: 3a081684c1a5df5800bf88aca8a9bdff2c10cfd9
|
||||
VGSLNetworking: 0ea8a335bc4f4eba3f6123ffe441cf1c08f267f2
|
||||
VGSLUI: 249a16cccdb75f1a5a1733894d013bca76e27c5e
|
||||
YandexMobileAds: ca6c63c4148ae87fefc1821d0b466ea567069d5b
|
||||
|
||||
PODFILE CHECKSUM: 70c5639090824ff26cfad959985347579609e1e6
|
||||
PODFILE CHECKSUM: 525ba559e93875de1314bb1a7894791eee442151
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
6
SodaLive/Resources/Assets.xcassets/v2/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
21
SodaLive/Resources/Assets.xcassets/v2/ic_bar_bell.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_bar_bell.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_bar_bell.imageset/ic_bar_bell.png
vendored
Normal file
|
After Width: | Height: | Size: 401 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_bar_cash.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_bar_cash.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_bar_cash.imageset/ic_bar_cash.png
vendored
Normal file
|
After Width: | Height: | Size: 693 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_bar_search.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_bar_search.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_bar_search.imageset/ic_bar_search.png
vendored
Normal file
|
After Width: | Height: | Size: 369 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_chat.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_chat.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_chat.imageset/ic_nav_chat.png
vendored
Normal file
|
After Width: | Height: | Size: 602 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_chat_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_chat_selected.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_chat_selected.imageset/ic_nav_chat_selected.png
vendored
Normal file
|
After Width: | Height: | Size: 446 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_content.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_content.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_content.imageset/ic_nav_content.png
vendored
Normal file
|
After Width: | Height: | Size: 537 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_content_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_content_selected.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_content_selected.imageset/ic_nav_content_selected.png
vendored
Normal file
|
After Width: | Height: | Size: 378 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_home.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_home.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_home.imageset/ic_nav_home.png
vendored
Normal file
|
After Width: | Height: | Size: 564 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_home_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_home_selected.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_home_selected.imageset/ic_nav_home_selected.png
vendored
Normal file
|
After Width: | Height: | Size: 408 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_my.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_my.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_my.imageset/ic_nav_my.png
vendored
Normal file
|
After Width: | Height: | Size: 723 B |
21
SodaLive/Resources/Assets.xcassets/v2/ic_nav_my_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_nav_my_selected.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/ic_nav_my_selected.imageset/ic_nav_my_selected.png
vendored
Normal file
|
After Width: | Height: | Size: 556 B |
21
SodaLive/Resources/Assets.xcassets/v2/img_text_logo_v2.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img_text_logo_v2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/v2/img_text_logo_v2.imageset/img_text_logo_v2.png
vendored
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
@@ -39,7 +39,6 @@
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<!-- Specify URL scheme to use when returning from LINE to your app. -->
|
||||
<string>line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
@@ -67,49 +66,113 @@
|
||||
</dict>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cstr6suwn9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
<string>n9x2a789qt.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
<string>r26jy69rpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
<string>5l3tpt7t6e.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
<string>su67r6k2v3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ludvb6z3bs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>633vhxswh4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>tmhh9296z4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vcra2ehyfk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zh3b7bxvad.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xmn954pzmp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>79w64w269u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>488r3q3dtq.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>d7g9azk84q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nzq8sh4pbs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>866k9ut3g3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2q884k2j68.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x8jxxk4ff5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gfat3222tu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pd25vrrwzn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>lr83yxwka7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cp8zw746q7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
<string>pwdxu55a5a.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
@@ -119,18 +182,198 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s39g8k73mm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wg4vff78zm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g28c52eehv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>523jb4fst2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>294l99pt4k.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qy4746246.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a8cz6cu7e5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ggvn48r87g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y755zyxw56.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>qlbq5gtkt8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mls7yz5dvl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>67369282zy.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>899vrgt9g8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mj797d8u6f.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f38h382jlk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>24t9a8vw3c.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mp6xlyr22a.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x44k69ngh6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>88k8774x49.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hs6bdukanm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t3b3f7n3x8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>prcb7njmu6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c7g47wypnu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>52fl2v3hgk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9vvzujtq5s.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>m8dbw4sv7c.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9g2aggbj52.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>m5mvw97r93.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>z5b3gh5ugf.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dd3a75yxkv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9nlqeag3gk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cj5566h2ga.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>h5jmj969g5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dr774724x4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t7ky8fmwkd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>fz2k2k5tej.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>u679fj5vs4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cs644xg564.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9b89h5y424.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>w28pnjg2k4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2rq3zucswp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a7xqa6mtl2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g2y4y55b64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vc83br9sjg.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eqhxz8m8av.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7k3cvf297u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>w9q455wk68.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nu4557a4je.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v4nxqhlyqp.skadnetwork</string>
|
||||
@@ -139,10 +382,46 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wzmmz9fp6w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7fmhfwg9en.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yclnxrl5pm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7tnzynbdc7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>l6nv3x923s.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>h8vml93bkz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uzqba5354d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8qiegk9qfv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v79kvwwj4g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xx9sdjej2w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>au67k4efj4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t38b2kh725.skadnetwork</string>
|
||||
@@ -153,11 +432,31 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
<string>rx5hdcabgc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
<string>5lm9lj6jb7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>qqp299437r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zmvfpc5aq8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9rd848q2bz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>79pbpufp6p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dmv22haz9p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
@@ -167,6 +466,10 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n6fk4nfna4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7rz58n8ntl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v9wttpbfk9.skadnetwork</string>
|
||||
@@ -175,34 +478,134 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n38lu8286q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>feyaarzu9v.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7fbxrn65az.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>47vhws6wlr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
<string>ejvt5qm6ak.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>b55w3d8y8z.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v7896pgt74.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5ghnmfs3dh.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>275upjj5gd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>627r9wr2y5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>sczv5946wb.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8w3np9l82g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hb56zgv37p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9t245vhmpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nrt9jy4kw9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7953jerfzd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dn942472g5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6v7lgmsu45.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cad8qz2s3j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eh6m2bh4zr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>jb7bn6koa5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>fkak3gfpt6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a2p9lx4jpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>97r2b46745.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>22mmun2rn5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>238da6jt44.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>44jx6755aq.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>b9bk5wbcq9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>k674qkevps.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>tl55sbb4fm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>24zw6aqk47.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4468km3ulz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2tdux39lx8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2u9pt9hc89.skadnetwork</string>
|
||||
@@ -211,22 +614,106 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8s468mfl3y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3cgn6rq224.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>glqzh8vgby.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>av6w8kgt66.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>klf5c3l5u5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nfqy3847ph.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dticjx1a9i.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ppxm28t8ap.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9wsyqb3ku7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>74b6s63p6l.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xy9t38ct57.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>424m5254lk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>qu637u8glc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f73kdq92p3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>44n7hlldy6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbmxgpxpgc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ecpz2srf59.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x5854y7y24.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f7s53z58qe.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x8uqf25wch.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uw77j35x4d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6964rsfnh4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gvmwg8q7h5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6yxyv74ff7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>84993kbrcf.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>54nzkqm89y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pwa73g5rt2.skadnetwork</string>
|
||||
@@ -235,13 +722,49 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mlmmfzh3r3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9yg77x724h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n66cz3y3bx.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>578prtvx9j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
<string>bvpn9ufa9b.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6qx585k4p6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mtkv5xtk9e.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>l93v5h6a4m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>rvh3l7un93.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5tjdwbrq8w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>r45fhb6rf7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>32z4fx6l9h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
@@ -253,16 +776,208 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
<string>axh5283zss.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3rd42ekr43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5mv394q32t.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4w7y6s5ca2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>252b5q8x7y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3l6bd9hu43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4mn522wn87.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6g9af3uyq4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6p4ks3rnbw.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6xzpu9s2p8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>737z793b9f.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>89z7zv988g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8m87ys6875.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8r8llnkz5a.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>bxvub5ada5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c3frkrj4fj.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cg4yq2srnc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dbu4b84rxf.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dkc879ngq3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dzg6xy7pwj.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta8lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hdw39hrw9y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hjevpa356n.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>krvm3zuq6h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ln5gz23vtd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>m297p6643m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pu4na253f3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s69wq72ugq.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t6d3zquu66.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x2jnk7ly8j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x5l83yy675.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y45688jllp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yrqqpx2mcb.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>z4gj7hsk7h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>33r6p7g8nc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g69uk9uh2b.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g6gcrrvk4p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mqn7fxpca7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ns5j362hk7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vhf287vqwu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xga6mpmplv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>53nm4hsx3w.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
@@ -283,13 +998,9 @@
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<!-- 카카오톡으로 로그인 -->
|
||||
<string>kakaokompassauth</string>
|
||||
<!-- 카카오톡 공유 -->
|
||||
<string>kakaolink</string>
|
||||
<!-- 카카오톡 채널 -->
|
||||
<string>kakaoplus</string>
|
||||
<!-- 라인 -->
|
||||
<string>lineauth2</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<!-- Specify URL scheme to use when returning from LINE to your app. -->
|
||||
<string>line3rdp.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
@@ -67,49 +66,113 @@
|
||||
</dict>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cstr6suwn9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
<string>n9x2a789qt.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
<string>r26jy69rpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
<string>5l3tpt7t6e.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
<string>su67r6k2v3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ludvb6z3bs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>633vhxswh4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>tmhh9296z4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vcra2ehyfk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zh3b7bxvad.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xmn954pzmp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>79w64w269u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>488r3q3dtq.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>d7g9azk84q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nzq8sh4pbs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>866k9ut3g3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2q884k2j68.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x8jxxk4ff5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gfat3222tu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pd25vrrwzn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>lr83yxwka7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cp8zw746q7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
<string>pwdxu55a5a.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
@@ -119,18 +182,198 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s39g8k73mm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wg4vff78zm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g28c52eehv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>523jb4fst2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>294l99pt4k.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qy4746246.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a8cz6cu7e5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ggvn48r87g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y755zyxw56.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>qlbq5gtkt8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mls7yz5dvl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>67369282zy.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>899vrgt9g8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mj797d8u6f.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f38h382jlk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>24t9a8vw3c.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mp6xlyr22a.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x44k69ngh6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>88k8774x49.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hs6bdukanm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t3b3f7n3x8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>prcb7njmu6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c7g47wypnu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>52fl2v3hgk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9vvzujtq5s.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>m8dbw4sv7c.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9g2aggbj52.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>m5mvw97r93.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>z5b3gh5ugf.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dd3a75yxkv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9nlqeag3gk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cj5566h2ga.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>h5jmj969g5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dr774724x4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t7ky8fmwkd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>fz2k2k5tej.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>u679fj5vs4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cs644xg564.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9b89h5y424.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>w28pnjg2k4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2rq3zucswp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a7xqa6mtl2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g2y4y55b64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vc83br9sjg.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eqhxz8m8av.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7k3cvf297u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>w9q455wk68.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nu4557a4je.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v4nxqhlyqp.skadnetwork</string>
|
||||
@@ -139,10 +382,46 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wzmmz9fp6w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7fmhfwg9en.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yclnxrl5pm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7tnzynbdc7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>l6nv3x923s.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>h8vml93bkz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uzqba5354d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8qiegk9qfv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v79kvwwj4g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xx9sdjej2w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>au67k4efj4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t38b2kh725.skadnetwork</string>
|
||||
@@ -153,11 +432,31 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
<string>rx5hdcabgc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
<string>5lm9lj6jb7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>qqp299437r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zmvfpc5aq8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9rd848q2bz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>79pbpufp6p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dmv22haz9p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
@@ -167,6 +466,10 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n6fk4nfna4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7rz58n8ntl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v9wttpbfk9.skadnetwork</string>
|
||||
@@ -175,34 +478,134 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n38lu8286q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>feyaarzu9v.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7fbxrn65az.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>47vhws6wlr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
<string>ejvt5qm6ak.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>b55w3d8y8z.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v7896pgt74.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5ghnmfs3dh.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>275upjj5gd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>627r9wr2y5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>sczv5946wb.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8w3np9l82g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hb56zgv37p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9t245vhmpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nrt9jy4kw9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7953jerfzd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dn942472g5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6v7lgmsu45.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cad8qz2s3j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eh6m2bh4zr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>jb7bn6koa5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>fkak3gfpt6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a2p9lx4jpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>97r2b46745.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>22mmun2rn5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>238da6jt44.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>44jx6755aq.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>b9bk5wbcq9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>k674qkevps.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>tl55sbb4fm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>24zw6aqk47.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4468km3ulz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2tdux39lx8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2u9pt9hc89.skadnetwork</string>
|
||||
@@ -211,22 +614,106 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8s468mfl3y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3cgn6rq224.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>glqzh8vgby.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>av6w8kgt66.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>klf5c3l5u5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>nfqy3847ph.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dticjx1a9i.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ppxm28t8ap.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9wsyqb3ku7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>74b6s63p6l.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xy9t38ct57.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>424m5254lk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>qu637u8glc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f73kdq92p3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>44n7hlldy6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbmxgpxpgc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ecpz2srf59.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x5854y7y24.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f7s53z58qe.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x8uqf25wch.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uw77j35x4d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6964rsfnh4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gvmwg8q7h5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6yxyv74ff7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>84993kbrcf.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>54nzkqm89y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pwa73g5rt2.skadnetwork</string>
|
||||
@@ -235,13 +722,49 @@
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mlmmfzh3r3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9yg77x724h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n66cz3y3bx.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>578prtvx9j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
<string>bvpn9ufa9b.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6qx585k4p6.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mtkv5xtk9e.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>l93v5h6a4m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>rvh3l7un93.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5tjdwbrq8w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>r45fhb6rf7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>32z4fx6l9h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
@@ -253,16 +776,208 @@
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
<string>axh5283zss.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3rd42ekr43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5mv394q32t.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4w7y6s5ca2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>252b5q8x7y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3l6bd9hu43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4mn522wn87.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6g9af3uyq4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6p4ks3rnbw.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>6xzpu9s2p8.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>737z793b9f.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>89z7zv988g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8m87ys6875.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8r8llnkz5a.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>bxvub5ada5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c3frkrj4fj.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cg4yq2srnc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dbu4b84rxf.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dkc879ngq3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>dzg6xy7pwj.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta8lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hdw39hrw9y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hjevpa356n.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>krvm3zuq6h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ln5gz23vtd.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>m297p6643m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pu4na253f3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s69wq72ugq.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t6d3zquu66.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x2jnk7ly8j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>x5l83yy675.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y45688jllp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yrqqpx2mcb.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>z4gj7hsk7h.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>33r6p7g8nc.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g69uk9uh2b.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>g6gcrrvk4p.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mqn7fxpca7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ns5j362hk7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vhf287vqwu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>xga6mpmplv.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>53nm4hsx3w.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
@@ -283,13 +998,9 @@
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<!-- 카카오톡으로 로그인 -->
|
||||
<string>kakaokompassauth</string>
|
||||
<!-- 카카오톡 공유 -->
|
||||
<string>kakaolink</string>
|
||||
<!-- 카카오톡 채널 -->
|
||||
<string>kakaoplus</string>
|
||||
<!-- Specify URL scheme to use when launching LINE from your app. -->
|
||||
<string>lineauth2</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
||||
@@ -15,6 +15,7 @@ import FirebaseCore
|
||||
import FirebaseAnalytics
|
||||
import FirebaseMessaging
|
||||
import LineSDK
|
||||
import YandexMobileAds
|
||||
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
@@ -30,6 +31,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
Messaging.messaging().delegate = self
|
||||
setupAppsFlyer()
|
||||
|
||||
// YandexAds 초기화
|
||||
YandexAds.initializeSDK(completionHandler: nil)
|
||||
|
||||
// For iOS 10 display notification (sent via APNS)
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct ApplyMethodView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("오디션 지원방식")
|
||||
Text(I18n.Audition.ApplyMethod.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.graybb)
|
||||
.padding(.top, 33.3)
|
||||
@@ -39,7 +39,7 @@ struct ApplyMethodView: View {
|
||||
HStack(spacing: 3) {
|
||||
Image("ic_upload")
|
||||
|
||||
Text("파일 업로드")
|
||||
Text(I18n.Audition.ApplyMethod.fileUpload)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ struct ApplyMethodView: View {
|
||||
HStack(spacing: 3) {
|
||||
Image("ic_mic_color_button")
|
||||
|
||||
Text("바로 녹음")
|
||||
Text(I18n.Audition.ApplyMethod.recordNow)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
@@ -81,7 +81,7 @@ struct ApplyMethodView: View {
|
||||
.padding(.top, 21.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("※ 파일은 mp3, aac만 업로드 가능")
|
||||
Text(I18n.Audition.ApplyMethod.fileFormatNotice)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
@@ -30,7 +30,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
VStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션 녹음")
|
||||
Text(I18n.Audition.Recording.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -76,7 +76,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 15.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb.opacity(0))
|
||||
|
||||
@@ -99,7 +99,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("삭제")
|
||||
Text(I18n.Common.delete)
|
||||
.appFont(size: 15.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
.onTapGesture {
|
||||
@@ -113,7 +113,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
.padding(.vertical, 52.3)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Text("다시 녹음")
|
||||
Text(I18n.Audition.Recording.recordAgain)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.frame(width: (proxy.size.width - 40) / 3, height: 50)
|
||||
@@ -129,7 +129,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
soundManager.recordMode = .RECORD
|
||||
}
|
||||
|
||||
Text("녹음완료")
|
||||
Text(I18n.Audition.Recording.recordComplete)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: (proxy.size.width - 40) * 2 / 3, height: 50)
|
||||
@@ -140,7 +140,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
let soundData = try Data(contentsOf: soundManager.getAudioFileURL())
|
||||
onClickCompleteRecording(tempFileName, soundData)
|
||||
} catch {
|
||||
errorMessage = "녹음파일을 생성하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
errorMessage = I18n.Audition.Recording.createFileFailed
|
||||
isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ struct AuditionApplyView: View {
|
||||
if isShow {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션 지원")
|
||||
Text(I18n.Audition.Apply.title)
|
||||
.appFont(size: 18.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -45,7 +45,7 @@ struct AuditionApplyView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("녹음파일")
|
||||
Text(I18n.Audition.Apply.recordingFile)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.top, 20)
|
||||
@@ -53,7 +53,7 @@ struct AuditionApplyView: View {
|
||||
HStack(spacing: 4) {
|
||||
Image("ic_note_square")
|
||||
|
||||
Text(filename)
|
||||
Text(displayFileName)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.grayd2)
|
||||
|
||||
@@ -66,12 +66,12 @@ struct AuditionApplyView: View {
|
||||
.cornerRadius(5.3)
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("연락처")
|
||||
Text(I18n.Audition.Apply.contact)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
TextField("합격시 받을 연락처를 남겨주세요", text: $phoneNumber)
|
||||
TextField(I18n.Audition.Apply.contactPlaceholder, text: $phoneNumber)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.decimalPad)
|
||||
@@ -89,7 +89,7 @@ struct AuditionApplyView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("보이스온 오디오 드라마 오디션 합격시 개인 연락을 위한 개인 정보(연락처) 수집 및 활용에 동의합니다.\n오디션 지원자는 개인정보 수집 및 활용 동의에 거부할 권리가 있으며 비동의시 오디션 지원은 취소 됩니다.")
|
||||
Text(I18n.Audition.Apply.privacyAgreement)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.lineSpacing(3)
|
||||
@@ -100,7 +100,7 @@ struct AuditionApplyView: View {
|
||||
isAgree.toggle()
|
||||
}
|
||||
|
||||
Text("오디션 지원하기")
|
||||
Text(I18n.Audition.Apply.submit)
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.vertical, 13.3)
|
||||
@@ -110,7 +110,7 @@ struct AuditionApplyView: View {
|
||||
.padding(.top, 35)
|
||||
.onTapGesture {
|
||||
if !isAgree {
|
||||
errorMessage = "연락처 수집 및 활용에 동의하셔야 오디션 지원이 가능합니다."
|
||||
errorMessage = I18n.Audition.Apply.requireAgreement
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -137,6 +137,14 @@ struct AuditionApplyView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var displayFileName: String {
|
||||
if filename.hasPrefix("voiceon_now_voice_") {
|
||||
return I18n.Audition.Apply.recordedVoiceFileName
|
||||
}
|
||||
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@@ -24,7 +24,7 @@ struct AuditionView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("오디션")
|
||||
Text(I18n.Audition.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
@@ -43,13 +43,13 @@ struct AuditionView: View {
|
||||
.background(Color.black)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("보이스온 오디션 이용방법")
|
||||
Text(I18n.Audition.List.usageGuide)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("자세히>")
|
||||
Text(I18n.Audition.List.detail)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
@@ -73,17 +73,17 @@ struct AuditionView: View {
|
||||
if $0 == 0 && !item.isOff {
|
||||
VStack(alignment: .leading, spacing: 25) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션")
|
||||
Text(I18n.Audition.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(" ON")
|
||||
Text(I18n.Audition.List.onStatus)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.mainRed)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("총 \(viewModel.inProgressCount)개")
|
||||
Text(I18n.Audition.List.totalCount(viewModel.inProgressCount))
|
||||
.appFont(size: 11.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
}
|
||||
@@ -111,17 +111,17 @@ struct AuditionView: View {
|
||||
.padding(.top, 5)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션")
|
||||
Text(I18n.Audition.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(" OFF")
|
||||
Text(I18n.Audition.List.offStatus)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("총 \(viewModel.completedCount)개")
|
||||
Text(I18n.Audition.List.totalCount(viewModel.completedCount))
|
||||
.appFont(size: 11.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
}
|
||||
|
||||
@@ -66,13 +66,13 @@ final class AuditionViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ struct AuditionDetailView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.cornerRadius(6.7)
|
||||
|
||||
Text("오디션 정보")
|
||||
Text(I18n.Audition.Detail.informationTitle)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
@@ -38,7 +38,7 @@ struct AuditionDetailView: View {
|
||||
ExpandableTextView(text: response.information)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("오디션 캐릭터")
|
||||
Text(I18n.Audition.Detail.characterTitle)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
@@ -18,7 +18,7 @@ final class AuditionDetailViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
|
||||
@Published var response: GetAuditionDetailResponse? = nil
|
||||
@Published var title: String = "보이스온"
|
||||
@Published var title: String = I18n.Audition.defaultTitle
|
||||
|
||||
func getAuditionDetail(auditionId: Int, onFailure: @escaping () -> Void) {
|
||||
isLoading = true
|
||||
@@ -45,7 +45,7 @@ final class AuditionDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -54,7 +54,7 @@ final class AuditionDetailViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
onFailure()
|
||||
|
||||
@@ -64,7 +64,7 @@ class AuditionSoundManager: NSObject, ObservableObject {
|
||||
|
||||
private func setupPlayer(with url: String) {
|
||||
guard let url = URL(string: url) else {
|
||||
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Sound.playbackFailed
|
||||
self.isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -92,7 +92,7 @@ class AuditionSoundManager: NSObject, ObservableObject {
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Sound.playbackFailed
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ struct AuditionDetailRoleItemView: View {
|
||||
.opacity(item.isComplete ? 0.7 : 0.0)
|
||||
)
|
||||
|
||||
Text(item.isComplete ? "모집완료" : "모집중")
|
||||
Text(item.isComplete ? I18n.Audition.Detail.recruitmentClosed : I18n.Audition.Detail.recruiting)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 9)
|
||||
|
||||
@@ -45,7 +45,7 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
HStack(spacing: 14) {
|
||||
if let url = URL(string: roleDetail.originalWorkUrl), UIApplication.shared.canOpenURL(url) {
|
||||
Text("원작 보러가기")
|
||||
Text(I18n.Audition.Detail.viewOriginalWork)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 12)
|
||||
@@ -59,7 +59,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
if let url = URL(string: roleDetail.auditionScriptUrl), UIApplication.shared.canOpenURL(url) {
|
||||
Text("오디션 대본 확인")
|
||||
Text(I18n.Audition.Detail.checkScript)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 12)
|
||||
@@ -74,7 +74,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
Text("오디션 캐릭터 정보")
|
||||
Text(I18n.Audition.Detail.characterInfo)
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
@@ -83,13 +83,13 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
if viewModel.applicantList.isEmpty {
|
||||
Text("지원자가 없습니다.")
|
||||
Text(I18n.Audition.Detail.noApplicants)
|
||||
.appFont(size: 13, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
Text("참여자")
|
||||
Text(I18n.Audition.Detail.participants)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
@@ -98,13 +98,13 @@ struct AuditionRoleDetailView: View {
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.leading, 2.3)
|
||||
|
||||
Text("명")
|
||||
Text(I18n.Audition.Detail.personUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Audition.Detail.sortNewest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
viewModel.sortType == .NEWEST ? Color.button : Color.graybb
|
||||
@@ -113,7 +113,7 @@ struct AuditionRoleDetailView: View {
|
||||
viewModel.setSortType(sortType: .NEWEST)
|
||||
}
|
||||
|
||||
Text("좋아요순")
|
||||
Text(I18n.Audition.Detail.sortLikes)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
viewModel.sortType == .LIKES ? Color.button : Color.graybb
|
||||
@@ -161,7 +161,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
if let roleDetail = viewModel.auditionRoleDetail {
|
||||
Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원")
|
||||
Text(roleDetail.isAlreadyApplicant ? I18n.Audition.Apply.reapply : I18n.Audition.Apply.apply)
|
||||
.appFont(size: 15.3, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(14)
|
||||
@@ -241,9 +241,9 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if isShowNoticeReapply {
|
||||
SodaDialog(
|
||||
title: "재지원 안내",
|
||||
desc: "재지원 시 이전 지원 내역은 삭제되며 받은 투표수는 무효 처리됩니다.",
|
||||
confirmButtonTitle: "확인"
|
||||
title: I18n.Audition.Apply.reapplyNoticeTitle,
|
||||
desc: I18n.Audition.Apply.reapplyNoticeDesc,
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
isShowNoticeReapply = false
|
||||
isShowApplyMethodView = true
|
||||
@@ -252,9 +252,9 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if isShowNoticeAuthView {
|
||||
SodaDialog(
|
||||
title: "- 본인인증 -",
|
||||
desc: "마이페이지에서 '본인인증'을 하고 다시 오디션에 지원해 주세요.",
|
||||
confirmButtonTitle: "확인"
|
||||
title: I18n.Audition.Apply.authRequiredTitle,
|
||||
desc: I18n.Audition.Apply.authRequiredDesc,
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
isShowNoticeAuthView = false
|
||||
}
|
||||
@@ -264,7 +264,7 @@ struct AuditionRoleDetailView: View {
|
||||
SodaDialog(
|
||||
title: viewModel.dialogTitle,
|
||||
desc: viewModel.dialogDesc,
|
||||
confirmButtonTitle: "확인"
|
||||
confirmButtonTitle: I18n.Common.confirm
|
||||
) {
|
||||
viewModel.isShowVoteCompleteView = false
|
||||
viewModel.isShowNotifyVote = false
|
||||
@@ -300,17 +300,17 @@ struct AuditionRoleDetailView: View {
|
||||
viewModel.fileName = fileUrl.lastPathComponent
|
||||
isShowApplyView = true
|
||||
} else {
|
||||
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
|
||||
viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
} else {
|
||||
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
|
||||
viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
DEBUG_LOG("error: \(error.localizedDescription)")
|
||||
viewModel.errorMessage = "콘텐츠 파일을 불러오지 못했습니다.\n다시 선택해 주세요"
|
||||
viewModel.errorMessage = I18n.Audition.Apply.fileLoadFailed
|
||||
viewModel.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
@Published var totalCount = 0
|
||||
@Published var applicantList = [GetAuditionRoleApplicantItem]()
|
||||
|
||||
@Published var name = "보이스온"
|
||||
@Published var name = I18n.Audition.defaultTitle
|
||||
@Published var auditionRoleDetail: GetAuditionRoleDetailResponse? = nil
|
||||
|
||||
@Published private(set) var sortType = AuditionApplicantSortType.NEWEST {
|
||||
@@ -93,13 +93,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = roleDetailDecoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = applicantListDecoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -127,7 +127,7 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
||||
self.onFailure()
|
||||
@@ -172,13 +172,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -188,13 +188,13 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
|
||||
func applyAudition(onSuccess: @escaping () -> Void) {
|
||||
if phoneNumber.count != 11 {
|
||||
errorMessage = "잘못된 연락처 입니다.\n다시 입력해 주세요."
|
||||
errorMessage = I18n.Audition.Apply.invalidContact
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
guard let soundData = soundData else {
|
||||
errorMessage = "잘못된 녹음 파일 입니다.\n다시 선택해 주세요."
|
||||
errorMessage = I18n.Audition.Apply.invalidRecordingFile
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -248,19 +248,19 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Audition.Apply.applyFailed
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Audition.Apply.applyFailed
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
self.errorMessage = "오디션 지원을 완료하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Audition.Apply.applyFailed
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -287,8 +287,8 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
|
||||
if decoded.success {
|
||||
if self.isShowNotifyVote {
|
||||
self.dialogTitle = "[오디션 응원]"
|
||||
self.dialogDesc = "오디션을 응원하셨습니다\n(무료응원 : 1계정당 1일 1회)\n1캔으로 추가 응원을 해보세요."
|
||||
self.dialogTitle = I18n.Audition.Vote.cheerTitle
|
||||
self.dialogDesc = I18n.Audition.Vote.cheerDescription
|
||||
self.isShowVoteCompleteView = true
|
||||
}
|
||||
|
||||
@@ -302,20 +302,20 @@ final class AuditionRoleDetailViewModel: ObservableObject {
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
if message.contains("오늘 응원은 여기까지") {
|
||||
self.dialogTitle = "[오늘 응원 제한]"
|
||||
self.dialogDesc = "오늘 응원은 여기까지!\n하루 최대 10회까지 이용이 가능합니다.\n내일 다시 이용해주세요."
|
||||
self.dialogTitle = I18n.Audition.Vote.limitTitle
|
||||
self.dialogDesc = I18n.Audition.Vote.limitDescription
|
||||
self.isShowVoteCompleteView = true
|
||||
} else {
|
||||
self.errorMessage = message
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} else {
|
||||
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Vote.unknownError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Audition.Vote.unknownError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ struct CharacterItemView: View {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Text("N")
|
||||
Text(I18n.Chat.Character.newBadge)
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 30, height: 30)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CharacterSectionView: View {
|
||||
let title: LocalizedStringResource
|
||||
let title: String
|
||||
let items: [Character]
|
||||
let isShowRank: Bool
|
||||
var trailingTitle: String? = nil
|
||||
@@ -52,7 +52,7 @@ struct CharacterSectionView: View {
|
||||
|
||||
#Preview {
|
||||
CharacterSectionView(
|
||||
title: "신규 캐릭터",
|
||||
title: I18n.Chat.Character.newSectionTitle,
|
||||
items: [
|
||||
Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300", isNew: true),
|
||||
Character(characterId: 2, name: "데이지", description: "", imageUrl: "https://picsum.photos/300", isNew: false)
|
||||
|
||||
@@ -36,10 +36,16 @@ struct CharacterView: View {
|
||||
}
|
||||
}
|
||||
|
||||
YandexInlineBannerView(
|
||||
placement: .chatCharacterList,
|
||||
horizontalPadding: 24
|
||||
)
|
||||
.padding(.vertical, -24)
|
||||
|
||||
// 인기 캐릭터 섹션
|
||||
if !viewModel.popularCharacters.isEmpty {
|
||||
CharacterSectionView(
|
||||
title: "인기 캐릭터",
|
||||
title: I18n.Chat.Character.popularSectionTitle,
|
||||
items: viewModel.popularCharacters,
|
||||
isShowRank: true,
|
||||
onTap: { ch in
|
||||
@@ -51,7 +57,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 +73,7 @@ struct CharacterView: View {
|
||||
if !viewModel.recommendCharacters.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack {
|
||||
Text("추천 캐릭터")
|
||||
Text(I18n.Chat.Character.recommendSectionTitle)
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -62,7 +62,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
self.isLoading = false
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -101,11 +101,10 @@ final class CharacterViewModel: ObservableObject {
|
||||
self.isLoading = false
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ struct CharacterDetailView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "캐릭터 정보")) {
|
||||
DetailNavigationBar(title: I18n.Chat.Character.detailTitle) {
|
||||
if presentationMode.wrappedValue.isPresented {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} else {
|
||||
@@ -77,7 +77,7 @@ struct CharacterDetailView: View {
|
||||
if let others = viewModel.characterDetail?.others, !others.isEmpty {
|
||||
VStack(spacing: 16) {
|
||||
HStack {
|
||||
Text("장르의 다른 캐릭터")
|
||||
Text(I18n.Chat.Character.detailOtherCharactersTitle)
|
||||
.appFont(size: 26, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -178,6 +178,13 @@ extension CharacterDetailView {
|
||||
|
||||
// MARK: - Profile Section
|
||||
extension CharacterDetailView {
|
||||
private func isMaleGender(_ gender: String) -> Bool {
|
||||
let normalizedGender = gender
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.lowercased()
|
||||
return normalizedGender == "남성" || normalizedGender == "male"
|
||||
}
|
||||
|
||||
private var profileSection: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if viewModel.characterDetail?.mbti != nil ||
|
||||
@@ -189,7 +196,7 @@ extension CharacterDetailView {
|
||||
Text(viewModel.characterDetail?.translated?.gender ?? gender)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(
|
||||
gender == "남성" ?
|
||||
isMaleGender(gender) ?
|
||||
Color.button :
|
||||
Color.mainRed
|
||||
)
|
||||
@@ -201,7 +208,7 @@ extension CharacterDetailView {
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.stroke(lineWidth: 1)
|
||||
.foregroundColor(
|
||||
gender == "남성" ?
|
||||
isMaleGender(gender) ?
|
||||
Color.button :
|
||||
Color.mainRed
|
||||
)
|
||||
@@ -209,7 +216,7 @@ extension CharacterDetailView {
|
||||
}
|
||||
|
||||
if let age = viewModel.characterDetail?.age {
|
||||
Text("\(age)세")
|
||||
Text(I18n.Chat.Character.age(age))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 7)
|
||||
@@ -252,7 +259,7 @@ extension CharacterDetailView {
|
||||
|
||||
if let characterType = viewModel.characterDetail?.characterType {
|
||||
HStack(spacing: 8) {
|
||||
Text(characterType.rawValue)
|
||||
Text(characterType == .Clone ? I18n.Chat.Character.typeClone : I18n.Chat.Character.typeCharacter)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 5)
|
||||
@@ -282,7 +289,7 @@ extension CharacterDetailView {
|
||||
private func worldViewSection(backgrounds: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("[세계관 및 작품 소개]")
|
||||
Text(I18n.Chat.Character.detailWorldViewTitle)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -300,7 +307,7 @@ extension CharacterDetailView {
|
||||
private func originalWorkSection(title: String, link: String) -> some View {
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Text("원작")
|
||||
Text(I18n.Chat.Character.detailOriginalTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.white)
|
||||
@@ -321,7 +328,7 @@ extension CharacterDetailView {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
Text("원작 보러가기")
|
||||
Text(I18n.Chat.Character.detailOriginalLinkButton)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(hex: "3BB9F1"))
|
||||
@@ -342,7 +349,7 @@ extension CharacterDetailView {
|
||||
private func personalitySection(personalities: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("[성격 및 특징]")
|
||||
Text(I18n.Chat.Character.detailPersonalityTitle)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -354,24 +361,19 @@ extension CharacterDetailView {
|
||||
// 캐릭터톡 대화 가이드
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack {
|
||||
Text("⚠️ 캐릭터톡 대화 가이드")
|
||||
Text(I18n.Chat.Character.detailConversationGuideTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
Text("""
|
||||
보이스온의 오픈월드 캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다. 세계관 속 연관 캐릭터가 되어 대화를 하거나 완전히 새로운 인물이 되어 캐릭터와 당신만의 스토리를 만들어 갈 수 있습니다.
|
||||
""")
|
||||
Text(I18n.Chat.Character.detailConversationGuideDescription1)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "AEAEB2"))
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
Text("""
|
||||
오픈월드 캐릭터톡은 캐릭터를 정교하게 설계하였지만, 대화가 어색하거나 불완전할 수도 있습니다.
|
||||
대화 도중 캐릭터의 대화가 이상하거나 새로운 캐릭터로 대화를 나누고 싶다면 대화를 초기화 하고 새롭게 캐릭터와 대화를 나눠보세요.
|
||||
""")
|
||||
Text(I18n.Chat.Character.detailConversationGuideDescription2)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "AEAEB2"))
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -393,7 +395,7 @@ extension CharacterDetailView {
|
||||
// MARK: - Chat Button
|
||||
extension CharacterDetailView {
|
||||
private var chatButton: some View {
|
||||
Text("대화하기")
|
||||
Text(I18n.Chat.Character.detailChatButton)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -450,7 +452,7 @@ struct CharacterExpandableTextView: View {
|
||||
.foregroundColor(Color(hex: "607D8B"))
|
||||
.rotationEffect(.degrees(isExpanded ? 180 : 0))
|
||||
|
||||
Text(isExpanded ? "간략히" : "더보기")
|
||||
Text(isExpanded ? I18n.Chat.Character.detailCollapse : I18n.Chat.Character.detailExpand)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "607D8B"))
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ struct CharacterDetailGalleryView: View {
|
||||
VStack(spacing: 8) {
|
||||
// 상단 정보 (계산된 % 보유중, 정보 아이콘, 개수)
|
||||
HStack {
|
||||
Text("\(viewModel.ownershipPercentage)% 보유중")
|
||||
Text(I18n.Chat.Character.DetailGallery.ownership(viewModel.ownershipPercentage))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -91,7 +91,7 @@ struct CharacterDetailGalleryView: View {
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("\(viewModel.totalCount)개")
|
||||
Text(I18n.Chat.Character.DetailGallery.totalCount(viewModel.totalCount))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ final class NewCharacterListViewModel: ObservableObject {
|
||||
} else {
|
||||
self?.isLoading = false
|
||||
}
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
@@ -93,7 +93,7 @@ final class NewCharacterListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
self.isShowPopup = true
|
||||
if isLoadMore {
|
||||
@@ -108,7 +108,7 @@ final class NewCharacterListViewModel: ObservableObject {
|
||||
} else {
|
||||
self.isLoading = false
|
||||
}
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,18 @@ struct NewCharacterListView: View {
|
||||
Group { BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 8) {
|
||||
// Toolbar
|
||||
DetailNavigationBar(title: String(localized: "신규 캐릭터 전체보기"))
|
||||
DetailNavigationBar(title: I18n.Chat.Character.NewList.title)
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// 전체 n개
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Chat.Character.NewList.totalPrefix)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Text(" \(viewModel.totalCount)")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
Text("개")
|
||||
Text(I18n.Chat.Character.NewList.countUnit)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Spacer()
|
||||
|
||||
@@ -14,8 +14,8 @@ struct RecentCharacterSectionView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(spacing: 0) {
|
||||
Text("최근 대화한 캐릭터 ")
|
||||
HStack(spacing: 4) {
|
||||
Text(I18n.Chat.Character.recentSectionTitle)
|
||||
.appFont(size: 20, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -168,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
|
||||
}
|
||||
@@ -190,15 +190,14 @@ struct ChatTabView: View {
|
||||
|
||||
if isShowAuthConfirmView {
|
||||
SodaDialog(
|
||||
title: "본인인증",
|
||||
desc: "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" +
|
||||
"캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.",
|
||||
confirmButtonTitle: "본인인증 하러가기",
|
||||
title: I18n.Chat.Auth.dialogTitle,
|
||||
desc: I18n.Chat.Auth.dialogDescription,
|
||||
confirmButtonTitle: I18n.Chat.Auth.goToVerification,
|
||||
confirmButtonAction: {
|
||||
isShowAuthConfirmView = false
|
||||
isShowAuthView = true
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonTitle: I18n.Common.cancel,
|
||||
cancelButtonAction: {
|
||||
isShowAuthConfirmView = false
|
||||
pendingAction = nil
|
||||
|
||||
@@ -58,7 +58,7 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
}
|
||||
|
||||
if item.isAdult {
|
||||
Text("19+")
|
||||
Text(I18n.Chat.Original.adultBadge)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.horizontal, 7)
|
||||
|
||||
@@ -151,7 +151,7 @@ struct OriginalWorkInfoView: View {
|
||||
ZStack {
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("작품 소개")
|
||||
Text(I18n.Chat.Original.workIntroductionTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -170,7 +170,7 @@ struct OriginalWorkInfoView: View {
|
||||
.cornerRadius(16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("원작 보러 가기")
|
||||
Text(I18n.Chat.Original.viewOriginalLinksTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
@@ -197,26 +197,26 @@ struct OriginalWorkInfoView: View {
|
||||
.cornerRadius(16)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("상세 정보")
|
||||
Text(I18n.Chat.Original.detailInfoTitle)
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let _ = response.writer {
|
||||
Text("작가")
|
||||
Text(I18n.Chat.Original.writerLabel)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.studio {
|
||||
Text("제작사")
|
||||
Text(I18n.Chat.Original.studioLabel)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.originalWork {
|
||||
Text("원작")
|
||||
Text(I18n.Chat.Original.originalLabel)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ final class OriginalWorkDetailViewModel: ObservableObject {
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
@@ -61,14 +61,14 @@ final class OriginalWorkDetailViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ struct OriginalTabView: View {
|
||||
let width = (geo.size.width - (horizontalPadding * 2) - totalSpacing) / 3
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 12) {
|
||||
YandexInlineBannerView(
|
||||
placement: .chatOriginalTabTop,
|
||||
horizontalPadding: horizontalPadding
|
||||
)
|
||||
|
||||
LazyVGrid(
|
||||
columns: Array(
|
||||
repeating: GridItem(
|
||||
@@ -60,6 +66,7 @@ struct OriginalTabView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 0, maxHeight: .infinity)
|
||||
.padding(.vertical, 12)
|
||||
.onAppear {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,17 @@ class ChatRoomRepository {
|
||||
return talkApi.requestPublisher(.getChatQuotaStatus(roomId: roomId))
|
||||
}
|
||||
|
||||
func purchaseChatQuota(roomId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return talkApi.requestPublisher(.purchaseChatQuota(roomId: roomId, request: ChatQuotaPurchaseRequest()))
|
||||
func purchaseChatQuota(
|
||||
roomId: Int,
|
||||
chargeType: ChatRoomQuotaChargeType = .can,
|
||||
canOption: ChatRoomQuotaCanOption? = nil
|
||||
) -> AnyPublisher<Response, MoyaError> {
|
||||
return talkApi.requestPublisher(
|
||||
.purchaseChatQuota(
|
||||
roomId: roomId,
|
||||
request: ChatQuotaPurchaseRequest(chargeType: chargeType, canOption: canOption)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func resetChatRoom(roomId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
|
||||
@@ -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)
|
||||
@@ -144,9 +148,17 @@ struct ChatRoomView: View {
|
||||
}
|
||||
|
||||
if viewModel.showQuotaNoticeView {
|
||||
ChatQuotaNoticeItemView(remainingTime: viewModel.countdownText) {
|
||||
viewModel.purchaseChatQuota()
|
||||
ChatQuotaNoticeItemView(
|
||||
onSelectAd: {
|
||||
viewModel.showRewardedAdForChatQuota()
|
||||
},
|
||||
onSelectCan10: {
|
||||
viewModel.purchaseChatQuota(canOption: .can10)
|
||||
},
|
||||
onSelectCan20: {
|
||||
viewModel.purchaseChatQuota(canOption: .can20)
|
||||
}
|
||||
)
|
||||
.id("quota_\(viewModel.messages.count)")
|
||||
.padding(.bottom, 12)
|
||||
.onAppear {
|
||||
@@ -186,7 +198,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 +301,7 @@ struct ChatRoomView: View {
|
||||
ActivityIndicatorView()
|
||||
.frame(width: 100, height: 100)
|
||||
|
||||
Text("대화 초기화 중...")
|
||||
Text(I18n.Chat.Room.resettingMessage)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
@@ -305,9 +317,6 @@ struct ChatRoomView: View {
|
||||
viewModel.getMemberInfo()
|
||||
viewModel.enterRoom(roomId: roomId)
|
||||
}
|
||||
.onDisappear {
|
||||
viewModel.stopTimer()
|
||||
}
|
||||
.sodaToast(isPresented: $viewModel.isShowPopup, message: viewModel.errorMessage, autohideIn: 2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -28,7 +28,7 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published private(set) var countdownText: String = "00:00:00"
|
||||
@Published private(set) var totalRemaining: Int = 0
|
||||
@Published private(set) var showQuotaNoticeView: Bool = false
|
||||
|
||||
@Published private(set) var showSendingMessage: Bool = false
|
||||
@@ -72,8 +72,6 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
private var hasMoreMessages: Bool = true
|
||||
private var nextCursor: Int64? = nil
|
||||
|
||||
private var timer: Timer?
|
||||
|
||||
// MARK: - Actions
|
||||
func sendMessage() {
|
||||
guard !messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
@@ -113,7 +111,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)
|
||||
}
|
||||
@@ -125,16 +123,15 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<SendChatMessageResponse>.self, from: responseData)
|
||||
if let data = decoded.data, decoded.success {
|
||||
self.messages.append(contentsOf: data.messages)
|
||||
self.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self.updateQuota(totalRemaining: data.totalRemaining)
|
||||
} 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
|
||||
}
|
||||
}
|
||||
@@ -178,12 +175,12 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.hasMoreMessages = data.hasMoreMessages
|
||||
self?.nextCursor = data.messages.last?.messageId
|
||||
|
||||
self?.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self?.updateQuota(totalRemaining: data.totalRemaining)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -192,7 +189,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 +257,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,17 +266,32 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.isLoading = false
|
||||
} catch {
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func purchaseChatQuota() {
|
||||
func purchaseChatQuota(canOption: ChatRoomQuotaCanOption) {
|
||||
purchaseChatQuota(chargeType: .can, canOption: canOption)
|
||||
}
|
||||
|
||||
func showRewardedAdForChatQuota() {
|
||||
_Concurrency.Task {
|
||||
await YandexRewardedAdManager.shared.showAdIfAvailable(for: .chatRoomQuota) { [weak self] in
|
||||
self?.purchaseChatQuota(chargeType: .ad, canOption: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func purchaseChatQuota(
|
||||
chargeType: ChatRoomQuotaChargeType,
|
||||
canOption: ChatRoomQuotaCanOption?
|
||||
) {
|
||||
isLoading = true
|
||||
|
||||
repository.purchaseChatQuota(roomId: roomId)
|
||||
repository.purchaseChatQuota(roomId: roomId, chargeType: chargeType, canOption: canOption)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { result in
|
||||
switch result {
|
||||
@@ -296,15 +308,17 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<ChatQuotaStatusResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self?.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self?.updateQuota(totalRemaining: data.totalRemaining)
|
||||
|
||||
if let canOption {
|
||||
let can = UserDefaults.int(forKey: .can)
|
||||
UserDefaults.set(can - 30, forKey: .can)
|
||||
UserDefaults.set(can - canOption.needCan, forKey: .can)
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -313,7 +327,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 +362,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,12 +395,12 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
|
||||
private func resetData() {
|
||||
characterProfileUrl = ""
|
||||
characterName = "Character Name"
|
||||
characterName = I18n.Chat.Room.defaultCharacterName
|
||||
characterType = .Character
|
||||
chatRoomBgImageUrl = nil
|
||||
roomId = 0
|
||||
|
||||
countdownText = "00:00:00"
|
||||
totalRemaining = 0
|
||||
showQuotaNoticeView = false
|
||||
|
||||
showSendingMessage = false
|
||||
@@ -422,12 +436,12 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<ChatQuotaStatusResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self?.updateQuota(nextRechargeAtEpoch: data.nextRechargeAtEpoch)
|
||||
self?.updateQuota(totalRemaining: data.totalRemaining)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
@@ -436,85 +450,25 @@ final class ChatRoomViewModel: ObservableObject {
|
||||
self?.isLoading = false
|
||||
} catch {
|
||||
self?.isLoading = false
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func updateQuota(nextRechargeAtEpoch: Int64?) {
|
||||
isLoading = true
|
||||
stopTimer()
|
||||
|
||||
// epoch 없음 → 카운트다운 비표시
|
||||
guard let nextRechargeAtEpoch else {
|
||||
countdownText = "00:00:00"
|
||||
showQuotaNoticeView = false
|
||||
isLoading = false
|
||||
return
|
||||
private func updateQuota(totalRemaining: Int) {
|
||||
self.totalRemaining = totalRemaining
|
||||
showQuotaNoticeView = totalRemaining <= 0
|
||||
prepareRewardedAdIfNeeded(totalRemaining: totalRemaining)
|
||||
}
|
||||
|
||||
// 즉시 1회 갱신
|
||||
let remainMs = remainingMs(to: nextRechargeAtEpoch)
|
||||
updateCountdownText(remainMs)
|
||||
private func prepareRewardedAdIfNeeded(totalRemaining: Int) {
|
||||
guard totalRemaining <= 1 else { return }
|
||||
|
||||
// 이미 0이면 종료 처리
|
||||
guard remainMs > 0 else {
|
||||
checkQuotaStatus()
|
||||
return
|
||||
_Concurrency.Task {
|
||||
await YandexRewardedAdManager.shared.preloadAd(for: .chatRoomQuota)
|
||||
}
|
||||
|
||||
isLoading = false
|
||||
showQuotaNoticeView = true
|
||||
|
||||
// 타이머 시작 (1초마다 갱신)
|
||||
startTimer(targetEpoch: nextRechargeAtEpoch)
|
||||
}
|
||||
|
||||
private func updateCountdownText(_ remainMs: Int64) {
|
||||
countdownText = remainMs > 0 ? formatMillisToHms(remainMs) : "00:00:00"
|
||||
}
|
||||
|
||||
private func startTimer(targetEpoch: Int64) {
|
||||
stopTimer()
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||
guard let self else { return }
|
||||
let remain = self.remainingMs(to: targetEpoch)
|
||||
self.updateCountdownText(remain)
|
||||
if remain == 0 {
|
||||
self.stopTimer()
|
||||
self.checkQuotaStatus()
|
||||
}
|
||||
}
|
||||
if let t = timer { RunLoop.main.add(t, forMode: .common) }
|
||||
}
|
||||
|
||||
func stopTimer() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private func remainingMs(to epoch: Int64) -> Int64 {
|
||||
let ms = normalizeToMs(epoch)
|
||||
let nowMs = Int64(Date().timeIntervalSince1970 * 1000)
|
||||
let fudgeMs: Int64 = 5000
|
||||
|
||||
// Kotlin 로직과 동일하게 표시 보정 적용
|
||||
return max(ms - nowMs + fudgeMs, 0)
|
||||
}
|
||||
|
||||
/// 초 단위/밀리초 단위 혼용 대비
|
||||
private func normalizeToMs(_ epoch: Int64) -> Int64 {
|
||||
epoch < 1_000_000_000_000 ? epoch * 1000 : epoch
|
||||
}
|
||||
|
||||
private func formatMillisToHms(_ ms: Int64) -> String {
|
||||
let total = ms / 1000
|
||||
let h = total / 3600
|
||||
let m = (total % 3600) / 60
|
||||
let s = total % 60
|
||||
return String(format: "%02d:%02d:%02d", h, m, s)
|
||||
}
|
||||
|
||||
private func getSavedBackgroundImageId() -> Int? {
|
||||
|
||||
@@ -130,7 +130,7 @@ struct AiMessageItemView: View {
|
||||
.foregroundColor(.button)
|
||||
}
|
||||
|
||||
Text("눌러서 잠금해제")
|
||||
Text(I18n.Chat.Room.unlockImagePrompt)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ struct TypingIndicatorItemView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.accessibilityLabel(Text("입력 중"))
|
||||
.accessibilityLabel(Text(I18n.Chat.Room.typingAccessibilityLabel))
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 8)
|
||||
|
||||
@@ -9,40 +9,62 @@ import SwiftUI
|
||||
|
||||
struct ChatQuotaNoticeItemView: View {
|
||||
|
||||
let remainingTime: String
|
||||
let purchase: () -> Void
|
||||
let onSelectAd: () -> Void
|
||||
let onSelectCan10: () -> Void
|
||||
let onSelectCan20: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 10) {
|
||||
VStack(spacing: 8) {
|
||||
Image("ic_time")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
|
||||
Text(remainingTime)
|
||||
Button {
|
||||
onSelectAd()
|
||||
} label: {
|
||||
Text(I18n.Chat.Room.quotaAdAction(chatCount: 5))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("기다리면 무료 이용이 가능합니다.")
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 14)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 15)
|
||||
.background(Color(hex: "EC8280"))
|
||||
.cornerRadius(10)
|
||||
.background(Color(hex: "FEF8E3"))
|
||||
.cornerRadius(30)
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 30)
|
||||
.stroke(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "F7CB50"))
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Button {
|
||||
onSelectCan10()
|
||||
} label: {
|
||||
canButtonLabel(can: 10, chatCount: 15)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Button {
|
||||
onSelectCan20()
|
||||
} label: {
|
||||
canButtonLabel(can: 20, chatCount: 40)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func canButtonLabel(can: Int, chatCount: Int) -> some View {
|
||||
HStack(spacing: 4) {
|
||||
Image("ic_can")
|
||||
|
||||
Text("10")
|
||||
.appFont(size: 24, weight: .bold)
|
||||
HStack(spacing: 0) {
|
||||
Text("\(can)")
|
||||
.appFont(size: 20, weight: .bold)
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
|
||||
Text("(채팅 12개) 바로 대화 시작")
|
||||
.appFont(size: 24, weight: .bold)
|
||||
Text(" / \(I18n.Chat.Room.quotaChatCount(chatCount))")
|
||||
.appFont(size: 20, weight: .medium)
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 12)
|
||||
@@ -53,13 +75,13 @@ struct ChatQuotaNoticeItemView: View {
|
||||
.stroke(lineWidth: 1)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
.onTapGesture {
|
||||
purchase()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ChatQuotaNoticeItemView(remainingTime: "05:59:55") {}
|
||||
ChatQuotaNoticeItemView(
|
||||
onSelectAd: {},
|
||||
onSelectCan10: {},
|
||||
onSelectCan20: {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,4 +7,42 @@
|
||||
|
||||
struct ChatQuotaPurchaseRequest: Encodable {
|
||||
let container: String = "ios"
|
||||
let chargeType: ChatRoomQuotaChargeType
|
||||
let canOption: ChatRoomQuotaCanOption?
|
||||
|
||||
init(
|
||||
chargeType: ChatRoomQuotaChargeType = .can,
|
||||
canOption: ChatRoomQuotaCanOption? = nil
|
||||
) {
|
||||
self.chargeType = chargeType
|
||||
self.canOption = canOption
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatRoomQuotaChargeType: String, Encodable {
|
||||
case can = "CAN"
|
||||
case ad = "AD"
|
||||
}
|
||||
|
||||
enum ChatRoomQuotaCanOption: String, Encodable {
|
||||
case can10 = "CAN_10"
|
||||
case can20 = "CAN_20"
|
||||
|
||||
var needCan: Int {
|
||||
switch self {
|
||||
case .can10:
|
||||
return 10
|
||||
case .can20:
|
||||
return 20
|
||||
}
|
||||
}
|
||||
|
||||
var quota: Int {
|
||||
switch self {
|
||||
case .can10:
|
||||
return 15
|
||||
case .can20:
|
||||
return 40
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ struct ChatBgSelectionView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "배경 이미지 선택")) {
|
||||
DetailNavigationBar(title: I18n.Chat.Room.backgroundSelectionTitle) {
|
||||
isShowing = false
|
||||
}
|
||||
// 갤러리 그리드
|
||||
@@ -79,7 +79,7 @@ struct ChatBgSelectionView: View {
|
||||
}
|
||||
|
||||
if selectedBgImageId == item.id {
|
||||
Text("현재 배경")
|
||||
Text(I18n.Chat.Room.currentBackground)
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 6)
|
||||
|
||||
@@ -75,14 +75,14 @@ final class ChatBgSelectionViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self?.errorMessage = message
|
||||
} else {
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
ERROR_LOG(String(describing: error))
|
||||
self?.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self?.errorMessage = I18n.Common.commonError
|
||||
self?.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ struct ChatSettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: String(localized: "대화 설정")) {
|
||||
DetailNavigationBar(title: I18n.Chat.Room.settingsTitle) {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ChatSettingsView: View {
|
||||
VStack(spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
Toggle(isOn: $isHideBg) {
|
||||
Text("배경 이미지 끄기")
|
||||
Text(I18n.Chat.Room.hideBackgroundImage)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
@@ -42,7 +42,7 @@ struct ChatSettingsView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
Text("배경 이미지 변경")
|
||||
Text(I18n.Chat.Room.changeBackgroundImage)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 24)
|
||||
@@ -61,16 +61,16 @@ struct ChatSettingsView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("대화 초기화")
|
||||
Text(I18n.Chat.Room.resetConversationTitle)
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("⚠️ ")
|
||||
Text(I18n.Chat.Room.resetWarningPrefix)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
|
||||
Text("지금까지의 대화가 모두 초기화 되고, 이용자가 새로운 캐릭터가 되어 새롭게 대화를 시작합니다.")
|
||||
Text(I18n.Chat.Room.resetWarningDescription)
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
@@ -13,13 +13,18 @@ struct TalkView: View {
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVStack(spacing: 24) {
|
||||
YandexInlineBannerView(
|
||||
placement: .chatTalkTabTop,
|
||||
horizontalPadding: 24
|
||||
)
|
||||
|
||||
if viewModel.talkRooms.isEmpty {
|
||||
Text("대화 중인 톡이 없습니다")
|
||||
Text(I18n.Chat.Talk.emptyMessage)
|
||||
.appFont(size: 20, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVStack(spacing: 24) {
|
||||
ForEach(0..<viewModel.talkRooms.count, id: \.self) { index in
|
||||
let item = viewModel.talkRooms[index]
|
||||
TalkItemView(item: item)
|
||||
@@ -38,14 +43,15 @@ struct TalkView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isLoadingMore {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
441
SodaLive/Sources/Common/YandexAdSupport.swift
Normal file
@@ -0,0 +1,441 @@
|
||||
//
|
||||
// YandexAdSupport.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by OpenCode on 2026/04/28.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import YandexMobileAds
|
||||
|
||||
enum YandexBannerPlacement {
|
||||
case liveTab
|
||||
case liveDetail
|
||||
case contentDetail
|
||||
case creatorCommunityAll
|
||||
case seriesMainHome
|
||||
case seriesMainDayOfWeek
|
||||
case seriesMainByGenre
|
||||
case pushNotificationList
|
||||
case notificationReceiveSettings
|
||||
case chatCharacterList
|
||||
case chatOriginalTabTop
|
||||
case chatTalkTabTop
|
||||
}
|
||||
|
||||
enum YandexInterstitialPlacement {
|
||||
case contentDetail
|
||||
}
|
||||
|
||||
enum YandexRewardedPlacement {
|
||||
case chatRoomQuota
|
||||
}
|
||||
|
||||
enum YandexAdUnitIdProvider {
|
||||
|
||||
static func banner(for placement: YandexBannerPlacement) -> String {
|
||||
switch placement {
|
||||
case .liveTab:
|
||||
YANDEX_LIVE_TAB_BANNER_AD_UNIT_ID
|
||||
case .liveDetail:
|
||||
YANDEX_LIVE_DETAIL_BANNER_AD_UNIT_ID
|
||||
case .contentDetail:
|
||||
YANDEX_CONTENT_DETAIL_BANNER_AD_UNIT_ID
|
||||
case .creatorCommunityAll:
|
||||
YANDEX_CREATOR_COMMUNITY_ALL_BANNER_AD_UNIT_ID
|
||||
case .seriesMainHome:
|
||||
YANDEX_SERIES_MAIN_HOME_BANNER_AD_UNIT_ID
|
||||
case .seriesMainDayOfWeek:
|
||||
YANDEX_SERIES_MAIN_DAY_OF_WEEK_BANNER_AD_UNIT_ID
|
||||
case .seriesMainByGenre:
|
||||
YANDEX_SERIES_MAIN_BY_GENRE_BANNER_AD_UNIT_ID
|
||||
case .pushNotificationList:
|
||||
YANDEX_PUSH_NOTIFICATION_LIST_BANNER_AD_UNIT_ID
|
||||
case .notificationReceiveSettings:
|
||||
YANDEX_NOTIFICATION_RECEIVE_SETTINGS_BANNER_AD_UNIT_ID
|
||||
case .chatCharacterList:
|
||||
YANDEX_CHAT_CHARACTER_LIST_BANNER_AD_UNIT_ID
|
||||
case .chatOriginalTabTop:
|
||||
YANDEX_CHAT_ORIGINAL_TAB_TOP_BANNER_AD_UNIT_ID
|
||||
case .chatTalkTabTop:
|
||||
YANDEX_CHAT_TALK_TAB_TOP_BANNER_AD_UNIT_ID
|
||||
}
|
||||
}
|
||||
|
||||
static func interstitial(for placement: YandexInterstitialPlacement) -> String {
|
||||
switch placement {
|
||||
case .contentDetail:
|
||||
YANDEX_CONTENT_DETAIL_INTERSTITIAL_AD_UNIT_ID
|
||||
}
|
||||
}
|
||||
|
||||
static func rewarded(for placement: YandexRewardedPlacement) -> String {
|
||||
switch placement {
|
||||
case .chatRoomQuota:
|
||||
YANDEX_CHAT_ROOM_QUOTA_REWARDED_AD_UNIT_ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct YandexInlineBannerView: View {
|
||||
|
||||
let placement: YandexBannerPlacement
|
||||
|
||||
var maxHeight: CGFloat = 90
|
||||
var horizontalPadding: CGFloat = 13.3
|
||||
|
||||
@State private var bannerHeight: CGFloat = 0
|
||||
@State private var isLoadFailed = true
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
let width = max(proxy.size.width - (horizontalPadding * 2), 1)
|
||||
let resolvedHeight = isLoadFailed ? 0 : (bannerHeight > 0 ? bannerHeight : maxHeight)
|
||||
|
||||
YandexInlineBannerContainer(
|
||||
placement: placement,
|
||||
width: width,
|
||||
maxHeight: maxHeight,
|
||||
bannerHeight: $bannerHeight,
|
||||
isLoadFailed: $isLoadFailed
|
||||
)
|
||||
.frame(width: width, height: resolvedHeight)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
}
|
||||
.frame(height: isLoadFailed ? 0 : (bannerHeight > 0 ? bannerHeight : maxHeight))
|
||||
}
|
||||
}
|
||||
|
||||
private struct YandexInlineBannerContainer: UIViewRepresentable {
|
||||
|
||||
let placement: YandexBannerPlacement
|
||||
|
||||
let width: CGFloat
|
||||
let maxHeight: CGFloat
|
||||
|
||||
@Binding var bannerHeight: CGFloat
|
||||
@Binding var isLoadFailed: Bool
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
Coordinator(parent: self)
|
||||
}
|
||||
|
||||
func makeUIView(context: Context) -> UIView {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .clear
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIView, context: Context) {
|
||||
context.coordinator.parent = self
|
||||
context.coordinator.configureIfNeeded(containerView: uiView)
|
||||
}
|
||||
|
||||
final class Coordinator: NSObject, BannerAdViewDelegate {
|
||||
|
||||
var parent: YandexInlineBannerContainer
|
||||
private weak var containerView: UIView?
|
||||
private var bannerAdView: BannerAdView?
|
||||
private var currentWidth: CGFloat = 0
|
||||
|
||||
init(parent: YandexInlineBannerContainer) {
|
||||
self.parent = parent
|
||||
}
|
||||
|
||||
func configureIfNeeded(containerView: UIView) {
|
||||
self.containerView = containerView
|
||||
|
||||
let roundedWidth = parent.width.rounded(.down)
|
||||
|
||||
guard bannerAdView == nil || abs(currentWidth - roundedWidth) > 0.5 else {
|
||||
return
|
||||
}
|
||||
|
||||
currentWidth = roundedWidth
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.parent.bannerHeight = 0
|
||||
self.parent.isLoadFailed = false
|
||||
}
|
||||
|
||||
bannerAdView?.removeFromSuperview()
|
||||
|
||||
let adSize = BannerAdSize.inline(width: roundedWidth, maxHeight: parent.maxHeight)
|
||||
let bannerAdView = BannerAdView(adSize: adSize)
|
||||
bannerAdView.translatesAutoresizingMaskIntoConstraints = false
|
||||
bannerAdView.delegate = self
|
||||
|
||||
containerView.addSubview(bannerAdView)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
bannerAdView.topAnchor.constraint(equalTo: containerView.topAnchor),
|
||||
bannerAdView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
|
||||
bannerAdView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||
bannerAdView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
|
||||
])
|
||||
|
||||
self.bannerAdView = bannerAdView
|
||||
bannerAdView.loadAd(with: AdRequest(adUnitID: YandexAdUnitIdProvider.banner(for: parent.placement)))
|
||||
}
|
||||
|
||||
func bannerAdViewDidLoad(_ adView: BannerAdView) {
|
||||
adView.layoutIfNeeded()
|
||||
|
||||
let measuredHeight = adView.bounds.height > 0 ? adView.bounds.height : parent.maxHeight
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.parent.bannerHeight = measuredHeight
|
||||
self.parent.isLoadFailed = false
|
||||
}
|
||||
}
|
||||
|
||||
func bannerAdViewDidFailLoading(_ adView: BannerAdView, error: Error) {
|
||||
DispatchQueue.main.async {
|
||||
self.parent.bannerHeight = 0
|
||||
self.parent.isLoadFailed = true
|
||||
}
|
||||
}
|
||||
|
||||
func bannerAdViewDidClick(_ adView: BannerAdView) {
|
||||
}
|
||||
|
||||
func bannerAdView(_ adView: BannerAdView, didTrackImpression impressionData: ImpressionData?) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class YandexInterstitialAdManager: NSObject {
|
||||
|
||||
static let shared = YandexInterstitialAdManager()
|
||||
|
||||
private var interstitialAd: InterstitialAd?
|
||||
private var interstitialAdLoader: InterstitialAdLoader?
|
||||
private var currentPlacement: YandexInterstitialPlacement?
|
||||
private var pendingAction: (@MainActor () -> Void)?
|
||||
private var isLoading = false
|
||||
|
||||
func preloadAd(for placement: YandexInterstitialPlacement) {
|
||||
guard !isLoading else {
|
||||
return
|
||||
}
|
||||
|
||||
if currentPlacement == placement, interstitialAd != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let loader = InterstitialAdLoader()
|
||||
interstitialAdLoader = loader
|
||||
interstitialAd = nil
|
||||
currentPlacement = placement
|
||||
isLoading = true
|
||||
|
||||
Task {
|
||||
do {
|
||||
let loadedAd = try await loader.loadAd(with: AdRequest(adUnitID: YandexAdUnitIdProvider.interstitial(for: placement)))
|
||||
guard currentPlacement == placement else {
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
loadedAd.delegate = self
|
||||
interstitialAd = loadedAd
|
||||
} catch {
|
||||
if currentPlacement == placement {
|
||||
interstitialAd = nil
|
||||
}
|
||||
}
|
||||
|
||||
if currentPlacement == placement {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showAdIfAvailable(for placement: YandexInterstitialPlacement, then action: @escaping @MainActor () -> Void) {
|
||||
guard let presenter = presentingViewController(), let interstitialAd, currentPlacement == placement else {
|
||||
action()
|
||||
preloadAd(for: placement)
|
||||
return
|
||||
}
|
||||
|
||||
pendingAction = action
|
||||
interstitialAd.show(from: presenter)
|
||||
}
|
||||
|
||||
private func completePendingAction() {
|
||||
let action = pendingAction
|
||||
pendingAction = nil
|
||||
interstitialAd = nil
|
||||
action?()
|
||||
|
||||
if let currentPlacement {
|
||||
preloadAd(for: currentPlacement)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentingViewController() -> UIViewController? {
|
||||
guard
|
||||
let rootViewController = UIApplication.shared.connectedScenes
|
||||
.compactMap({ $0 as? UIWindowScene })
|
||||
.flatMap({ $0.windows })
|
||||
.first(where: { $0.isKeyWindow })?
|
||||
.rootViewController
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var topViewController = rootViewController
|
||||
|
||||
while let presentedViewController = topViewController.presentedViewController {
|
||||
topViewController = presentedViewController
|
||||
}
|
||||
|
||||
return topViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension YandexInterstitialAdManager: InterstitialAdDelegate {
|
||||
|
||||
func interstitialAdDidShow(_ interstitialAd: InterstitialAd) {
|
||||
}
|
||||
|
||||
func interstitialAdDidDismiss(_ interstitialAd: InterstitialAd) {
|
||||
completePendingAction()
|
||||
}
|
||||
|
||||
func interstitialAdDidClick(_ interstitialAd: InterstitialAd) {
|
||||
}
|
||||
|
||||
func interstitialAd(_ interstitialAd: InterstitialAd, didTrackImpression impressionData: ImpressionData?) {
|
||||
}
|
||||
|
||||
func interstitialAd(_ interstitialAd: InterstitialAd, didFailToShow error: Error) {
|
||||
completePendingAction()
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class YandexRewardedAdManager: NSObject {
|
||||
|
||||
static let shared = YandexRewardedAdManager()
|
||||
|
||||
private var rewardedAd: RewardedAd?
|
||||
private var rewardedAdLoader: RewardedAdLoader?
|
||||
private var currentPlacement: YandexRewardedPlacement?
|
||||
private var pendingRewardAction: (@MainActor () -> Void)?
|
||||
private var rewardedPlacement: YandexRewardedPlacement?
|
||||
private var isLoading = false
|
||||
|
||||
func preloadAd(for placement: YandexRewardedPlacement) {
|
||||
guard !isLoading else {
|
||||
return
|
||||
}
|
||||
|
||||
if currentPlacement == placement, rewardedAd != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let loader = RewardedAdLoader()
|
||||
rewardedAdLoader = loader
|
||||
rewardedAd = nil
|
||||
currentPlacement = placement
|
||||
isLoading = true
|
||||
|
||||
Task {
|
||||
do {
|
||||
let loadedAd = try await loader.loadAd(with: AdRequest(adUnitID: YandexAdUnitIdProvider.rewarded(for: placement)))
|
||||
guard currentPlacement == placement else {
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
loadedAd.delegate = self
|
||||
rewardedAd = loadedAd
|
||||
} catch {
|
||||
if currentPlacement == placement {
|
||||
rewardedAd = nil
|
||||
}
|
||||
}
|
||||
|
||||
if currentPlacement == placement {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showAdIfAvailable(for placement: YandexRewardedPlacement, onReward: @escaping @MainActor () -> Void) -> Bool {
|
||||
guard let presenter = presentingViewController(), let rewardedAd, currentPlacement == placement else {
|
||||
preloadAd(for: placement)
|
||||
return false
|
||||
}
|
||||
|
||||
pendingRewardAction = onReward
|
||||
rewardedPlacement = placement
|
||||
rewardedAd.show(from: presenter)
|
||||
return true
|
||||
}
|
||||
|
||||
private func completeRewardIfNeeded() {
|
||||
let action = pendingRewardAction
|
||||
pendingRewardAction = nil
|
||||
action?()
|
||||
}
|
||||
|
||||
private func resetAndPreload() {
|
||||
pendingRewardAction = nil
|
||||
rewardedAd = nil
|
||||
|
||||
if let rewardedPlacement {
|
||||
self.rewardedPlacement = nil
|
||||
preloadAd(for: rewardedPlacement)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentingViewController() -> UIViewController? {
|
||||
guard
|
||||
let rootViewController = UIApplication.shared.connectedScenes
|
||||
.compactMap({ $0 as? UIWindowScene })
|
||||
.flatMap({ $0.windows })
|
||||
.first(where: { $0.isKeyWindow })?
|
||||
.rootViewController
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var topViewController = rootViewController
|
||||
|
||||
while let presentedViewController = topViewController.presentedViewController {
|
||||
topViewController = presentedViewController
|
||||
}
|
||||
|
||||
return topViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension YandexRewardedAdManager: RewardedAdDelegate {
|
||||
|
||||
func rewardedAd(_ rewardedAd: RewardedAd, didReward reward: Reward) {
|
||||
DEBUG_LOG("리워드 광고 보상 받기 성공")
|
||||
completeRewardIfNeeded()
|
||||
}
|
||||
|
||||
func rewardedAd(_ rewardedAd: RewardedAd, didFailToShow error: Error) {
|
||||
DEBUG_LOG("리워드 광고 에러")
|
||||
resetAndPreload()
|
||||
}
|
||||
|
||||
func rewardedAdDidShow(_ rewardedAd: RewardedAd) {
|
||||
}
|
||||
|
||||
func rewardedAdDidDismiss(_ rewardedAd: RewardedAd) {
|
||||
DEBUG_LOG("리워드 광고 닫기")
|
||||
resetAndPreload()
|
||||
}
|
||||
|
||||
func rewardedAdDidClick(_ rewardedAd: RewardedAd) {
|
||||
}
|
||||
|
||||
func rewardedAd(_ rewardedAd: RewardedAd, didTrackImpression impressionData: ImpressionData?) {
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ struct ContentAllByThemeView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -34,7 +34,7 @@ struct ContentAllByThemeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
Text(I18n.Content.Sort.priceHigh)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -46,7 +46,7 @@ struct ContentAllByThemeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
Text(I18n.Content.Sort.priceLow)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -64,7 +64,7 @@ struct ContentAllByThemeView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -73,7 +73,7 @@ struct ContentAllByThemeView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -78,13 +78,13 @@ final class ContentAllByThemeViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@ struct ContentAllView: View {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: isFree ? String(localized: "무료 콘텐츠 전체") : isPointAvailableOnly ? String(localized: "포인트 대여 전체") : String(localized: "콘텐츠 전체"))
|
||||
DetailNavigationBar(
|
||||
title: isFree ?
|
||||
I18n.Content.All.freeTitle :
|
||||
isPointAvailableOnly ? I18n.Content.All.pointRentalTitle : I18n.Content.All.title
|
||||
)
|
||||
|
||||
if !viewModel.themeList.isEmpty {
|
||||
ContentMainContentThemeView(
|
||||
@@ -32,7 +36,7 @@ struct ContentAllView: View {
|
||||
HStack(spacing: 12) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -44,7 +48,7 @@ struct ContentAllView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("인기순")
|
||||
Text(I18n.Content.Sort.popularity)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
|
||||
@@ -41,7 +41,7 @@ struct ContentNewAllItemView: View {
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 8.5, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ struct ContentNewAllView: View {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
DetailNavigationBar(title: isFree ? "최신 무료 콘텐츠" : "최신 콘텐츠")
|
||||
DetailNavigationBar(title: isFree ? I18n.Content.New.freeTitle : I18n.Content.New.title)
|
||||
|
||||
Text("※ 최근 2주간 등록된 새로운 콘텐츠 입니다.")
|
||||
Text(I18n.Content.New.recentTwoWeeksNotice)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(.graybb)
|
||||
.padding(.horizontal, 13.3)
|
||||
@@ -37,7 +37,7 @@ struct ContentNewAllView: View {
|
||||
)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -46,7 +46,7 @@ struct ContentNewAllView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -17,14 +17,14 @@ struct ContentRankingAllView: View {
|
||||
Group {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "인기 콘텐츠")
|
||||
DetailNavigationBar(title: I18n.Content.Ranking.title)
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text("\(viewModel.dateString)")
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.")
|
||||
Text(I18n.Content.Ranking.weeklyUpdateNotice)
|
||||
.appFont(size: 13.3, weight: .light)
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
}
|
||||
@@ -82,7 +82,7 @@ struct ContentRankingAllView: View {
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
Text(I18n.Common.points)
|
||||
.appFont(size: 8, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
@@ -116,7 +116,7 @@ struct ContentRankingAllView: View {
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.horizontal, 5.3)
|
||||
|
||||
@@ -71,13 +71,13 @@ final class ContentRankingAllViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -109,13 +109,13 @@ final class ContentRankingAllViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,11 +49,11 @@ struct ContentListCategoryView: View {
|
||||
|
||||
ContentListCategoryView(
|
||||
categoryList: [
|
||||
GetCategoryListResponse(categoryId: 0, category: "전체"),
|
||||
GetCategoryListResponse(categoryId: 0, category: I18n.Category.all),
|
||||
GetCategoryListResponse(categoryId: 1, category: "test"),
|
||||
GetCategoryListResponse(categoryId: 0, category: "test2")
|
||||
],
|
||||
selectCategory: { _ in },
|
||||
selectedCategory: .constant("전체")
|
||||
selectedCategory: .constant(I18n.Category.all)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ struct ContentListItemView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(spacing: 8) {
|
||||
if item.isScheduledToOpen {
|
||||
Text("오픈예정")
|
||||
Text(I18n.Common.openScheduled)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(2.6)
|
||||
@@ -52,7 +52,7 @@ struct ContentListItemView: View {
|
||||
.cornerRadius(2.6)
|
||||
|
||||
if item.isPointAvailable {
|
||||
Text("포인트")
|
||||
Text(I18n.Common.points)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(2.6)
|
||||
@@ -98,7 +98,7 @@ struct ContentListItemView: View {
|
||||
Spacer()
|
||||
|
||||
if item.isOwned {
|
||||
Text("소장중")
|
||||
Text(I18n.Content.Status.owned)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.gray11)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -106,7 +106,7 @@ struct ContentListItemView: View {
|
||||
.background(Color(hex: "b1ef2c"))
|
||||
.cornerRadius(2.6)
|
||||
} else if item.isRented {
|
||||
Text("대여중")
|
||||
Text(I18n.Content.Status.rented)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -114,7 +114,7 @@ struct ContentListItemView: View {
|
||||
.background(Color(hex: "660fd4"))
|
||||
.cornerRadius(2.6)
|
||||
} else if item.isSoldOut {
|
||||
Text("Sold Out")
|
||||
Text(I18n.Content.Status.soldOut)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -135,7 +135,7 @@ struct ContentListItemView: View {
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
Text(I18n.CreateContent.free)
|
||||
.appFont(size: 14, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ContentListView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("콘텐츠 전체보기")
|
||||
Text(I18n.Content.List.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
@@ -46,7 +46,7 @@ struct ContentListView: View {
|
||||
}
|
||||
|
||||
if userId == UserDefaults.int(forKey: .userId) {
|
||||
Text("새로운 콘텐츠 등록하기")
|
||||
Text(I18n.Content.List.createNewContentAction)
|
||||
.appFont(size: 15, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 17)
|
||||
@@ -61,7 +61,7 @@ struct ContentListView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -73,7 +73,7 @@ struct ContentListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
Text(I18n.Content.Sort.priceHigh)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -85,7 +85,7 @@ struct ContentListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
Text(I18n.Content.Sort.priceLow)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -103,7 +103,7 @@ struct ContentListView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -112,7 +112,7 @@ struct ContentListView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -200,7 +200,7 @@ extension ContentPlayManager {
|
||||
}
|
||||
|
||||
private func showError() {
|
||||
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||
self.errorMessage = I18n.Content.Playback.playFailed
|
||||
self.isShowPopup = true
|
||||
self.resetAudioData()
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ struct ContentCreateSelectThemeView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("테마 선택")
|
||||
Text(I18n.CreateContent.selectTheme)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@ final class ContentCreateSelectThemeViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ContentCreateView: View {
|
||||
@Environment(\.locale) private var locale
|
||||
|
||||
@StateObject var keyboardHandler = KeyboardHandler()
|
||||
@StateObject private var viewModel = ContentCreateViewModel()
|
||||
@@ -36,11 +37,11 @@ struct ContentCreateView: View {
|
||||
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)
|
||||
@@ -73,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)
|
||||
@@ -103,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)
|
||||
@@ -121,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)
|
||||
}
|
||||
@@ -146,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)
|
||||
@@ -188,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)
|
||||
@@ -215,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)
|
||||
@@ -236,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)
|
||||
@@ -264,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)
|
||||
@@ -281,7 +282,7 @@ struct ContentCreateView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("캔")
|
||||
Text(I18n.CreateContent.canUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
@@ -296,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)
|
||||
@@ -316,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)
|
||||
@@ -336,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)
|
||||
@@ -353,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)
|
||||
@@ -375,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)
|
||||
@@ -398,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)
|
||||
@@ -429,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)
|
||||
@@ -458,7 +459,7 @@ struct ContentCreateView: View {
|
||||
|
||||
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)
|
||||
@@ -477,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)
|
||||
@@ -488,7 +489,7 @@ struct ContentCreateView: View {
|
||||
}
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("댓글 가능 여부")
|
||||
Text(I18n.CreateContent.commentAvailabilityTitle)
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
@@ -533,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)
|
||||
|
||||
@@ -541,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)
|
||||
@@ -554,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)
|
||||
|
||||
@@ -562,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)
|
||||
@@ -586,7 +587,7 @@ struct ContentCreateView: View {
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("등록")
|
||||
Text(I18n.CreateContent.registerButton)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.frame(height: 50)
|
||||
|
||||
@@ -154,7 +154,7 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
mimeType: "image/*")
|
||||
)
|
||||
} else {
|
||||
errorMessage = "커버이미지를 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.coverImageUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
@@ -176,19 +176,19 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
)
|
||||
)
|
||||
} else {
|
||||
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.contentFileUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
} else {
|
||||
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.contentFileUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
} else {
|
||||
errorMessage = "콘텐츠 파일을 업로드 하지 못했습니다.\n다시 선택해 주세요"
|
||||
errorMessage = I18n.CreateContent.contentFileUploadFailed
|
||||
isShowPopup = true
|
||||
isLoading = false
|
||||
return
|
||||
@@ -219,19 +219,19 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
@@ -240,37 +240,37 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
|
||||
private func validateData() -> Bool {
|
||||
if title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
errorMessage = "제목을 입력해 주세요."
|
||||
errorMessage = I18n.CreateContent.titleRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if detail.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || detail.count < 5 {
|
||||
errorMessage = "내용을 5자 이상 입력해 주세요."
|
||||
errorMessage = I18n.CreateContent.detailMinLengthRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if theme == nil {
|
||||
errorMessage = "테마를 선택해 주세요."
|
||||
errorMessage = I18n.CreateContent.themeRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if coverImage == nil {
|
||||
errorMessage = "커버이미지를 선택해 주세요."
|
||||
errorMessage = I18n.CreateContent.coverImageRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if selectedFileUrl == nil {
|
||||
errorMessage = "오디오 콘텐츠를 선택해 주세요."
|
||||
errorMessage = I18n.CreateContent.audioContentRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if !isFree && price < 5 {
|
||||
errorMessage = "콘텐츠의 최소금액은 5캔 입니다."
|
||||
errorMessage = I18n.CreateContent.minimumPriceRequired
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
@@ -278,14 +278,14 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
if previewStartTime.count > 0 && previewEndTime.count > 0 {
|
||||
let startTimeArray = previewStartTime.split(separator: ":")
|
||||
if startTimeArray.count != 3 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
for time in startTimeArray {
|
||||
if time.count != 2 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
@@ -293,14 +293,14 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
|
||||
let endTimeArray = previewStartTime.split(separator: ":")
|
||||
if endTimeArray.count != 3 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
for time in endTimeArray {
|
||||
if time.count != 2 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
errorMessage = I18n.CreateContent.previewTimeFormatInvalid
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
@@ -308,13 +308,13 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
|
||||
let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime)
|
||||
if timeDifference < 15.0 {
|
||||
errorMessage = "미리 듣기의 최소 시간은 15초 입니다"
|
||||
errorMessage = I18n.CreateContent.previewMinimumDurationError
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if previewStartTime.count > 0 || previewEndTime.count > 0 {
|
||||
errorMessage = "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다."
|
||||
errorMessage = I18n.CreateContent.previewStartEndBothOrNone
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct QuarterTimePickerView: View {
|
||||
@Environment(\.locale) private var locale
|
||||
|
||||
@Binding var selectedTime: Date
|
||||
@Binding var isShowing: Bool
|
||||
@@ -28,7 +29,7 @@ struct QuarterTimePickerView: View {
|
||||
)
|
||||
.datePickerStyle(WheelDatePickerStyle())
|
||||
.labelsHidden()
|
||||
.environment(\.locale, Locale.init(identifier: "ko"))
|
||||
.environment(\.locale, locale)
|
||||
.frame(width: proxy.size.width - 53.4)
|
||||
.onAppear {
|
||||
UIDatePicker.appearance().minuteInterval = 15
|
||||
@@ -38,7 +39,7 @@ struct QuarterTimePickerView: View {
|
||||
}
|
||||
|
||||
Button(action: { self.isShowing = false }) {
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 16)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 10)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SelectDatePicker: View {
|
||||
@Environment(\.locale) private var locale
|
||||
|
||||
@Binding var selectedDate: Date
|
||||
@Binding var isShowing: Bool
|
||||
@@ -24,11 +25,11 @@ struct SelectDatePicker: View {
|
||||
DatePicker("", selection: $selectedDate, in: Date()..., displayedComponents: .date)
|
||||
.datePickerStyle(WheelDatePickerStyle())
|
||||
.labelsHidden()
|
||||
.environment(\.locale, Locale.init(identifier: "ko"))
|
||||
.environment(\.locale, locale)
|
||||
.frame(width: proxy.size.width)
|
||||
|
||||
Button(action: { self.isShowing = false }) {
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 16)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 10)
|
||||
|
||||
@@ -30,7 +30,7 @@ struct ContentCurationView: View {
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
Text(I18n.Content.Sort.newest)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -42,7 +42,7 @@ struct ContentCurationView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
Text(I18n.Content.Sort.priceHigh)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -54,7 +54,7 @@ struct ContentCurationView: View {
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
Text(I18n.Content.Sort.priceLow)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
@@ -72,7 +72,7 @@ struct ContentCurationView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
Text(I18n.Content.Count.totalPrefix)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
@@ -81,7 +81,7 @@ struct ContentCurationView: View {
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
Text(I18n.Content.Count.countUnit)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
@@ -77,13 +77,13 @@ final class ContentCurationViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ struct AudioContentDeleteDialogView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
Text("콘텐츠 삭제")
|
||||
Text(I18n.ContentDetail.DeleteDialog.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("[\(title)]을 삭제하시겠습니까?")
|
||||
Text(I18n.ContentDetail.DeleteDialog.confirmQuestion(title))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.top, 21.3)
|
||||
@@ -36,7 +36,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
isAgree.toggle()
|
||||
}
|
||||
|
||||
Text("삭제된 콘텐츠는 되돌릴 수 없음을 알고 있습니다.")
|
||||
Text(I18n.ContentDetail.DeleteDialog.irreversibleAcknowledgement)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.onTapGesture {
|
||||
@@ -48,7 +48,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
.cornerRadius(6.7)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("콘텐츠를 삭제하더라도 이미 구매한\n사용자는 콘텐츠를 이용할 수 있습니다.")
|
||||
Text(I18n.ContentDetail.DeleteDialog.purchasedUserNotice)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color(hex: "dd4500"))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
@@ -56,7 +56,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Text("취소")
|
||||
Text(I18n.Common.cancel)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(.horizontal, 55)
|
||||
@@ -70,7 +70,7 @@ struct AudioContentDeleteDialogView: View {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
Text("확인")
|
||||
Text(I18n.Common.confirm)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.horizontal, 55)
|
||||
|
||||
@@ -13,15 +13,7 @@ struct AudioContentReportDialogView: View {
|
||||
let confirmAction: (String) -> Void
|
||||
|
||||
@State private var selectedIndex: Int? = nil
|
||||
let reasons = [
|
||||
"괴롭힘 및 사이버 폭력",
|
||||
"개인정보 침해",
|
||||
"명의도용",
|
||||
"폭력적 위협",
|
||||
"아동학대",
|
||||
"보호대상 집단에 대한 증오심 표현",
|
||||
"스팸 및 사기"
|
||||
]
|
||||
let reasons = I18n.ContentDetail.ReportDialog.reasons
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -31,7 +23,7 @@ struct AudioContentReportDialogView: View {
|
||||
.onTapGesture { isShowing = false }
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
Text("콘텐츠 신고")
|
||||
Text(I18n.ContentDetail.ReportDialog.title)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
@@ -59,13 +51,13 @@ struct AudioContentReportDialogView: View {
|
||||
.cornerRadius(6.7)
|
||||
.padding(.vertical, 21.3)
|
||||
|
||||
Text("신고한 콘텐츠를 관리자가 확인 후, 서비스정책을\n위반한 경우 삭제 조치할 예정입니다.")
|
||||
Text(I18n.ContentDetail.ReportDialog.notice)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color(hex: "dd4500"))
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
HStack(spacing: 12) {
|
||||
Text("취소")
|
||||
Text(I18n.Common.cancel)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(.vertical, 16)
|
||||
@@ -79,7 +71,7 @@ struct AudioContentReportDialogView: View {
|
||||
isShowing = false
|
||||
}
|
||||
|
||||
Text("신고")
|
||||
Text(I18n.ContentDetail.ReportDialog.reportAction)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.vertical, 16)
|
||||
|
||||
@@ -51,7 +51,7 @@ struct AudioContentCommentItemView: View {
|
||||
.foregroundColor(Color.gray90)
|
||||
|
||||
if commentItem.isSecret {
|
||||
Text("비밀댓글")
|
||||
Text(I18n.ContentDetail.Comment.secretComment)
|
||||
.appFont(size: 11, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.horizontal, 4)
|
||||
@@ -104,7 +104,7 @@ struct AudioContentCommentItemView: View {
|
||||
HStack(spacing: 0) {
|
||||
if isModeModify {
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
@@ -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 {
|
||||
|
||||
@@ -29,7 +29,7 @@ struct AudioContentCommentListView: View {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("댓글")
|
||||
Text(I18n.ContentDetail.Comment.title)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
.padding(.leading, 13.3)
|
||||
@@ -65,7 +65,7 @@ struct AudioContentCommentListView: View {
|
||||
viewModel.isSecret.toggle()
|
||||
}
|
||||
|
||||
Text("비밀댓글")
|
||||
Text(I18n.ContentDetail.Comment.secretComment)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(viewModel.isSecret ? Color.button : Color.grayee)
|
||||
.onTapGesture {
|
||||
@@ -85,7 +85,7 @@ struct AudioContentCommentListView: View {
|
||||
.clipShape(Circle())
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $viewModel.comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $viewModel.comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
|
||||
@@ -64,13 +64,13 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -115,13 +115,13 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -135,13 +135,13 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
isActive: Bool? = nil
|
||||
) {
|
||||
if comment == nil && isActive == nil {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
errorMessage = I18n.ContentDetail.Comment.noChanges
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
errorMessage = "내용을 입력하세요."
|
||||
errorMessage = I18n.ContentDetail.Comment.inputContent
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -187,14 +187,14 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ struct AudioContentListReplyView: View {
|
||||
HStack(spacing: 6.7) {
|
||||
Image("ic_back")
|
||||
|
||||
Text("답글")
|
||||
Text(I18n.ContentDetail.Comment.replyTitle)
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -55,7 +55,7 @@ struct AudioContentListReplyView: View {
|
||||
.clipShape(Circle())
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $viewModel.comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $viewModel.comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
|
||||
@@ -62,13 +62,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -133,13 +133,13 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
isActive: Bool? = nil
|
||||
) {
|
||||
if comment == nil && isActive == nil {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
errorMessage = I18n.ContentDetail.Comment.noChanges
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
errorMessage = "내용을 입력하세요."
|
||||
errorMessage = I18n.ContentDetail.Comment.inputContent
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
@@ -185,14 +185,14 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ struct ContentDetailCommentView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10.3) {
|
||||
HStack(spacing: 5.3) {
|
||||
Text("댓글")
|
||||
Text(I18n.ContentDetail.Comment.title)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -38,7 +38,7 @@ struct ContentDetailCommentView: View {
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("비밀댓글")
|
||||
Text(I18n.ContentDetail.Comment.secretComment)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(isSecret ? Color.button : Color.grayee)
|
||||
}
|
||||
@@ -71,7 +71,7 @@ struct ContentDetailCommentView: View {
|
||||
.padding(.leading, 3)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $comment)
|
||||
TextField(I18n.ContentDetail.Comment.inputPlaceholder, text: $comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
|
||||
@@ -18,7 +18,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
HStack(spacing: 0) {
|
||||
Text("한정판")
|
||||
Text(I18n.ContentDetail.LimitedEdition.title)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
|
||||
@@ -40,7 +40,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
.foregroundColor(Color.grayd2)
|
||||
.padding(.leading, 2.3)
|
||||
} else if (remainingContentCount <= 0) {
|
||||
Text("Sold Out")
|
||||
Text(I18n.Content.Status.soldOut)
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
.padding(.horizontal, 5.3)
|
||||
@@ -51,7 +51,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
.foregroundColor(Color.grayd2)
|
||||
)
|
||||
} else {
|
||||
Text("잔여수량")
|
||||
Text(I18n.ContentDetail.LimitedEdition.remainingCount)
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayd2)
|
||||
|
||||
@@ -69,7 +69,7 @@ struct ContentDetailInfoLimitedEditionView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
if !buyerList.isEmpty {
|
||||
Text("구매자")
|
||||
Text(I18n.ContentDetail.LimitedEdition.buyers)
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
|
||||