Compare commits
142 Commits
d8c27ae225
...
ed9c2d9d32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed9c2d9d32 | ||
|
|
27720cfb65 | ||
|
|
11dede49e2 | ||
|
|
5cd0ea9bc2 | ||
|
|
42e375ec4b | ||
|
|
5e85b1d679 | ||
|
|
5e209662d8 | ||
|
|
b522b50dee | ||
|
|
95cae7d0bf | ||
|
|
7e37fc6f57 | ||
|
|
d0a17739a1 | ||
|
|
091f90dee6 | ||
|
|
e100bcb593 | ||
|
|
7825f54a01 | ||
|
|
aa29acad0e | ||
|
|
fb6b623564 | ||
|
|
791ebb0242 | ||
|
|
0a40bdbee8 | ||
|
|
7cdb46dd8a | ||
|
|
02d767d5d8 | ||
|
|
9533b06d1e | ||
|
|
b3331d5512 | ||
|
|
c9c1db39a6 | ||
|
|
280e424385 | ||
|
|
d92dcbc696 | ||
|
|
5a344956e3 | ||
|
|
87c66136ac | ||
|
|
4b9cdeb824 | ||
|
|
75452f0ffd | ||
|
|
9223e26a07 | ||
|
|
a743fecfdd | ||
|
|
d3037d1ba3 | ||
|
|
f1353fc2e6 | ||
|
|
68dab028cc | ||
|
|
4d893a2081 | ||
|
|
d9861b4612 | ||
|
|
a34c050d25 | ||
|
|
c84227008c | ||
|
|
4354530372 | ||
|
|
1b583eabba | ||
|
|
eb83d5e5e3 | ||
|
|
fa4483cd18 | ||
|
|
f855ca55ca | ||
|
|
e7c8c2a12e | ||
|
|
2f8331f2ff | ||
|
|
921c7e008c | ||
|
|
7775e4f0d9 | ||
|
|
19380ccc70 | ||
|
|
64bb5668f4 | ||
|
|
c6da15f42c | ||
|
|
fba3940a94 | ||
|
|
57f24f2481 | ||
|
|
7c1d57676c | ||
|
|
6a7497af7d | ||
|
|
dd51a3fc2e | ||
|
|
7307e5b255 | ||
|
|
dbc4e40904 | ||
|
|
3220f300b5 | ||
|
|
8dae75afd7 | ||
|
|
0ddb5810b1 | ||
|
|
f67fd5bb26 | ||
|
|
a406935e4f | ||
|
|
ee4b7c9a79 | ||
|
|
321d97bf4d | ||
|
|
fdac62fc7a | ||
|
|
27b024f187 | ||
|
|
e035e57fc2 | ||
|
|
c80246adbf | ||
|
|
bea50b0085 | ||
|
|
f51fe327e9 | ||
|
|
8bcbd3aca0 | ||
|
|
982a17bb41 | ||
|
|
8efa89d564 | ||
|
|
a0bb593431 | ||
|
|
fa163ec83d | ||
|
|
619870e1de | ||
|
|
ad9f26c8a0 | ||
|
|
da2e8d0209 | ||
|
|
d2ff2782fe | ||
|
|
30c70ee638 | ||
|
|
b61f432b72 | ||
|
|
96df4c1f1b | ||
|
|
0285f62ecb | ||
|
|
b2c94a44d9 | ||
|
|
bc4b30a965 | ||
|
|
3f24b1f3d4 | ||
|
|
d5437c03b0 | ||
|
|
f88926f8a8 | ||
|
|
ea733b57e6 | ||
|
|
dd7b42d58a | ||
|
|
972e889fab | ||
|
|
3e60a8c340 | ||
|
|
dbf8bc2dd2 | ||
|
|
3f857eb293 | ||
|
|
0aa86c26ca | ||
|
|
a36a440340 | ||
|
|
55fb470b37 | ||
|
|
0375722d4c | ||
|
|
4c8aabdd4c | ||
|
|
9e7c62b794 | ||
|
|
d3ddcecc68 | ||
|
|
6a4f4767e0 | ||
|
|
90b6dfb498 | ||
|
|
001f161fc5 | ||
|
|
4d5ac61dbe | ||
|
|
68fd9ee3ad | ||
|
|
af42fd074f | ||
|
|
31319e4292 | ||
|
|
6cd0e86308 | ||
|
|
54a5c99666 | ||
|
|
cb1b26c548 | ||
|
|
5fcc6a9a60 | ||
|
|
a6ef1d89ce | ||
|
|
9cea4c244a | ||
|
|
43629a27b8 | ||
|
|
daca685ea2 | ||
|
|
438f12024f | ||
|
|
72329d6f60 | ||
|
|
280ce4beda | ||
|
|
320dbb6d57 | ||
|
|
be40fbc226 | ||
|
|
ed48efd58d | ||
|
|
c4a7742514 | ||
|
|
e5810766b1 | ||
|
|
74212405a4 | ||
|
|
2c74bb743b | ||
|
|
0fd49a71f6 | ||
|
|
0902b1fe30 | ||
|
|
8bacf363ff | ||
|
|
1f88bcbddc | ||
|
|
7de2b1c4dd | ||
|
|
6e3a1e1869 | ||
|
|
0134a5286f | ||
|
|
2af2f2ffea | ||
|
|
3a7da9a876 | ||
|
|
54d8845342 | ||
|
|
a4c5a790fe | ||
|
|
3cbac1280e | ||
|
|
34eed366bd | ||
|
|
95c2e992de | ||
|
|
76757215cf | ||
|
|
0a59c6f575 |
1
.gitignore
vendored
@@ -279,5 +279,6 @@ xcuserdata
|
||||
|
||||
.kiro/
|
||||
.junie/
|
||||
docs/
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/macos,xcode,appcode,swift,swiftpackagemanager,swiftpm,fastlane,cocoapods
|
||||
|
||||
18
AGENTS.md
Normal file
@@ -0,0 +1,18 @@
|
||||
질문에 대한 답변과 설명은 한국어로 한다.
|
||||
|
||||
## Quality Assurance Guidelines
|
||||
|
||||
### Commit Standards
|
||||
1. Write in Korean.
|
||||
2. Use the present tense in the subject line (e.g., "Add feature" not "Added feature").
|
||||
3. Keep the subject line to 50 characters or less.
|
||||
4. Add a blank line between the subject and body.
|
||||
5. Keep the body to 72 characters or less per line.
|
||||
6. Within a paragraph, only break lines when the text exceeds 72 characters.
|
||||
7. Describe changes to public API features and do not include implementation details such as package-private code.
|
||||
8. Do not mention test code in commit messages.
|
||||
9. Do not use any prefix (such as "fix:", "update:", "docs:", "feat:", etc.) in the subject line.
|
||||
10. Do not start the subject line with a lowercase letter unless the first word is a function name or another identifier that is conventionally lowercase and there is a clear, justifiable reason for the exception. Otherwise, always start with an uppercase letter.
|
||||
11. Do not include tool advertisements, branding, or promotional content in commit messages.
|
||||
12. Use separate git commands to stage files before committing.
|
||||
13. Always validate commits using `work/scripts/check-commit-message-rules.sh` and fix until validation passes.
|
||||
4
Podfile
@@ -8,6 +8,8 @@ target 'SodaLive' do
|
||||
# Pods for SodaLive
|
||||
pod 'BootpayUI', '4.4.10'
|
||||
pod 'AgoraRtm', '2.2.4'
|
||||
pod 'GoogleSignIn'
|
||||
pod 'GoogleSignInSwiftSupport'
|
||||
|
||||
end
|
||||
|
||||
@@ -18,6 +20,8 @@ target 'SodaLive-dev' do
|
||||
# Pods for SodaLive-dev
|
||||
pod 'BootpayUI', '4.4.10'
|
||||
pod 'AgoraRtm', '2.2.4'
|
||||
pod 'GoogleSignIn'
|
||||
pod 'GoogleSignInSwiftSupport'
|
||||
|
||||
end
|
||||
|
||||
|
||||
51
Podfile.lock
@@ -5,6 +5,16 @@ PODS:
|
||||
- AgoraRtm/RtmBasic (2.2.4)
|
||||
- AgoraRtm/RtmKit (2.2.4)
|
||||
- Alamofire (5.10.2)
|
||||
- AppAuth (2.0.0):
|
||||
- AppAuth/Core (= 2.0.0)
|
||||
- AppAuth/ExternalUserAgent (= 2.0.0)
|
||||
- AppAuth/Core (2.0.0)
|
||||
- AppAuth/ExternalUserAgent (2.0.0):
|
||||
- AppAuth/Core
|
||||
- AppCheckCore (11.2.0):
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- Bootpay (4.4.6):
|
||||
- CryptoSwift
|
||||
- NVActivityIndicatorView
|
||||
@@ -17,40 +27,79 @@ PODS:
|
||||
- SnapKit
|
||||
- SwiftyJSON
|
||||
- CryptoSwift (1.8.4)
|
||||
- GoogleSignIn (9.1.0):
|
||||
- AppAuth (~> 2.0)
|
||||
- AppCheckCore (~> 11.0)
|
||||
- GTMAppAuth (~> 5.0)
|
||||
- GTMSessionFetcher/Core (~> 3.3)
|
||||
- GoogleSignInSwiftSupport (9.1.0):
|
||||
- GoogleSignIn (~> 9.0)
|
||||
- GoogleUtilities/Environment (8.1.0):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.1.0)
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GTMAppAuth (5.0.0):
|
||||
- AppAuth/Core (~> 2.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- NVActivityIndicatorView (5.2.0):
|
||||
- NVActivityIndicatorView/Base (= 5.2.0)
|
||||
- NVActivityIndicatorView/Base (5.2.0)
|
||||
- ObjectMapper (4.4.2)
|
||||
- PromisesObjC (2.4.0)
|
||||
- SnapKit (5.7.1)
|
||||
- SwiftyJSON (5.0.2)
|
||||
|
||||
DEPENDENCIES:
|
||||
- AgoraRtm (= 2.2.4)
|
||||
- BootpayUI (= 4.4.10)
|
||||
- GoogleSignIn
|
||||
- GoogleSignInSwiftSupport
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- AgoraRtm
|
||||
- Alamofire
|
||||
- AppAuth
|
||||
- AppCheckCore
|
||||
- Bootpay
|
||||
- BootpayUI
|
||||
- CryptoSwift
|
||||
- GoogleSignIn
|
||||
- GoogleSignInSwiftSupport
|
||||
- GoogleUtilities
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
- NVActivityIndicatorView
|
||||
- ObjectMapper
|
||||
- PromisesObjC
|
||||
- SnapKit
|
||||
- SwiftyJSON
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AgoraRtm: 534144434383d41b3b0ebfae2a961ef0f51b0645
|
||||
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||
AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
Bootpay: cd7f0542b096ab0af0b09a6e12a6b87f2cbbb531
|
||||
BootpayUI: beec5b0bba002b4dbced8c0ecace571ed6a017bc
|
||||
CryptoSwift: e64e11850ede528a02a0f3e768cec8e9d92ecb90
|
||||
GoogleSignIn: fcee2257188d5eda57a5e2b6a715550ffff9206d
|
||||
GoogleSignInSwiftSupport: aca902e4e15b234611ecac74ef5c8f61278f774e
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
NVActivityIndicatorView: fe52a6a68664c2df8991d7d9e3d86d8d19453c53
|
||||
ObjectMapper: e6e4d91ff7f2861df7aecc536c92d8363f4c9677
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
|
||||
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
|
||||
|
||||
PODFILE CHECKSUM: 197d8c8b434dbcc335438281fc68e94718f6a8e1
|
||||
PODFILE CHECKSUM: 70c5639090824ff26cfad959985347579609e1e6
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "1f28da3687662a2a9efe60ffc2ca2499be411b5b0a1e07f72559059c40728121",
|
||||
"originHash" : "9f35428c4c178ca4a8bfa4b72544585a9e4d5b119825b423e1d2166cbe03fe37",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "abseil-cpp-binary",
|
||||
@@ -127,6 +127,15 @@
|
||||
"version" : "100.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "kakao-ios-sdk",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kakao/kakao-ios-sdk",
|
||||
"state" : {
|
||||
"revision" : "5978979157a5a0521c9c56fd0156aec794caa21c",
|
||||
"version" : "2.27.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "kingfisher",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -145,6 +154,15 @@
|
||||
"version" : "1.22.5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "line-sdk-ios-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/line/line-sdk-ios-swift.git",
|
||||
"state" : {
|
||||
"revision" : "51ef2ebefb05db8f748e80208b3281ca723abcdb",
|
||||
"version" : "5.14.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "moya",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
21
SodaLive/Resources/Assets.xcassets/btn_follow_big_en.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_follow_big_en.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/btn_follow_big_en.imageset/btn_follow_big_en.png
vendored
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
21
SodaLive/Resources/Assets.xcassets/btn_follow_big_ja.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_follow_big_ja.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/btn_follow_big_ja.imageset/btn_follow_big_ja.png
vendored
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
21
SodaLive/Resources/Assets.xcassets/btn_following_big_en.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_following_big_en.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/btn_following_big_en.imageset/btn_following_big_en.png
vendored
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
21
SodaLive/Resources/Assets.xcassets/btn_following_big_ja.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_following_big_ja.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/btn_following_big_ja.imageset/btn_following_big_ja.png
vendored
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
21
SodaLive/Resources/Assets.xcassets/btn_following_no_alarm_big_en.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_following_no_alarm_big_en.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
21
SodaLive/Resources/Assets.xcassets/btn_following_no_alarm_big_ja.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_following_no_alarm_big_ja.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -9,7 +9,7 @@
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_make_live.png",
|
||||
"filename" : "ic_login_line.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/ic_login_line.imageset/ic_login_line.png
vendored
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_login_x.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_login_x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/ic_login_x.imageset/ic_login_x.png
vendored
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_make_live.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_make_live.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/ic_make_live.imageset/ic_make_live.png
vendored
Normal file
|
After Width: | Height: | Size: 896 B |
21
SodaLive/Resources/Assets.xcassets/ic_no_preview.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_no_preview.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/ic_no_preview.imageset/ic_no_preview.png
vendored
Normal file
|
After Width: | Height: | Size: 677 B |
21
SodaLive/Resources/Assets.xcassets/img_apply_creator.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img_apply_creator.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/img_apply_creator.imageset/img_apply_creator.png
vendored
Normal file
|
After Width: | Height: | Size: 42 KiB |
21
SodaLive/Resources/Assets.xcassets/img_rank_1.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img_rank_1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/img_rank_1.imageset/img_rank_1.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
21
SodaLive/Resources/Assets.xcassets/img_rank_2.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img_rank_2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/img_rank_2.imageset/img_rank_2.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
21
SodaLive/Resources/Assets.xcassets/img_rank_3.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "img_rank_3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
SodaLive/Resources/Assets.xcassets/img_rank_3.imageset/img_rank_3.png
vendored
Normal file
|
After Width: | Height: | Size: 16 KiB |
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rank_1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rank_2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "rank_3.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.5 MiB After Width: | Height: | Size: 4.1 MiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,257 +1,296 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb608674328645232</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>voiceon-test</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>FacebookAppID</key>
|
||||
<string>608674328645232</string>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>3775e6ea83236a685d264b6c5a1bbb4d</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>보이스온 - Test1</string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<false/>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-1299501215847962~3447556960</string>
|
||||
<key>NSAdvertisingAttributionReportEndpoint</key>
|
||||
<string>https://appsflyer-skadnetwork.com/</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cstr6suwn9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ludvb6z3bs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cp8zw746q7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c6k4g5qg8m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s39g8k73mm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qy4746246.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f38h382jlk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hs6bdukanm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v4nxqhlyqp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wzmmz9fp6w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yclnxrl5pm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t38b2kh725.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7ug5zh24hu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y5ghdn5j9k.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n6fk4nfna4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v9wttpbfk9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n38lu8286q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>47vhws6wlr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9t245vhmpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eh6m2bh4zr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a2p9lx4jpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>22mmun2rn5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4468km3ulz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2u9pt9hc89.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8s468mfl3y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>klf5c3l5u5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ppxm28t8ap.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ecpz2srf59.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uw77j35x4d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pwa73g5rt2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mlmmfzh3r3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>578prtvx9j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>e5fvkxwrpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8c4e2ghe7u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3rd42ekr43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>gmarket_sans_bold.otf</string>
|
||||
<string>gmarket_sans_medium.otf</string>
|
||||
<string>gmarket_sans_light.otf</string>
|
||||
<string>Pretendard-Bold.otf</string>
|
||||
<string>Pretendard-Medium.otf</string>
|
||||
<string>Pretendard-Light.otf</string>
|
||||
<string>Pretendard-Regular.otf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb608674328645232</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>voiceon-test</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>kakao20cf19413d63bfdfd30e8e6dff933d33</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>com.googleusercontent.apps.758414412471-3cf403jb4s405eu17qrfrcbs9ofhq369</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<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>
|
||||
</array>
|
||||
<key>FacebookAppID</key>
|
||||
<string>608674328645232</string>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>3775e6ea83236a685d264b6c5a1bbb4d</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>보이스온 - Test1</string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<false/>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-1299501215847962~3447556960</string>
|
||||
<key>GIDClientID</key>
|
||||
<string>758414412471-3cf403jb4s405eu17qrfrcbs9ofhq369.apps.googleusercontent.com</string>
|
||||
<key>NSAdvertisingAttributionReportEndpoint</key>
|
||||
<string>https://appsflyer-skadnetwork.com/</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cstr6suwn9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ludvb6z3bs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cp8zw746q7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c6k4g5qg8m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s39g8k73mm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qy4746246.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f38h382jlk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hs6bdukanm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v4nxqhlyqp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wzmmz9fp6w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yclnxrl5pm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t38b2kh725.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7ug5zh24hu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y5ghdn5j9k.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n6fk4nfna4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v9wttpbfk9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n38lu8286q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>47vhws6wlr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9t245vhmpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eh6m2bh4zr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a2p9lx4jpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>22mmun2rn5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4468km3ulz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2u9pt9hc89.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8s468mfl3y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>klf5c3l5u5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ppxm28t8ap.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ecpz2srf59.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uw77j35x4d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pwa73g5rt2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mlmmfzh3r3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>578prtvx9j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>e5fvkxwrpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8c4e2ghe7u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3rd42ekr43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Pretendard-Bold.otf</string>
|
||||
<string>Pretendard-Medium.otf</string>
|
||||
<string>Pretendard-Light.otf</string>
|
||||
<string>Pretendard-Regular.otf</string>
|
||||
<string>NotoSansJP-Bold.ttf</string>
|
||||
<string>NotoSansJP-Medium.ttf</string>
|
||||
<string>NotoSansJP-Light.ttf</string>
|
||||
<string>NotoSansJP-Regular.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<!-- 카카오톡으로 로그인 -->
|
||||
<string>kakaokompassauth</string>
|
||||
<!-- 카카오톡 공유 -->
|
||||
<string>kakaolink</string>
|
||||
<!-- 카카오톡 채널 -->
|
||||
<string>kakaoplus</string>
|
||||
<!-- 라인 -->
|
||||
<string>lineauth2</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
12
SodaLive/Resources/Debug/en.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SodaLive
|
||||
|
||||
Created by klaus on 12/5/25.
|
||||
|
||||
*/
|
||||
"CFBundleDisplayName" = "VoiceOn-Test";
|
||||
"NSPhotoLibraryUsageDescription" = "Allow access to your photo library to select photos.";
|
||||
"NSMicrophoneUsageDescription" = "Allow access to your microphone to record audio.";
|
||||
"NSUserTrackingUsageDescription" = "Allow tracking to deliver personalized ads.";
|
||||
"NSCameraUsageDescription" = "Allow access to your camera to take photos.";
|
||||
12
SodaLive/Resources/Debug/ja.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SodaLive
|
||||
|
||||
Created by klaus on 12/5/25.
|
||||
|
||||
*/
|
||||
"CFBundleDisplayName" = "ボイスオン-テスト";
|
||||
"NSPhotoLibraryUsageDescription" = "写真を選択するために写真ライブラリへのアクセスが必要です。";
|
||||
"NSMicrophoneUsageDescription" = "音声を録音するためにマイクへのアクセスが必要です。";
|
||||
"NSUserTrackingUsageDescription" = "パーソナライズされた広告のためにトラッキングを許可してください。";
|
||||
"NSCameraUsageDescription" = "写真を撮影するためにカメラへのアクセスが必要です。";
|
||||
12
SodaLive/Resources/Debug/ko.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SodaLive
|
||||
|
||||
Created by klaus on 12/5/25.
|
||||
|
||||
*/
|
||||
"CFBundleDisplayName" = "보이스온-테스트";
|
||||
"NSPhotoLibraryUsageDescription" = "사진을 선택하려면 사진 라이브러리 접근이 필요합니다.";
|
||||
"NSMicrophoneUsageDescription" = "음성 녹음을 위해 마이크 접근이 필요합니다.";
|
||||
"NSUserTrackingUsageDescription" = "맞춤형 광고 제공을 위해 추적을 허용해 주세요.";
|
||||
"NSCameraUsageDescription" = "사진을 촬영하려면 카메라 접근이 필요합니다.";
|
||||
BIN
SodaLive/Resources/Font/NotoSansJP-Bold.ttf
Executable file
BIN
SodaLive/Resources/Font/NotoSansJP-Light.ttf
Executable file
BIN
SodaLive/Resources/Font/NotoSansJP-Medium.ttf
Executable file
BIN
SodaLive/Resources/Font/NotoSansJP-Regular.ttf
Executable file
@@ -1,257 +1,296 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb612448298237287</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>voiceon</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>FacebookAppID</key>
|
||||
<string>612448298237287</string>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>32af760f4a7b7cb7e3b1e7ffd0b0da70</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>보이스온</string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<false/>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-1299501215847962~8852459715</string>
|
||||
<key>NSAdvertisingAttributionReportEndpoint</key>
|
||||
<string>https://appsflyer-skadnetwork.com/</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cstr6suwn9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ludvb6z3bs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cp8zw746q7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c6k4g5qg8m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s39g8k73mm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qy4746246.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f38h382jlk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hs6bdukanm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v4nxqhlyqp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wzmmz9fp6w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yclnxrl5pm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t38b2kh725.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7ug5zh24hu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y5ghdn5j9k.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n6fk4nfna4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v9wttpbfk9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n38lu8286q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>47vhws6wlr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9t245vhmpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eh6m2bh4zr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a2p9lx4jpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>22mmun2rn5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4468km3ulz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2u9pt9hc89.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8s468mfl3y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>klf5c3l5u5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ppxm28t8ap.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ecpz2srf59.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uw77j35x4d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pwa73g5rt2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mlmmfzh3r3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>578prtvx9j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>e5fvkxwrpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8c4e2ghe7u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3rd42ekr43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>gmarket_sans_bold.otf</string>
|
||||
<string>gmarket_sans_medium.otf</string>
|
||||
<string>gmarket_sans_light.otf</string>
|
||||
<string>Pretendard-Bold.otf</string>
|
||||
<string>Pretendard-Medium.otf</string>
|
||||
<string>Pretendard-Light.otf</string>
|
||||
<string>Pretendard-Regular.otf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>fb612448298237287</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>voiceon</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>kakao231cf78acfa8252fca38b9eedf87c5cb</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>com.googleusercontent.apps.983594297130-m6bv7lvc1lsetsvv3rk92etqc98uopqj</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<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>
|
||||
</array>
|
||||
<key>FacebookAppID</key>
|
||||
<string>612448298237287</string>
|
||||
<key>FacebookAutoLogAppEventsEnabled</key>
|
||||
<true/>
|
||||
<key>FacebookClientToken</key>
|
||||
<string>32af760f4a7b7cb7e3b1e7ffd0b0da70</string>
|
||||
<key>FacebookDisplayName</key>
|
||||
<string>보이스온</string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<false/>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-1299501215847962~8852459715</string>
|
||||
<key>GIDClientID</key>
|
||||
<string>983594297130-m6bv7lvc1lsetsvv3rk92etqc98uopqj.apps.googleusercontent.com</string>
|
||||
<key>NSAdvertisingAttributionReportEndpoint</key>
|
||||
<string>https://appsflyer-skadnetwork.com/</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>SKAdNetworkItems</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cstr6suwn9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4fzdc2evr5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4pfyvq9l8r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2fnua5tdw4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ydx93a7ass.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>5a6flpkh64.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>p78axxw29g.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v72qych5uu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ludvb6z3bs.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>cp8zw746q7.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3sh42y64q3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>c6k4g5qg8m.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>s39g8k73mm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qy4746246.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>f38h382jlk.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>hs6bdukanm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v4nxqhlyqp.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>wzmmz9fp6w.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>yclnxrl5pm.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>t38b2kh725.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>7ug5zh24hu.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>gta9lk7p23.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>vutu7akeur.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>y5ghdn5j9k.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n6fk4nfna4.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>v9wttpbfk9.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>n38lu8286q.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>47vhws6wlr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>kbd757ywx3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>9t245vhmpl.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>eh6m2bh4zr.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>a2p9lx4jpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>22mmun2rn5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4468km3ulz.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>2u9pt9hc89.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8s468mfl3y.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>klf5c3l5u5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ppxm28t8ap.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>ecpz2srf59.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>uw77j35x4d.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>pwa73g5rt2.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>mlmmfzh3r3.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>578prtvx9j.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>4dzt52r2t5.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>e5fvkxwrpn.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>8c4e2ghe7u.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>zq492l623r.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3rd42ekr43.skadnetwork</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SKAdNetworkIdentifier</key>
|
||||
<string>3qcr597p9d.skadnetwork</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Pretendard-Bold.otf</string>
|
||||
<string>Pretendard-Medium.otf</string>
|
||||
<string>Pretendard-Light.otf</string>
|
||||
<string>Pretendard-Regular.otf</string>
|
||||
<string>NotoSansJP-Bold.ttf</string>
|
||||
<string>NotoSansJP-Medium.ttf</string>
|
||||
<string>NotoSansJP-Light.ttf</string>
|
||||
<string>NotoSansJP-Regular.ttf</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</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>
|
||||
</plist>
|
||||
|
||||
9966
SodaLive/Resources/Localizable.xcstrings
Normal file
12
SodaLive/Resources/en.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SodaLive
|
||||
|
||||
Created by klaus on 12/5/25.
|
||||
|
||||
*/
|
||||
"CFBundleDisplayName" = "VoiceOn";
|
||||
"NSPhotoLibraryUsageDescription" = "Allow access to your photo library to select photos.";
|
||||
"NSMicrophoneUsageDescription" = "Allow access to your microphone to record audio.";
|
||||
"NSUserTrackingUsageDescription" = "Allow tracking to deliver personalized ads.";
|
||||
"NSCameraUsageDescription" = "Allow access to your camera to take photos.";
|
||||
12
SodaLive/Resources/ja.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SodaLive
|
||||
|
||||
Created by klaus on 12/5/25.
|
||||
|
||||
*/
|
||||
"CFBundleDisplayName" = "ボイスオン";
|
||||
"NSPhotoLibraryUsageDescription" = "写真を選択するために写真ライブラリへのアクセスが必要です。";
|
||||
"NSMicrophoneUsageDescription" = "音声を録音するためにマイクへのアクセスが必要です。";
|
||||
"NSUserTrackingUsageDescription" = "パーソナライズされた広告のためにトラッキングを許可してください。";
|
||||
"NSCameraUsageDescription" = "写真を撮影するためにカメラへのアクセスが必要です。";
|
||||
12
SodaLive/Resources/ko.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
InfoPlist.strings
|
||||
SodaLive
|
||||
|
||||
Created by klaus on 12/5/25.
|
||||
|
||||
*/
|
||||
"CFBundleDisplayName" = "보이스온";
|
||||
"NSPhotoLibraryUsageDescription" = "사진을 선택하려면 사진 라이브러리 접근이 필요합니다.";
|
||||
"NSMicrophoneUsageDescription" = "음성 녹음을 위해 마이크 접근이 필요합니다.";
|
||||
"NSUserTrackingUsageDescription" = "맞춤형 광고 제공을 위해 추적을 허용해 주세요.";
|
||||
"NSCameraUsageDescription" = "사진을 촬영하려면 카메라 접근이 필요합니다.";
|
||||
@@ -4,6 +4,10 @@
|
||||
<dict>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.applesignin</key>
|
||||
<array>
|
||||
<string>Default</string>
|
||||
</array>
|
||||
<key>com.apple.developer.associated-domains</key>
|
||||
<array>
|
||||
<string>applinks:voiceon.onelink.me</string>
|
||||
|
||||
@@ -14,6 +14,7 @@ import FBSDKCoreKit
|
||||
import FirebaseCore
|
||||
import FirebaseAnalytics
|
||||
import FirebaseMessaging
|
||||
import LineSDK
|
||||
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
@@ -24,6 +25,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
FirebaseApp.configure()
|
||||
LoginManager.shared.setup(channelID: LINE_CHANNEL_ID, universalLinkURL: nil)
|
||||
Notifly.initialize(projectId: NOTIFLY_PROJECT_ID, username: NOTIFLY_USERNAME, password: NOTIFLY_PASSWORD)
|
||||
Messaging.messaging().delegate = self
|
||||
setupAppsFlyer()
|
||||
@@ -75,6 +77,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([any UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
_ = LoginManager.shared.application(application, open: userActivity.webpageURL)
|
||||
AppsFlyerLib.shared().continue(userActivity, restorationHandler: nil)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class AppState: ObservableObject {
|
||||
@Published var purchasedContentId = 0
|
||||
@Published var purchasedContentOrderType = OrderType.KEEP
|
||||
|
||||
@Published var isChangeAdultContentVisible = false
|
||||
@Published var isRestartApp = false
|
||||
@Published var startTab: HomeViewModel.CurrentTab = .home
|
||||
|
||||
@Published var marketingUtmSource = ""
|
||||
@@ -74,4 +74,13 @@ class AppState: ObservableObject {
|
||||
self.appStep = .main
|
||||
}
|
||||
}
|
||||
|
||||
// 언어 적용 직후 앱을 소프트 재시작(스플래시 -> 메인)하여 전역 UI를 새로고침
|
||||
func softRestart() {
|
||||
isRestartApp = true
|
||||
setAppStep(step: .splash)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.setAppStep(step: .main)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ enum AppStep {
|
||||
|
||||
case settings
|
||||
|
||||
case languageSettings
|
||||
|
||||
case notices
|
||||
|
||||
case noticeDetail(notice: NoticeItem)
|
||||
@@ -128,7 +130,7 @@ enum AppStep {
|
||||
|
||||
case seriesDetail(seriesId: Int)
|
||||
|
||||
case seriesAll(creatorId: Int)
|
||||
case seriesAll(creatorId: Int? = nil, creatorNickname: String? = nil, isOriginal: Bool = false, isCompleted: Bool = false)
|
||||
|
||||
case seriesContentAll(seriesId: Int, seriesTitle: String)
|
||||
|
||||
@@ -169,4 +171,8 @@ enum AppStep {
|
||||
case newCharacterAll
|
||||
|
||||
case originalWorkDetail(originalId: Int)
|
||||
|
||||
case contentAll(isFree: Bool = false, isPointOnly: Bool = false)
|
||||
|
||||
case seriesMain
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ import Kingfisher
|
||||
|
||||
import FBSDKCoreKit
|
||||
import AppsFlyerLib
|
||||
import GoogleSignIn
|
||||
import KakaoSDKCommon
|
||||
import KakaoSDKAuth
|
||||
import LineSDK
|
||||
|
||||
@main
|
||||
struct SodaLiveApp: App {
|
||||
@@ -19,9 +23,13 @@ struct SodaLiveApp: App {
|
||||
@ObservedObject var viewModel = AppViewModel()
|
||||
|
||||
@StateObject var canPgPaymentViewModel = CanPgPaymentViewModel()
|
||||
@StateObject private var languageEnvironment = LanguageContainer.environment
|
||||
|
||||
init() {
|
||||
configureImageCache()
|
||||
// 앱 시작 직후, 초기 네트워크 요청도 올바른 언어 헤더를 갖도록 동기 초기화
|
||||
LanguageHeaderProvider.initialize()
|
||||
KakaoSDK.initSDK(appKey: KAKAO_APP_KEY)
|
||||
}
|
||||
|
||||
private func configureImageCache() {
|
||||
@@ -40,6 +48,10 @@ struct SodaLiveApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(canPgPaymentViewModel: canPgPaymentViewModel)
|
||||
.environment(\.locale, languageEnvironment.locale)
|
||||
.task {
|
||||
await LanguageContainer.service.bootstrap()
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
|
||||
CreatorCommunityMediaPlayerManager.shared.pauseContent()
|
||||
// 백그라운드 전환 시 메모리 캐시 정리
|
||||
@@ -60,6 +72,11 @@ struct SodaLiveApp: App {
|
||||
}
|
||||
.onOpenURL { url in
|
||||
DEBUG_LOG("I have received a URL through a custom scheme! \(url.absoluteString)")
|
||||
|
||||
if KakaoSDKAuth.AuthApi.isKakaoTalkLoginUrl(url) {
|
||||
_ = AuthController.handleOpenUrl(url: url)
|
||||
return
|
||||
}
|
||||
|
||||
if let comps = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||
url.scheme?.lowercased() == APPSCHEME.lowercased(),
|
||||
@@ -67,8 +84,10 @@ struct SodaLiveApp: App {
|
||||
comps.path.lowercased() == "/result" {
|
||||
canPgPaymentViewModel.handleVerifyOpenURL(url)
|
||||
} else {
|
||||
_ = LoginManager.shared.application(UIApplication.shared, open: url, options: [:])
|
||||
ApplicationDelegate.shared.application(UIApplication.shared, open: url, options: [:])
|
||||
AppsFlyerLib.shared().handleOpen(url)
|
||||
GIDSignIn.sharedInstance.handle(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ struct ApplyMethodView: View {
|
||||
}
|
||||
|
||||
Text("오디션 지원방식")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.graybb)
|
||||
.padding(.top, 33.3)
|
||||
|
||||
@@ -40,7 +40,7 @@ struct ApplyMethodView: View {
|
||||
Image("ic_upload")
|
||||
|
||||
Text("파일 업로드")
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
@@ -61,7 +61,7 @@ struct ApplyMethodView: View {
|
||||
Image("ic_mic_color_button")
|
||||
|
||||
Text("바로 녹음")
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.appFont(size: 14.7, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
@@ -82,7 +82,7 @@ struct ApplyMethodView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("※ 파일은 mp3, aac만 업로드 가능")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ struct AuditionApplicantItemView: View {
|
||||
VStack(spacing: 8) {
|
||||
HStack(spacing: 0) {
|
||||
Text(item.nickname.count > 9 ? "\(item.nickname.prefix(9))..." : item.nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .creatorDetail(userId: item.memberId))
|
||||
@@ -53,7 +53,7 @@ struct AuditionApplicantItemView: View {
|
||||
|
||||
if soundManager.applicantId == item.applicantId {
|
||||
Text("\(secondsToMinutesSeconds(Int(soundManager.currentTime)))/\(secondsToMinutesSeconds(Int(soundManager.duration)))")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,7 @@ struct AuditionApplicantItemView: View {
|
||||
Image("ic_heart_vote")
|
||||
|
||||
Text("\(item.voteCount)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
.onTapGesture {
|
||||
|
||||
@@ -31,7 +31,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션 녹음")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
@@ -46,7 +46,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
}
|
||||
|
||||
Text(soundManager.timeString)
|
||||
.font(.custom(Font.bold.rawValue, size: 33.3))
|
||||
.appFont(size: 33.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.top, 80)
|
||||
|
||||
@@ -77,7 +77,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
Spacer()
|
||||
|
||||
Text("삭제")
|
||||
.font(.custom(Font.medium.rawValue, size: 15.3))
|
||||
.appFont(size: 15.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb.opacity(0))
|
||||
|
||||
Spacer()
|
||||
@@ -100,7 +100,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
Spacer()
|
||||
|
||||
Text("삭제")
|
||||
.font(.custom(Font.medium.rawValue, size: 15.3))
|
||||
.appFont(size: 15.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
.onTapGesture {
|
||||
soundManager.stopAudio()
|
||||
@@ -114,7 +114,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Text("다시 녹음")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.frame(width: (proxy.size.width - 40) / 3, height: 50)
|
||||
.background(Color.button.opacity(0.2))
|
||||
@@ -130,7 +130,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
}
|
||||
|
||||
Text("녹음완료")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: (proxy.size.width - 40) * 2 / 3, height: 50)
|
||||
.background(Color.button)
|
||||
@@ -178,7 +178,7 @@ struct AuditionApplicantRecordingView: View {
|
||||
.padding(.vertical, 13.3)
|
||||
.padding(.horizontal, 6.7)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -34,7 +34,7 @@ struct AuditionApplyView: View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션 지원")
|
||||
.font(.custom(Font.medium.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
@@ -46,7 +46,7 @@ struct AuditionApplyView: View {
|
||||
}
|
||||
|
||||
Text("녹음파일")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.top, 20)
|
||||
|
||||
@@ -54,7 +54,7 @@ struct AuditionApplyView: View {
|
||||
Image("ic_note_square")
|
||||
|
||||
Text(filename)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.grayd2)
|
||||
|
||||
Spacer()
|
||||
@@ -67,7 +67,7 @@ struct AuditionApplyView: View {
|
||||
.padding(.top, 10)
|
||||
|
||||
Text("연락처")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.appFont(size: 16.7, weight: .bold)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
@@ -75,7 +75,7 @@ struct AuditionApplyView: View {
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.keyboardType(.decimalPad)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.grayee)
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.vertical, 17)
|
||||
@@ -90,7 +90,7 @@ struct AuditionApplyView: View {
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("보이스온 오디오 드라마 오디션 합격시 개인 연락을 위한 개인 정보(연락처) 수집 및 활용에 동의합니다.\n오디션 지원자는 개인정보 수집 및 활용 동의에 거부할 권리가 있으며 비동의시 오디션 지원은 취소 됩니다.")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.lineSpacing(3)
|
||||
}
|
||||
@@ -101,7 +101,7 @@ struct AuditionApplyView: View {
|
||||
}
|
||||
|
||||
Text("오디션 지원하기")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -137,7 +137,7 @@ struct AuditionApplyView: View {
|
||||
Text(errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -31,7 +31,7 @@ struct AuditionItemView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Text(item.title)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
@@ -25,7 +25,7 @@ struct AuditionView: View {
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("오디션")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ struct AuditionView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("보이스온 오디션 이용방법")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("자세히>")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
@@ -74,17 +74,17 @@ struct AuditionView: View {
|
||||
VStack(alignment: .leading, spacing: 25) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(" ON")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.mainRed)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("총 \(viewModel.inProgressCount)개")
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.appFont(size: 11.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
}
|
||||
|
||||
@@ -112,17 +112,17 @@ struct AuditionView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("오디션")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(" OFF")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.appFont(size: 18.3, weight: .bold)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("총 \(viewModel.completedCount)개")
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.appFont(size: 11.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
}
|
||||
.padding(.top, 30)
|
||||
|
||||
@@ -31,7 +31,7 @@ struct AuditionDetailView: View {
|
||||
.cornerRadius(6.7)
|
||||
|
||||
Text("오디션 정보")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
@@ -39,7 +39,7 @@ struct AuditionDetailView: View {
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("오디션 캐릭터")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
|
||||
@@ -74,7 +74,7 @@ struct AuditionDetailView: View {
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
@@ -29,7 +29,7 @@ struct AuditionDetailRoleItemView: View {
|
||||
)
|
||||
|
||||
Text(item.isComplete ? "모집완료" : "모집중")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(.horizontal, 9)
|
||||
.padding(.vertical, 3)
|
||||
@@ -43,7 +43,7 @@ struct AuditionDetailRoleItemView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Text(item.name)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
@@ -46,7 +46,7 @@ struct AuditionRoleDetailView: View {
|
||||
HStack(spacing: 14) {
|
||||
if let url = URL(string: roleDetail.originalWorkUrl), UIApplication.shared.canOpenURL(url) {
|
||||
Text("원작 보러가기")
|
||||
.font(.custom(Font.bold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -60,7 +60,7 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if let url = URL(string: roleDetail.auditionScriptUrl), UIApplication.shared.canOpenURL(url) {
|
||||
Text("오디션 대본 확인")
|
||||
.font(.custom(Font.bold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.vertical, 12)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -75,7 +75,7 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
Text("오디션 캐릭터 정보")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.appFont(size: 14.7, weight: .bold)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
ExpandableTextView(text: roleDetail.information)
|
||||
@@ -84,28 +84,28 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if viewModel.applicantList.isEmpty {
|
||||
Text("지원자가 없습니다.")
|
||||
.font(.custom(Font.medium.rawValue, size: 13))
|
||||
.appFont(size: 13, weight: .medium)
|
||||
.foregroundColor(Color.grayee)
|
||||
.padding(.top, 15)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
Text("참여자")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Text("\(viewModel.totalCount)")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.button)
|
||||
.padding(.leading, 2.3)
|
||||
|
||||
Text("명")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(Color.graybb)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
viewModel.sortType == .NEWEST ? Color.button : Color.graybb
|
||||
)
|
||||
@@ -114,7 +114,7 @@ struct AuditionRoleDetailView: View {
|
||||
}
|
||||
|
||||
Text("좋아요순")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.appFont(size: 13.3, weight: .medium)
|
||||
.foregroundColor(
|
||||
viewModel.sortType == .LIKES ? Color.button : Color.graybb
|
||||
)
|
||||
@@ -156,7 +156,7 @@ struct AuditionRoleDetailView: View {
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -171,7 +171,7 @@ struct AuditionRoleDetailView: View {
|
||||
Text(soundManager.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -190,7 +190,7 @@ struct AuditionRoleDetailView: View {
|
||||
|
||||
if let roleDetail = viewModel.auditionRoleDetail {
|
||||
Text(roleDetail.isAlreadyApplicant ? "오디션 재지원" : "오디션 지원")
|
||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||
.appFont(size: 15.3, weight: .bold)
|
||||
.foregroundColor(Color.white)
|
||||
.padding(14)
|
||||
.background(Color.button)
|
||||
|
||||
@@ -79,11 +79,10 @@ private struct AutoSlideCharacterBannerPage: View {
|
||||
let width: CGFloat
|
||||
let height: CGFloat
|
||||
let onTap: (CharacterBannerResponse) -> Void
|
||||
@State private var boundURL: URL?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if let boundURL {
|
||||
if let boundURL = URL(string: item.imageUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? item.imageUrl){
|
||||
DownsampledKFImage(
|
||||
url: boundURL,
|
||||
size: CGSize(width: width, height: height)
|
||||
@@ -96,14 +95,5 @@ private struct AutoSlideCharacterBannerPage: View {
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { onTap(item) }
|
||||
.onAppear {
|
||||
let encoded = item.imageUrl.addingPercentEncoding(
|
||||
withAllowedCharacters: .urlQueryAllowed
|
||||
) ?? item.imageUrl
|
||||
boundURL = URL(string: encoded)
|
||||
}
|
||||
.onDisappear {
|
||||
boundURL = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ struct Character: Decodable {
|
||||
let name: String
|
||||
let description: String?
|
||||
let imageUrl: String
|
||||
let isNew: Bool
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ enum CharacterApi {
|
||||
case getMyCharacterImageList(characterId: Int64, page: Int, size: Int)
|
||||
case purchaseCharacterImage(imageId: Int)
|
||||
case getRecentCharacters(page: Int, size: Int)
|
||||
case refreshRecommendCharacters
|
||||
}
|
||||
|
||||
extension CharacterApi: TargetType {
|
||||
@@ -39,6 +40,9 @@ extension CharacterApi: TargetType {
|
||||
|
||||
case .getRecentCharacters:
|
||||
return "/api/chat/character/recent"
|
||||
|
||||
case .refreshRecommendCharacters:
|
||||
return "/api/chat/character/recommend"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +58,7 @@ extension CharacterApi: TargetType {
|
||||
|
||||
var task: Moya.Task {
|
||||
switch self {
|
||||
case .getCharacterHome, .getCharacterDetail:
|
||||
case .getCharacterHome, .getCharacterDetail, .refreshRecommendCharacters:
|
||||
return .requestPlain
|
||||
|
||||
case .getRecentCharacters(let page, let size):
|
||||
|
||||
@@ -10,5 +10,5 @@ struct CharacterHomeResponse: Decodable {
|
||||
let recentCharacters: [RecentCharacter]
|
||||
let popularCharacters: [Character]
|
||||
let newCharacters: [Character]
|
||||
let curationSections: [CurationSection]
|
||||
let recommendCharacters: [Character]
|
||||
}
|
||||
|
||||
@@ -21,31 +21,50 @@ struct CharacterItemView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
ZStack(alignment: .bottomLeading) {
|
||||
ZStack(alignment: .leading) {
|
||||
DownsampledKFImage(
|
||||
url: URL(string: character.imageUrl),
|
||||
size: CGSize(width: size, height: size)
|
||||
)
|
||||
.cornerRadius(12)
|
||||
|
||||
if isShowRank {
|
||||
Text("\(rank)")
|
||||
.font(.custom(Font.preBold.rawValue, size: 72))
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.frame(height: capHeight)
|
||||
VStack {
|
||||
if character.isNew {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Text("N")
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 30, height: 30)
|
||||
.background(Color.button)
|
||||
.clipShape(Circle())
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if isShowRank {
|
||||
Text("\(rank)")
|
||||
.appFont(size: 72, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.frame(height: capHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(character.name)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
if let desc = character.description, !desc.isEmpty {
|
||||
Text(desc)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
.lineLimit(1)
|
||||
}
|
||||
@@ -56,7 +75,7 @@ struct CharacterItemView: View {
|
||||
|
||||
#Preview {
|
||||
CharacterItemView(
|
||||
character: Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300"),
|
||||
character: Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300", isNew: true),
|
||||
size: 168,
|
||||
rank: 20,
|
||||
isShowRank: true
|
||||
|
||||
@@ -16,4 +16,8 @@ class CharacterRepository {
|
||||
func getCharacterMain() -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getCharacterHome)
|
||||
}
|
||||
|
||||
func refreshRecommendCharacters() -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.refreshRecommendCharacters)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CharacterSectionView: View {
|
||||
let title: String
|
||||
let title: LocalizedStringResource
|
||||
let items: [Character]
|
||||
let isShowRank: Bool
|
||||
var trailingTitle: String? = nil
|
||||
@@ -19,12 +19,12 @@ struct CharacterSectionView: View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(spacing: 0) {
|
||||
Text(title)
|
||||
.font(.custom(Font.preBold.rawValue, size: 20))
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
Spacer()
|
||||
if let trailingTitle = trailingTitle {
|
||||
Text(trailingTitle)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "90A4AE"))
|
||||
.onTapGesture { onTapTrailing?() }
|
||||
}
|
||||
@@ -54,8 +54,8 @@ struct CharacterSectionView: View {
|
||||
CharacterSectionView(
|
||||
title: "신규 캐릭터",
|
||||
items: [
|
||||
Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300"),
|
||||
Character(characterId: 2, name: "데이지", description: "", imageUrl: "https://picsum.photos/300")
|
||||
Character(characterId: 1, name: "찰리", description: "새로운 친구", imageUrl: "https://picsum.photos/300", isNew: true),
|
||||
Character(characterId: 2, name: "데이지", description: "", imageUrl: "https://picsum.photos/300", isNew: false)
|
||||
],
|
||||
isShowRank: true
|
||||
)
|
||||
|
||||
@@ -54,7 +54,7 @@ struct CharacterView: View {
|
||||
title: "신규 캐릭터",
|
||||
items: viewModel.newCharacters,
|
||||
isShowRank: false,
|
||||
trailingTitle: "전체보기",
|
||||
trailingTitle: I18n.Common.viewAll,
|
||||
onTapTrailing: {
|
||||
onSelectNewCharacterAll()
|
||||
},
|
||||
@@ -64,20 +64,50 @@ struct CharacterView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// 큐레이션 섹션 (여러 섹션)
|
||||
if !viewModel.curations.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 48) {
|
||||
ForEach(viewModel.curations.indices, id: \.self) { idx in
|
||||
let section = viewModel.curations[idx]
|
||||
CharacterSectionView(
|
||||
title: section.title,
|
||||
items: section.characters,
|
||||
isShowRank: false,
|
||||
onTap: { ch in
|
||||
onSelectCharacter(ch.characterId)
|
||||
if !viewModel.recommendCharacters.isEmpty {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack {
|
||||
Text("추천 캐릭터")
|
||||
.appFont(size: 24, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("ic_refresh")
|
||||
.onTapGesture {
|
||||
viewModel.refreshRecommendCharacters()
|
||||
}
|
||||
)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
let horizontalPadding: CGFloat = 24
|
||||
let gridSpacing: CGFloat = 16
|
||||
let width = (screenSize().width - (horizontalPadding * 2) - gridSpacing) / 2
|
||||
|
||||
LazyVGrid(
|
||||
columns: Array(
|
||||
repeating: GridItem(
|
||||
.flexible(),
|
||||
spacing: gridSpacing,
|
||||
alignment: .topLeading
|
||||
),
|
||||
count: 2
|
||||
),
|
||||
alignment: .leading,
|
||||
spacing: gridSpacing
|
||||
) {
|
||||
ForEach(viewModel.recommendCharacters.indices, id: \.self) { idx in
|
||||
let character = viewModel.recommendCharacters[idx]
|
||||
CharacterItemView(
|
||||
character: character,
|
||||
size: width,
|
||||
rank: idx + 1,
|
||||
isShowRank: false
|
||||
)
|
||||
.onTapGesture { onSelectCharacter(character.characterId) }
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +127,7 @@ struct CharacterView: View {
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -15,7 +15,7 @@ final class CharacterViewModel: ObservableObject {
|
||||
@Published private(set) var recentCharacters: [RecentCharacter] = []
|
||||
@Published private(set) var popularCharacters: [Character] = []
|
||||
@Published private(set) var newCharacters: [Character] = []
|
||||
@Published private(set) var curations: [CurationSection] = []
|
||||
@Published private(set) var recommendCharacters: [Character] = []
|
||||
|
||||
@Published var isLoading: Bool = false
|
||||
@Published var errorMessage: String = ""
|
||||
@@ -49,7 +49,46 @@ final class CharacterViewModel: ObservableObject {
|
||||
self.recentCharacters = data.recentCharacters
|
||||
self.popularCharacters = data.popularCharacters
|
||||
self.newCharacters = data.newCharacters
|
||||
self.curations = data.curationSections.filter { !$0.characters.isEmpty }
|
||||
self.recommendCharacters = data.recommendCharacters
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
self.isLoading = false
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func refreshRecommendCharacters() {
|
||||
isLoading = true
|
||||
|
||||
repository.refreshRecommendCharacters()
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { response in
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<[Character]>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self.recommendCharacters = data
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//
|
||||
// CurationSection.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 8/29/25.
|
||||
//
|
||||
|
||||
struct CurationSection: Decodable {
|
||||
let characterCurationId: Int
|
||||
let title: String
|
||||
let characters: [Character]
|
||||
}
|
||||
@@ -22,6 +22,7 @@ struct CharacterDetailResponse: Decodable {
|
||||
let others: [OtherCharacter]
|
||||
let latestComment: CharacterCommentResponse?
|
||||
let totalComments: Int
|
||||
let translated: TranslatedAiCharacterDetail?
|
||||
}
|
||||
|
||||
enum CharacterType: String, Decodable {
|
||||
@@ -44,3 +45,22 @@ struct CharacterBackgroundResponse: Decodable {
|
||||
let topic: String
|
||||
let description: String
|
||||
}
|
||||
|
||||
struct TranslatedAiCharacterDetail: Decodable {
|
||||
let name: String?
|
||||
let description: String?
|
||||
let gender: String?
|
||||
let personality: TranslatedAiCharacterPersonality?
|
||||
let background: TranslatedAiCharacterBackground?
|
||||
let tags: String?
|
||||
}
|
||||
|
||||
struct TranslatedAiCharacterPersonality: Decodable {
|
||||
let trait: String?
|
||||
let description: String?
|
||||
}
|
||||
|
||||
struct TranslatedAiCharacterBackground: Decodable {
|
||||
let topic: String?
|
||||
let description: String?
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ struct CharacterDetailView: View {
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .detail: return "상세"
|
||||
case .gallery: return "갤러리"
|
||||
case .detail: return I18n.Tab.detail
|
||||
case .gallery: return I18n.Tab.gallery
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ struct CharacterDetailView: View {
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "캐릭터 정보") {
|
||||
DetailNavigationBar(title: String(localized: "캐릭터 정보")) {
|
||||
if presentationMode.wrappedValue.isPresented {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} else {
|
||||
@@ -59,7 +59,7 @@ struct CharacterDetailView: View {
|
||||
|
||||
// 세계관 및 작품 소개
|
||||
if let backgrounds = viewModel.characterDetail?.backgrounds {
|
||||
worldViewSection(backgrounds: backgrounds)
|
||||
worldViewSection(backgrounds: viewModel.characterDetail?.translated?.background?.description ?? backgrounds.description)
|
||||
}
|
||||
|
||||
// 원작 섹션
|
||||
@@ -70,7 +70,7 @@ struct CharacterDetailView: View {
|
||||
|
||||
// 성격 및 특징 섹션
|
||||
if let personalities = viewModel.characterDetail?.personalities {
|
||||
personalitySection(personalities: personalities)
|
||||
personalitySection(personalities: viewModel.characterDetail?.translated?.personality?.description ?? personalities.description)
|
||||
}
|
||||
|
||||
// 장르의 다른 캐릭터 섹션
|
||||
@@ -78,7 +78,7 @@ struct CharacterDetailView: View {
|
||||
VStack(spacing: 16) {
|
||||
HStack {
|
||||
Text("장르의 다른 캐릭터")
|
||||
.font(.custom(Font.preBold.rawValue, size: 26))
|
||||
.appFont(size: 26, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
@@ -93,7 +93,8 @@ struct CharacterDetailView: View {
|
||||
characterId: otherCharacter.characterId,
|
||||
name: otherCharacter.name,
|
||||
description: otherCharacter.tags,
|
||||
imageUrl: otherCharacter.imageUrl
|
||||
imageUrl: otherCharacter.imageUrl,
|
||||
isNew: false
|
||||
),
|
||||
size: screenSize().width * 0.42,
|
||||
rank: 0,
|
||||
@@ -140,7 +141,7 @@ struct CharacterDetailView: View {
|
||||
.frame(alignment: .center)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 33.3)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -203,8 +204,8 @@ extension CharacterDetailView {
|
||||
{
|
||||
HStack(spacing: 4) {
|
||||
if let gender = viewModel.characterDetail?.gender {
|
||||
Text(gender)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
Text(viewModel.characterDetail?.translated?.gender ?? gender)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(
|
||||
gender == "남성" ?
|
||||
Color.button :
|
||||
@@ -227,7 +228,7 @@ extension CharacterDetailView {
|
||||
|
||||
if let age = viewModel.characterDetail?.age {
|
||||
Text("\(age)세")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
@@ -243,7 +244,7 @@ extension CharacterDetailView {
|
||||
|
||||
if let mbti = viewModel.characterDetail?.mbti {
|
||||
Text(mbti)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
@@ -261,8 +262,8 @@ extension CharacterDetailView {
|
||||
|
||||
// 이름과 상태
|
||||
HStack(spacing: 8) {
|
||||
Text(viewModel.characterDetail?.name ?? "")
|
||||
.font(.custom(Font.preBold.rawValue, size: 26))
|
||||
Text(viewModel.characterDetail?.translated?.name ?? viewModel.characterDetail?.name ?? "")
|
||||
.appFont(size: 26, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
@@ -270,7 +271,7 @@ extension CharacterDetailView {
|
||||
if let characterType = viewModel.characterDetail?.characterType {
|
||||
HStack(spacing: 8) {
|
||||
Text(characterType.rawValue)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 5)
|
||||
.padding(.vertical, 1)
|
||||
@@ -281,12 +282,12 @@ extension CharacterDetailView {
|
||||
}
|
||||
|
||||
// 설명
|
||||
Text(viewModel.characterDetail?.description ?? "")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 18))
|
||||
Text(viewModel.characterDetail?.translated?.description ?? viewModel.characterDetail?.description ?? "")
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
Text(viewModel.characterDetail?.tags ?? "")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
Text(viewModel.characterDetail?.translated?.tags ?? viewModel.characterDetail?.tags ?? "")
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "3BB9F1"))
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
@@ -296,17 +297,17 @@ extension CharacterDetailView {
|
||||
|
||||
// MARK: - World View Section
|
||||
extension CharacterDetailView {
|
||||
private func worldViewSection(backgrounds: CharacterBackgroundResponse) -> some View {
|
||||
private func worldViewSection(backgrounds: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("[세계관 및 작품 소개]")
|
||||
.font(.custom(Font.preBold.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
CharacterExpandableTextView(text: backgrounds.description)
|
||||
CharacterExpandableTextView(text: backgrounds)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
@@ -318,7 +319,7 @@ extension CharacterDetailView {
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Text("원작")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
@@ -327,7 +328,7 @@ extension CharacterDetailView {
|
||||
|
||||
HStack {
|
||||
Text(title)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
Spacer()
|
||||
@@ -339,7 +340,7 @@ extension CharacterDetailView {
|
||||
}
|
||||
}) {
|
||||
Text("원작 보러가기")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(Color(hex: "3BB9F1"))
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -356,23 +357,23 @@ extension CharacterDetailView {
|
||||
|
||||
// MARK: - Personality Section
|
||||
extension CharacterDetailView {
|
||||
private func personalitySection(personalities: CharacterPersonalityResponse) -> some View {
|
||||
private func personalitySection(personalities: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack {
|
||||
Text("[성격 및 특징]")
|
||||
.font(.custom(Font.preBold.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
CharacterExpandableTextView(text: personalities.description)
|
||||
CharacterExpandableTextView(text: personalities)
|
||||
|
||||
// 캐릭터톡 대화 가이드
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack {
|
||||
Text("⚠️ 캐릭터톡 대화 가이드")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
Spacer()
|
||||
@@ -381,7 +382,7 @@ extension CharacterDetailView {
|
||||
Text("""
|
||||
보이스온의 오픈월드 캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다. 세계관 속 연관 캐릭터가 되어 대화를 하거나 완전히 새로운 인물이 되어 캐릭터와 당신만의 스토리를 만들어 갈 수 있습니다.
|
||||
""")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "AEAEB2"))
|
||||
.multilineTextAlignment(.leading)
|
||||
|
||||
@@ -389,7 +390,7 @@ extension CharacterDetailView {
|
||||
오픈월드 캐릭터톡은 캐릭터를 정교하게 설계하였지만, 대화가 어색하거나 불완전할 수도 있습니다.
|
||||
대화 도중 캐릭터의 대화가 이상하거나 새로운 캐릭터로 대화를 나누고 싶다면 대화를 초기화 하고 새롭게 캐릭터와 대화를 나눠보세요.
|
||||
""")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "AEAEB2"))
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
@@ -411,7 +412,7 @@ extension CharacterDetailView {
|
||||
extension CharacterDetailView {
|
||||
private var chatButton: some View {
|
||||
Text("대화하기")
|
||||
.font(.custom(Font.preBold.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 54)
|
||||
@@ -442,7 +443,7 @@ struct CharacterExpandableTextView: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(text)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.lineLimit(isExpanded ? nil : 3)
|
||||
.multilineTextAlignment(.leading)
|
||||
@@ -463,12 +464,12 @@ struct CharacterExpandableTextView: View {
|
||||
Spacer()
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 16))
|
||||
.appFont(size: 16)
|
||||
.foregroundColor(Color(hex: "607D8B"))
|
||||
.rotationEffect(.degrees(isExpanded ? 180 : 0))
|
||||
|
||||
Text(isExpanded ? "간략히" : "더보기")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "607D8B"))
|
||||
}
|
||||
.onTapGesture {
|
||||
|
||||
@@ -54,14 +54,14 @@ final class CharacterDetailViewModel: 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
|
||||
}
|
||||
}
|
||||
@@ -94,14 +94,14 @@ final class CharacterDetailViewModel: 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ struct CharacterDetailGalleryView: View {
|
||||
.frame(alignment: .center)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 33.3)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -78,11 +78,11 @@ struct CharacterDetailGalleryView: View {
|
||||
.overlay {
|
||||
if viewModel.isShowPurchaseDialog {
|
||||
SodaDialog(
|
||||
title: "구매 확인",
|
||||
desc: "선택한 이미지를 구매하시겠습니까?",
|
||||
title: I18n.CharacterDetailGallery.purchaseConfirmTitle,
|
||||
desc: I18n.CharacterDetailGallery.purchaseConfirmDescription,
|
||||
confirmButtonTitle: viewModel.selectedItemPrice,
|
||||
confirmButtonAction: viewModel.onPurchaseConfirm,
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonTitle: I18n.Common.cancel,
|
||||
cancelButtonAction: viewModel.onPurchaseCancel
|
||||
)
|
||||
}
|
||||
@@ -95,22 +95,22 @@ struct CharacterDetailGalleryView: View {
|
||||
// 상단 정보 (계산된 % 보유중, 정보 아이콘, 개수)
|
||||
HStack {
|
||||
Text("\(viewModel.ownershipPercentage)% 보유중")
|
||||
.font(.custom(Font.preBold.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text("\(viewModel.ownedCount)")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(Color(hex: "#FDD453"))
|
||||
|
||||
Text("/")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("\(viewModel.totalCount)개")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ struct CharacterDetailGalleryView: View {
|
||||
.frame(width: 16)
|
||||
|
||||
Text("\(item.imagePriceCan)")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "#263238"))
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
@@ -44,7 +44,7 @@ final class CharacterDetailGalleryViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
var selectedItemPrice: String {
|
||||
return "\(selectedItem?.imagePriceCan ?? 0)캔으로 구매"
|
||||
return I18n.CharacterDetailGallery.purchaseWithCans(selectedItem?.imagePriceCan ?? 0)
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
@@ -112,7 +112,7 @@ final class CharacterDetailGalleryViewModel: ObservableObject {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
@@ -124,7 +124,7 @@ final class CharacterDetailGalleryViewModel: ObservableObject {
|
||||
self.selectedImageIndex = 0
|
||||
self.selectedItem = nil
|
||||
ERROR_LOG(String(describing: error))
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
@@ -172,14 +172,14 @@ final class CharacterDetailGalleryViewModel: 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
|
||||
}
|
||||
}
|
||||
@@ -219,14 +219,14 @@ final class CharacterDetailGalleryViewModel: 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,19 +18,19 @@ struct NewCharacterListView: View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 8) {
|
||||
// Toolbar
|
||||
DetailNavigationBar(title: "신규 캐릭터 전체보기")
|
||||
DetailNavigationBar(title: String(localized: "신규 캐릭터 전체보기"))
|
||||
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
// 전체 n개
|
||||
HStack(spacing: 0) {
|
||||
Text("전체 ")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
Text("전체")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Text("\(viewModel.totalCount)")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
Text(" \(viewModel.totalCount)")
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
Text("개")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
Spacer()
|
||||
}
|
||||
@@ -99,7 +99,7 @@ struct NewCharacterListView: View {
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -20,7 +20,7 @@ struct RecentCharacterItemView: View {
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(character.name)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: 76)
|
||||
|
||||
@@ -16,11 +16,11 @@ struct RecentCharacterSectionView: View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(spacing: 0) {
|
||||
Text("최근 대화한 캐릭터 ")
|
||||
.font(.custom(Font.preBold.rawValue, size: 20))
|
||||
.appFont(size: 20, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("\(titleCount)")
|
||||
.font(.custom(Font.preBold.rawValue, size: 20))
|
||||
.appFont(size: 20, weight: .bold)
|
||||
.foregroundColor(Color(hex: "FDCA2F"))
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -14,14 +14,18 @@ struct ChatTabView: View {
|
||||
@AppStorage("token") private var token: String = UserDefaults.string(forKey: UserDefaultsKey.token)
|
||||
@AppStorage("auth") private var auth: Bool = UserDefaults.bool(forKey: UserDefaultsKey.auth)
|
||||
|
||||
@StateObject var mypageViewModel = MyPageViewModel()
|
||||
|
||||
private enum InnerTab: Int, CaseIterable {
|
||||
case character = 0
|
||||
case original = 1
|
||||
case talk = 2
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .character: return "캐릭터"
|
||||
case .talk: return "톡"
|
||||
case .character: return I18n.Tab.character
|
||||
case .original: return I18n.Tab.work
|
||||
case .talk: return I18n.Tab.talk
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,6 +107,13 @@ struct ChatTabView: View {
|
||||
onTap: { if selectedTab != .character { selectedTab = .character } }
|
||||
)
|
||||
|
||||
ChatInnerTab(
|
||||
title: InnerTab.original.title,
|
||||
isSelected: selectedTab == .original,
|
||||
onTap: { if selectedTab != .original { selectedTab = .original } }
|
||||
)
|
||||
|
||||
|
||||
ChatInnerTab(
|
||||
title: InnerTab.talk.title,
|
||||
isSelected: selectedTab == .talk,
|
||||
@@ -115,6 +126,8 @@ struct ChatTabView: View {
|
||||
switch selectedTab {
|
||||
case .character:
|
||||
CharacterView(onSelectCharacter: handleCharacterSelection, onSelectNewCharacterAll: handleCharacterSelection)
|
||||
case .original:
|
||||
OriginalTabView()
|
||||
case .talk:
|
||||
TalkView()
|
||||
}
|
||||
@@ -142,12 +155,15 @@ struct ChatTabView: View {
|
||||
AppState.shared.isShowErrorPopup = true
|
||||
isShowAuthView = false
|
||||
}
|
||||
.onDone { _ in
|
||||
auth = true
|
||||
isShowAuthView = false
|
||||
if let action = pendingAction {
|
||||
pendingAction = nil
|
||||
action()
|
||||
.onDone {
|
||||
DEBUG_LOG("onDone: \($0)")
|
||||
mypageViewModel.authVerify($0) {
|
||||
auth = true
|
||||
isShowAuthView = false
|
||||
if let action = pendingAction {
|
||||
pendingAction = nil
|
||||
action()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onClose {
|
||||
@@ -188,12 +204,7 @@ struct ChatInnerTab: View {
|
||||
Spacer()
|
||||
|
||||
Text(title)
|
||||
.font(
|
||||
.custom(
|
||||
isSelected ? Font.preBold.rawValue : Font.preRegular.rawValue,
|
||||
size: 18
|
||||
)
|
||||
)
|
||||
.appFont(size: 18, weight: isSelected ? .bold : .regular)
|
||||
.foregroundColor(Color(hex: isSelected ? "3bb9f1" : "b0bec5"))
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
|
||||
@@ -24,14 +24,15 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
.cornerRadius(16)
|
||||
}
|
||||
|
||||
Text(item.title)
|
||||
.font(.custom(Font.preBold.rawValue, size: 26))
|
||||
// 번역 데이터가 있으면 번역값을 우선 사용
|
||||
Text(item.translated?.title ?? item.title)
|
||||
.appFont(size: 26, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.top, 40)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text(item.contentType)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
Text(item.translated?.contentType ?? item.contentType)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
@@ -43,8 +44,8 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
Text(item.category)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
Text(item.translated?.category ?? item.category)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.button)
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
@@ -58,7 +59,7 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
|
||||
if item.isAdult {
|
||||
Text("19+")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.horizontal, 7)
|
||||
.padding(.vertical, 3)
|
||||
@@ -74,11 +75,11 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
.padding(.top, 14)
|
||||
|
||||
Text(
|
||||
item.tags
|
||||
(item.translated?.tags ?? item.tags)
|
||||
.map { $0.hasPrefix("#") ? $0 : "#\($0)" }
|
||||
.joined(separator: " ")
|
||||
)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top, 14)
|
||||
@@ -101,7 +102,8 @@ struct OriginalWorkDetailHeaderView: View {
|
||||
studio: nil,
|
||||
originalLinks: [],
|
||||
tags: [],
|
||||
characters: []
|
||||
characters: [],
|
||||
translated: nil
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,4 +19,13 @@ struct OriginalWorkDetailResponse: Decodable {
|
||||
let originalLinks: [String]
|
||||
let tags: [String]
|
||||
let characters: [Character]
|
||||
let translated: TranslatedOriginalWork?
|
||||
}
|
||||
|
||||
struct TranslatedOriginalWork: Decodable {
|
||||
let title: String
|
||||
let contentType: String
|
||||
let category: String
|
||||
let description: String
|
||||
let tags: [String]
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ struct OriginalWorkDetailView: View {
|
||||
|
||||
HStack(spacing: 0) {
|
||||
SeriesDetailTabView(
|
||||
title: "캐릭터",
|
||||
title: I18n.Tab.character,
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .character
|
||||
) {
|
||||
@@ -63,7 +63,7 @@ struct OriginalWorkDetailView: View {
|
||||
}
|
||||
|
||||
SeriesDetailTabView(
|
||||
title: "작품정보",
|
||||
title: I18n.Tab.workInfo,
|
||||
width: screenSize().width / 2,
|
||||
isSelected: viewModel.currentTab == .info
|
||||
) {
|
||||
@@ -157,11 +157,11 @@ struct OriginalWorkInfoView: View {
|
||||
VStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("작품 소개")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text(response.description)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
Text(response.translated?.description ?? response.description)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
.lineLimit(isExpandDesc ? Int.max : 3)
|
||||
.truncationMode(.tail)
|
||||
@@ -176,7 +176,7 @@ struct OriginalWorkInfoView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("원작 보러 가기")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
@@ -185,7 +185,7 @@ struct OriginalWorkInfoView: View {
|
||||
let link = response.originalLinks[$0]
|
||||
|
||||
Text(link)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.onTapGesture {
|
||||
if let url = URL(string: link) {
|
||||
@@ -203,26 +203,26 @@ struct OriginalWorkInfoView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("상세 정보")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
|
||||
HStack(spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let _ = response.writer {
|
||||
Text("작가")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.studio {
|
||||
Text("제작사")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
|
||||
if let _ = response.originalWork {
|
||||
Text("원작")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "B0BEC5"))
|
||||
}
|
||||
}
|
||||
@@ -230,19 +230,19 @@ struct OriginalWorkInfoView: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let writer = response.writer {
|
||||
Text(writer)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
if let studio = response.studio {
|
||||
Text(studio)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
if let originalWork = response.originalWork {
|
||||
Text(originalWork)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.underline(response.originalLink != nil ? true : false)
|
||||
.onTapGesture {
|
||||
|
||||
@@ -21,13 +21,13 @@ struct OriginalTabItemView: View {
|
||||
).cornerRadius(16)
|
||||
|
||||
Text(item.title)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Text(item.contentType)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
@@ -76,7 +76,7 @@ struct OriginalTabView: View {
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -46,13 +46,13 @@ struct ChatRoomView: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(viewModel.characterName)
|
||||
.font(.custom(Font.preBold.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Text(viewModel.characterType.rawValue)
|
||||
.font(.custom(Font.preBold.rawValue, size: 10))
|
||||
.appFont(size: 10, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 2)
|
||||
@@ -73,7 +73,7 @@ struct ChatRoomView: View {
|
||||
.frame(width: 20, height: 20)
|
||||
|
||||
Text("\(can)")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
@@ -103,7 +103,7 @@ struct ChatRoomView: View {
|
||||
? "보이스온 AI캐릭터톡은 대화의 자유도가 높아 대화에 참여하는 당신은 누구든 될 수 있습니다.\n세계관 속 캐릭터로 대화를 하거나 새로운 인물로 캐릭터와 당신만의 스토리를 만들어보세요.\n※ AI캐릭터톡은 오픈베타 서비스 중이며, 캐릭터의 대화가 어색하거나 불완전할 수 있습니다."
|
||||
: "AI Clone은 크리에이터의 정보를 기반으로 대화하지만, 모든 정보를 완벽하게 반영하거나 실제 대화와 일치하지 않을 수 있습니다."
|
||||
)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Image(systemName: "chevron.up")
|
||||
@@ -187,12 +187,12 @@ struct ChatRoomView: View {
|
||||
ZStack(alignment: .leading) {
|
||||
if viewModel.messageText.isEmpty {
|
||||
Text("메시지를 입력하세요.")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
}
|
||||
|
||||
TextField("", text: $viewModel.messageText)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.onSubmit {
|
||||
viewModel.sendMessage()
|
||||
@@ -227,13 +227,13 @@ struct ChatRoomView: View {
|
||||
|
||||
if let message = viewModel.selectedMessage, viewModel.selectedMessageIndex >= 0 {
|
||||
SodaDialog(
|
||||
title: "잠금된 메시지",
|
||||
desc: "이 메시지를 \(message.price ?? 5)캔으로 잠금해제 하시겠습니까?",
|
||||
confirmButtonTitle: "잠금해제",
|
||||
title: I18n.ChatRoom.lockedMessageTitle,
|
||||
desc: I18n.ChatRoom.unlockMessageDescription(message.price ?? 5),
|
||||
confirmButtonTitle: I18n.ChatRoom.unlock,
|
||||
confirmButtonAction: {
|
||||
viewModel.purchaseChatMessage()
|
||||
},
|
||||
cancelButtonTitle: "취소"
|
||||
cancelButtonTitle: I18n.Common.cancel
|
||||
) {
|
||||
viewModel.selectedMessage = nil
|
||||
viewModel.selectedMessageIndex = -1
|
||||
@@ -265,14 +265,14 @@ struct ChatRoomView: View {
|
||||
Color.black.opacity(0.7)
|
||||
|
||||
SodaDialog(
|
||||
title: "대화 초기화",
|
||||
desc: "지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다.",
|
||||
confirmButtonTitle: "30캔으로 초기화",
|
||||
title: I18n.ChatRoom.resetTitle,
|
||||
desc: I18n.ChatRoom.resetDescription,
|
||||
confirmButtonTitle: I18n.ChatRoom.resetWithCans(30),
|
||||
confirmButtonAction: {
|
||||
viewModel.resetChatRoom()
|
||||
viewModel.isShowingChatResetConfirmDialog = false
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonTitle: I18n.Common.cancel,
|
||||
cancelButtonAction: {
|
||||
viewModel.isShowingChatResetConfirmDialog = false
|
||||
}
|
||||
@@ -315,7 +315,7 @@ struct ChatRoomView: View {
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .medium)
|
||||
.background(Color.button)
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
@@ -86,7 +86,7 @@ struct AiMessageItemView: View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 4) {
|
||||
Text(characterName)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ struct AiMessageItemView: View {
|
||||
.frame(width: 24, height: 24)
|
||||
|
||||
Text("\(message.price ?? 5)")
|
||||
.font(.custom(Font.preBold.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .bold)
|
||||
.foregroundColor(Color(hex: "263238"))
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
@@ -131,7 +131,7 @@ struct AiMessageItemView: View {
|
||||
}
|
||||
|
||||
Text("눌러서 잠금해제")
|
||||
.font(.custom(Font.preBold.rawValue, size: 18))
|
||||
.appFont(size: 18, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.frame(width: maxWidth, height: imageHeight)
|
||||
@@ -162,7 +162,7 @@ struct AiMessageItemView: View {
|
||||
// 시간 표시
|
||||
VStack {
|
||||
Text(formatTime(from: message.createdAt))
|
||||
.font(.custom(Font.preRegular.rawValue, size: 10))
|
||||
.appFont(size: 10, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ struct AiMessageItemView: View {
|
||||
// 첫 번째 컴포넌트는 항상 일반 텍스트
|
||||
if !component.isEmpty {
|
||||
result = result + Text(component)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
} else {
|
||||
@@ -202,13 +202,13 @@ struct AiMessageItemView: View {
|
||||
// 소괄호 뒤의 텍스트 (일반 스타일)
|
||||
if !afterClose.isEmpty {
|
||||
result = result + Text(afterClose)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
} else {
|
||||
// 닫는 괄호가 없으면 일반 텍스트로 처리
|
||||
result = result + Text("(\(component)")
|
||||
.font(.custom(Font.preRegular.rawValue, size: 16))
|
||||
.appFont(size: 16, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// MessageInputView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 9/3/25.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MessageInputView: View {
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MessageInputView()
|
||||
}
|
||||
@@ -37,7 +37,7 @@ struct TypingIndicatorItemView: View {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 4) {
|
||||
Text(characterName)
|
||||
.font(.custom(Font.preRegular.rawValue, size: 12))
|
||||
.appFont(size: 12, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
|
||||