Compare commits
64 Commits
594bcc27ca
...
115a30a7b6
Author | SHA1 | Date |
---|---|---|
![]() |
115a30a7b6 | |
![]() |
c78b804678 | |
![]() |
1a01244d85 | |
![]() |
fea557560c | |
![]() |
c440e8abd9 | |
![]() |
41d5bad46f | |
![]() |
6c8e19aed5 | |
![]() |
282ee73de1 | |
![]() |
91c43e679f | |
![]() |
02835c4b2e | |
![]() |
a4d15be57a | |
![]() |
219128ea8d | |
![]() |
47ae2ec8e1 | |
![]() |
962197d319 | |
![]() |
c75f94722b | |
![]() |
c00931761c | |
![]() |
8255065bba | |
![]() |
1e1b97e2d4 | |
![]() |
f653667df2 | |
![]() |
658cff20eb | |
![]() |
fd356451ae | |
![]() |
91cd3fe995 | |
![]() |
5f7924880e | |
![]() |
00a8f7e8ff | |
![]() |
d1b5ab31aa | |
![]() |
225efc34e4 | |
![]() |
4e607ed624 | |
![]() |
c7522aa7c9 | |
![]() |
b632a65a6f | |
![]() |
d3c5a5bfb9 | |
![]() |
2449eb14d0 | |
![]() |
4c9e78f960 | |
![]() |
6e9aaa0c8a | |
![]() |
0294bbf223 | |
![]() |
36aa167e1d | |
![]() |
8c20ce9c62 | |
![]() |
f44ef505cf | |
![]() |
9a72e21fda | |
![]() |
1c4503bda1 | |
![]() |
b1773b117d | |
![]() |
a16c38f4ab | |
![]() |
475882570a | |
![]() |
5b0cb44645 | |
![]() |
b2f0975ad1 | |
![]() |
48b1093dac | |
![]() |
3bcd2b7dba | |
![]() |
9863fc66de | |
![]() |
58868f613a | |
![]() |
1f992a11dc | |
![]() |
948b1fd2b3 | |
![]() |
5d95c0f1c9 | |
![]() |
b31933715d | |
![]() |
707b6f804c | |
![]() |
05f5a4fe82 | |
![]() |
f08d72745e | |
![]() |
4611524f8f | |
![]() |
a41c423991 | |
![]() |
d76b1c7a59 | |
![]() |
6fa183b89a | |
![]() |
371d6d538a | |
![]() |
2aa3f944c8 | |
![]() |
e68961bd0d | |
![]() |
88fcbc98f4 | |
![]() |
3916a49e60 |
21
SodaLive/Resources/Assets.xcassets/btn_audio_content_preview_play.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "btn_audio_content_preview_play.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 845 B After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 927 B After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_record.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_record_pause.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_record_pause.imageset/ic_record_pause.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_record_play.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_record_play.imageset/ic_record_play.png
vendored
Normal file
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_record_stop.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_record_stop.imageset/ic_record_stop.png
vendored
Normal file
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_save.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 443 B |
|
@ -21,5 +21,206 @@
|
|||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-1299501215847962~3447556960</string>
|
||||
<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>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -21,5 +21,206 @@
|
|||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>GADApplicationIdentifier</key>
|
||||
<string>ca-app-pub-1299501215847962~8852459715</string>
|
||||
<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>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -16,7 +16,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
FirebaseApp.configure()
|
||||
|
||||
Messaging.messaging().delegate = self
|
||||
|
||||
// For iOS 10 display notification (sent via APNS)
|
||||
|
@ -95,16 +94,26 @@ extension AppDelegate : UNUserNotificationCenterDelegate {
|
|||
Messaging.messaging().appDidReceiveMessage(userInfo)
|
||||
|
||||
let roomIdString = userInfo["room_id"] as? String
|
||||
let audioContentIdString = userInfo["audio_content_id"] as? String
|
||||
let contentIdString = userInfo["content_id"] as? String
|
||||
let channelIdString = userInfo["channel_id"] as? String
|
||||
let messageIdString = userInfo["message_id"] as? String
|
||||
|
||||
if let roomIdString = roomIdString, let roomId = Int(roomIdString), roomId > 0 {
|
||||
AppState.shared.pushRoomId = roomId
|
||||
}
|
||||
|
||||
if let audioContentIdString = audioContentIdString, let audioContentId = Int(audioContentIdString), audioContentId > 0 {
|
||||
if let contentIdString = contentIdString, let audioContentId = Int(contentIdString), audioContentId > 0 {
|
||||
AppState.shared.pushAudioContentId = audioContentId
|
||||
}
|
||||
|
||||
if let channelIdString = channelIdString, let channelId = Int(channelIdString), channelId > 0 {
|
||||
AppState.shared.pushChannelId = channelId
|
||||
}
|
||||
|
||||
if let messageIdString = messageIdString, let messageId = Int(messageIdString), messageId > 0 {
|
||||
AppState.shared.pushMessageId = messageId
|
||||
}
|
||||
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ class AppState: ObservableObject {
|
|||
|
||||
@Published var pushRoomId = 0
|
||||
@Published var pushChannelId = 0
|
||||
@Published var pushMessageId = 0
|
||||
@Published var pushAudioContentId = 0
|
||||
@Published var roomId = 0 {
|
||||
didSet {
|
||||
|
|
|
@ -107,4 +107,10 @@ enum AppStep {
|
|||
case followingList
|
||||
|
||||
case orderListAll
|
||||
|
||||
case newContentAll
|
||||
|
||||
case curationAll(title: String, curationId: Int)
|
||||
|
||||
case contentRankingAll
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ struct SodaLiveApp: App {
|
|||
let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems
|
||||
let roomId = queryItems?.filter({$0.name == "room_id"}).first?.value
|
||||
let channelId = queryItems?.filter({$0.name == "channel_id"}).first?.value
|
||||
let audioContentId = queryItems?.filter({$0.name == "audio_content_id"}).first?.value
|
||||
let messageId = queryItems?.filter({$0.name == "message_id"}).first?.value
|
||||
let audioContentId = queryItems?.filter({$0.name == "content_id"}).first?.value
|
||||
|
||||
if let roomId = roomId {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
|
@ -40,6 +41,12 @@ struct SodaLiveApp: App {
|
|||
}
|
||||
}
|
||||
|
||||
if let messageId = messageId {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
AppState.shared.pushMessageId = Int(messageId) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
if let audioContentId = audioContentId {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
AppState.shared.pushAudioContentId = Int(audioContentId) ?? 0
|
||||
|
|
|
@ -13,7 +13,7 @@ struct AddAllPlaybackTrackingRequest: Encodable {
|
|||
}
|
||||
|
||||
struct PlaybackTrackingData: Encodable {
|
||||
let audioContentId: Int
|
||||
let contentId: Int
|
||||
let playDateTime: String
|
||||
let isPreview: Bool
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// ContentNewAllItemView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ContentNewAllItemView: View {
|
||||
|
||||
let item: GetAudioContentMainItem
|
||||
|
||||
@State var width: CGFloat = 0
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: width, height: width, alignment: .top)
|
||||
.cornerRadius(2.7)
|
||||
|
||||
Text(item.title)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.frame(width: width, alignment: .leading)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.lineLimit(2)
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
KFImage(URL(string: item.creatorProfileImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 21.3, height: 21.3)
|
||||
.clipShape(Circle())
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId)) }
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
.frame(width: width)
|
||||
.onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) }
|
||||
.onAppear {
|
||||
width = (screenSize().width - 40) / 2
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// ContentNewAllView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentNewAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentNewAllViewModel()
|
||||
|
||||
let columns = [
|
||||
GridItem(.flexible(), alignment: .top),
|
||||
GridItem(.flexible(), alignment: .top)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "새로운 콘텐츠")
|
||||
|
||||
Text("※ 최근 2주간 등록된 새로운 콘텐츠 입니다.")
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.vertical, 8)
|
||||
.frame(width: screenSize().width, alignment: .leading)
|
||||
.background(Color(hex: "222222"))
|
||||
.padding(.top, 13.3)
|
||||
|
||||
ContentMainNewContentThemeView(
|
||||
themes: viewModel.themeList,
|
||||
selectTheme: {
|
||||
viewModel.selectedTheme = $0
|
||||
},
|
||||
selectedTheme: $viewModel.selectedTheme
|
||||
).padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
Text("\(viewModel.totalCount)")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 13.3)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVGrid(columns: columns, spacing: 13.3) {
|
||||
ForEach(0..<viewModel.newContentList.count, id: \.self) { index in
|
||||
ContentNewAllItemView(item: viewModel.newContentList[index])
|
||||
.onAppear {
|
||||
if index == viewModel.newContentList.count - 1 {
|
||||
viewModel.getNewContentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getThemeList()
|
||||
viewModel.getNewContentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentNewAllView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentNewAllView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// ContentNewAllViewModel.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class ContentNewAllViewModel: ObservableObject {
|
||||
|
||||
private let repository = ContentRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
@Published var isLoading = false
|
||||
|
||||
@Published var themeList = [String]()
|
||||
@Published var newContentList = [GetAudioContentMainItem]()
|
||||
|
||||
@Published var selectedTheme = "전체" {
|
||||
didSet {
|
||||
page = 1
|
||||
isLast = false
|
||||
getNewContentList()
|
||||
}
|
||||
}
|
||||
@Published var totalCount = 0
|
||||
|
||||
var page = 1
|
||||
var isLast = false
|
||||
private let pageSize = 10
|
||||
|
||||
func getNewContentList() {
|
||||
if (!isLast && !isLoading) {
|
||||
isLoading = true
|
||||
|
||||
repository.getNewContentAllOfTheme(
|
||||
theme: selectedTheme == "전체" ? "" : selectedTheme,
|
||||
page: page,
|
||||
size: pageSize
|
||||
)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<GetNewContentAllResponse>.self, from: responseData)
|
||||
self.isLoading = false
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
if page == 1 {
|
||||
newContentList.removeAll()
|
||||
}
|
||||
|
||||
self.totalCount = data.totalCount
|
||||
|
||||
if !data.items.isEmpty {
|
||||
page += 1
|
||||
self.newContentList.append(contentsOf: data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
} else {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
func getThemeList() {
|
||||
repository.getNewContentThemeList()
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<[String]>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self.themeList.append("전체")
|
||||
self.themeList.append(contentsOf: data)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
//
|
||||
// ContentRankingAllView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ContentRankingAllView: View {
|
||||
|
||||
@StateObject var viewModel = ContentRankingAllViewModel()
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "인기 콘텐츠")
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text("\(viewModel.dateString)")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.")
|
||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.background(Color(hex: "222222"))
|
||||
.padding(.top, 13.3)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 20) {
|
||||
ForEach(0..<viewModel.contentRankingItemList.count, id: \.self) { index in
|
||||
let item = viewModel.contentRankingItemList[index]
|
||||
HStack(spacing: 0) {
|
||||
KFImage(URL(string: item.coverImageUrl))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 66.7, height: 66.7, alignment: .top)
|
||||
.clipped()
|
||||
.cornerRadius(5.3)
|
||||
|
||||
Text("\(index + 1)")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 8) {
|
||||
Text(item.themeStr)
|
||||
.font(.custom(Font.medium.rawValue, size: 8))
|
||||
.foregroundColor(Color(hex: "3bac6a"))
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "28312b"))
|
||||
.cornerRadius(2.6)
|
||||
|
||||
Text(item.duration)
|
||||
.font(.custom(Font.medium.rawValue, size: 8))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(2.6)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(2.6)
|
||||
}
|
||||
|
||||
Text(item.creatorNickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Text(item.title)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.lineLimit(2)
|
||||
.padding(.top, 2.7)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if item.price > 0 {
|
||||
HStack(spacing: 8) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 17, height: 17)
|
||||
|
||||
Text("\(item.price)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "909090"))
|
||||
}
|
||||
} else {
|
||||
Text("무료")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.horizontal, 5.3)
|
||||
.padding(.vertical, 2.7)
|
||||
.background(Color(hex: "cf5c37"))
|
||||
.cornerRadius(2.6)
|
||||
}
|
||||
}
|
||||
.frame(height: 66.7)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
AppState
|
||||
.shared
|
||||
.setAppStep(step: .contentDetail(contentId: item.contentId))
|
||||
}
|
||||
.onAppear {
|
||||
if index == viewModel.contentRankingItemList.count - 1 {
|
||||
viewModel.getContentRanking()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(13.3)
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
LoadingView()
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||
GeometryReader { geo in
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.padding(.horizontal, 6.7)
|
||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.background(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.cornerRadius(20)
|
||||
.padding(.top, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.getContentRanking()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentRankingAllView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentRankingAllView()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// ContentRankingAllViewModel.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/10/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class ContentRankingAllViewModel: ObservableObject {
|
||||
|
||||
private let repository = ContentRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
@Published var isLoading = false
|
||||
|
||||
@Published var dateString = ""
|
||||
@Published var contentRankingItemList = [GetAudioContentRankingItem]()
|
||||
|
||||
var page = 1
|
||||
var isLast = false
|
||||
private let pageSize = 10
|
||||
|
||||
func getContentRanking() {
|
||||
if (!isLast && !isLoading && page <= 5) {
|
||||
isLoading = true
|
||||
|
||||
repository.getContentRanking(page: page, size: pageSize)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<GetAudioContentRanking>.self, from: responseData)
|
||||
self.isLoading = false
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
if page == 1 {
|
||||
contentRankingItemList.removeAll()
|
||||
}
|
||||
|
||||
dateString = "\(data.startDate)~\(data.endDate)"
|
||||
|
||||
if !data.items.isEmpty {
|
||||
page += 1
|
||||
self.contentRankingItemList.append(contentsOf: data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
self.isLoading = false
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// GetNewContentAllResponse.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
struct GetNewContentAllResponse: Decodable {
|
||||
let totalCount: Int
|
||||
let items: [GetAudioContentMainItem]
|
||||
}
|
|
@ -25,6 +25,11 @@ enum ContentApi {
|
|||
case getMain
|
||||
case getNewContentOfTheme(theme: String)
|
||||
case donation(request: AudioContentDonationRequest)
|
||||
case modifyComment(request: ModifyCommentRequest)
|
||||
case getNewContentThemeList
|
||||
case getNewContentAllOfTheme(theme: String, page: Int, size: Int)
|
||||
case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
|
||||
case getContentRanking(page: Int, size: Int)
|
||||
}
|
||||
|
||||
extension ContentApi: TargetType {
|
||||
|
@ -81,15 +86,32 @@ extension ContentApi: TargetType {
|
|||
|
||||
case .donation:
|
||||
return "/audio-content/donation"
|
||||
|
||||
case .modifyComment:
|
||||
return "/audio-content/comment"
|
||||
|
||||
case .getNewContentThemeList:
|
||||
return "/audio-content/main/theme"
|
||||
|
||||
case .getNewContentAllOfTheme:
|
||||
return "/audio-content/main/new/all"
|
||||
|
||||
case .getAudioContentListByCurationId(let curationId, _, _, _):
|
||||
return "/audio-content/curation/\(curationId)"
|
||||
|
||||
case .getContentRanking:
|
||||
return "/audio-content/ranking"
|
||||
}
|
||||
}
|
||||
|
||||
var method: Moya.Method {
|
||||
switch self {
|
||||
case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme:
|
||||
case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList,
|
||||
.getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme,
|
||||
.getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId, .getContentRanking:
|
||||
return .get
|
||||
|
||||
case .likeContent, .modifyAudioContent:
|
||||
case .likeContent, .modifyAudioContent, .modifyComment:
|
||||
return .put
|
||||
|
||||
case .registerComment, .orderAudioContent, .addAllPlaybackTracking, .uploadAudioContent, .donation:
|
||||
|
@ -173,6 +195,38 @@ extension ContentApi: TargetType {
|
|||
|
||||
case .donation(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .modifyComment(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .getNewContentThemeList:
|
||||
return .requestPlain
|
||||
|
||||
case .getNewContentAllOfTheme(let theme, let page, let size):
|
||||
let parameters = [
|
||||
"theme": theme,
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getAudioContentListByCurationId(_, let page, let size, let sort):
|
||||
let parameters = [
|
||||
"page": page - 1,
|
||||
"size": size,
|
||||
"sort-type": sort
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
|
||||
case .getContentRanking(let page, let size):
|
||||
let parameters = [
|
||||
"page": page - 1,
|
||||
"size": size,
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,4 +76,24 @@ final class ContentRepository {
|
|||
func donation(contentId: Int, can: Int, comment: String) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.donation(request: AudioContentDonationRequest(contentId: contentId, donationCan: can, comment: comment)))
|
||||
}
|
||||
|
||||
func modifyComment(request: ModifyCommentRequest) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.modifyComment(request: request))
|
||||
}
|
||||
|
||||
func getNewContentThemeList() -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getNewContentThemeList)
|
||||
}
|
||||
|
||||
func getNewContentAllOfTheme(theme: String, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getNewContentAllOfTheme(theme: theme, page: page, size: size))
|
||||
}
|
||||
|
||||
func getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getAudioContentListByCurationId(curationId: curationId, page: page, size: size, sort: sort))
|
||||
}
|
||||
|
||||
func getContentRanking(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getContentRanking(page: page, size: size))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,7 +221,7 @@ struct ContentCreateView: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("가격을 입력하세요(10캔 이상)", text: $viewModel.priceString)
|
||||
TextField("가격을 입력하세요(5캔 이상)", text: $viewModel.priceString)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
|
@ -253,12 +253,12 @@ struct ContentCreateView: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text("※ 대여가격은 소장가격의 70%로 자동 반영")
|
||||
Text("※ 대여가격은 소장가격의 60%로 자동 반영")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text("※ 콘텐츠의 최소금액은 10캔 입니다")
|
||||
Text("※ 콘텐츠의 최소금액은 5캔 입니다")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
@ -321,6 +321,61 @@ struct ContentCreateView: View {
|
|||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
VStack(spacing: 10) {
|
||||
Text("미리듣기 시간 설정")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text("미리듣기 시간을 직접 설정하지 않으면 콘텐츠 앞부분 30초가 자동으로 설정됩니다. 미리듣기의 시간제한은 없습니다.")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
VStack(spacing: 5.3) {
|
||||
Text("시작 시간")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
TextField("00:00:00", text: $viewModel.previewStartTime)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.bold.rawValue, size: 14.6))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(.vertical, 16.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(6.7)
|
||||
.keyboardType(.default)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
|
||||
VStack(spacing: 5.3) {
|
||||
Text("종료 시간")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
TextField("00:00:30", text: $viewModel.previewEndTime)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.bold.rawValue, size: 14.6))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(.vertical, 16.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(6.7)
|
||||
.keyboardType(.default)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
.padding(.top, 3.3)
|
||||
}
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 0) {
|
||||
Text("등록")
|
||||
|
|
|
@ -58,6 +58,9 @@ final class ContentCreateViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
@Published var previewStartTime: String = ""
|
||||
@Published var previewEndTime: String = ""
|
||||
|
||||
var placeholder = "내용을 입력하세요"
|
||||
|
||||
func uploadAudioContent() {
|
||||
|
@ -71,7 +74,9 @@ final class ContentCreateViewModel: ObservableObject {
|
|||
price: price,
|
||||
themeId: theme!.id,
|
||||
isAdult: isAdult,
|
||||
isCommentAvailable: isAvailableComment
|
||||
isCommentAvailable: isAvailableComment,
|
||||
previewStartTime: previewStartTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewStartTime : nil,
|
||||
previewEndTime: previewEndTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewEndTime : nil
|
||||
)
|
||||
|
||||
var multipartData = [MultipartFormData]()
|
||||
|
@ -205,12 +210,68 @@ final class ContentCreateViewModel: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
if !isFree && price < 10 {
|
||||
errorMessage = "콘텐츠의 최소금액은 10캔 입니다."
|
||||
if !isFree && price < 5 {
|
||||
errorMessage = "콘텐츠의 최소금액은 5캔 입니다."
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
if previewStartTime.count > 0 && previewEndTime.count > 0 {
|
||||
let startTimeArray = previewStartTime.split(separator: ":")
|
||||
if startTimeArray.count != 3 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
for time in startTimeArray {
|
||||
if time.count != 2 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let endTimeArray = previewStartTime.split(separator: ":")
|
||||
if endTimeArray.count != 3 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
|
||||
for time in endTimeArray {
|
||||
if time.count != 2 {
|
||||
errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime)
|
||||
if timeDifference < 30.0 {
|
||||
errorMessage = "미리 듣기의 최소 시간은 30초 입니다"
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if previewStartTime.count > 0 || previewEndTime.count > 0 {
|
||||
errorMessage = "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다."
|
||||
isShowPopup = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private func timeDifference(startTime: String, endTime: String) -> Double {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "HH:mm:ss"
|
||||
|
||||
if let date1 = dateFormatter.date(from: startTime), let date2 = dateFormatter.date(from: endTime) {
|
||||
return date2.timeIntervalSince(date1)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,4 +15,6 @@ struct CreateAudioContentRequest: Encodable {
|
|||
let themeId: Int
|
||||
let isAdult: Bool
|
||||
let isCommentAvailable: Bool
|
||||
let previewStartTime: String?
|
||||
let previewEndTime: String?
|
||||
}
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
//
|
||||
// ContentCurationView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentCurationView: View {
|
||||
|
||||
@StateObject var viewModel = ContentCurationViewModel()
|
||||
|
||||
let title: String
|
||||
let curationId: Int
|
||||
|
||||
let columns = [
|
||||
GridItem(.flexible(), alignment: .top),
|
||||
GridItem(.flexible(), alignment: .top)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: title)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Spacer()
|
||||
|
||||
Text("최신순")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
.opacity(viewModel.sort == .NEWEST ? 1 : 0.5)
|
||||
)
|
||||
.onTapGesture {
|
||||
if viewModel.sort != .NEWEST {
|
||||
viewModel.sort = .NEWEST
|
||||
}
|
||||
}
|
||||
|
||||
Text("높은 가격순")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
.opacity(viewModel.sort == .PRICE_HIGH ? 1 : 0.5)
|
||||
)
|
||||
.onTapGesture {
|
||||
if viewModel.sort != .PRICE_HIGH {
|
||||
viewModel.sort = .PRICE_HIGH
|
||||
}
|
||||
}
|
||||
|
||||
Text("낮은 가격순")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(
|
||||
Color(hex: "e2e2e2")
|
||||
.opacity(viewModel.sort == .PRICE_LOW ? 1 : 0.5)
|
||||
)
|
||||
.onTapGesture {
|
||||
if viewModel.sort != .PRICE_LOW {
|
||||
viewModel.sort = .PRICE_LOW
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 13.3)
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color(hex: "161616"))
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("전체")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
|
||||
Text("\(viewModel.totalCount)")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "ff5c49"))
|
||||
.padding(.leading, 8)
|
||||
|
||||
Text("개")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "e2e2e2"))
|
||||
.padding(.leading, 2)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 13.3)
|
||||
.padding(.horizontal, 20)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVGrid(columns: columns, spacing: 13.3) {
|
||||
ForEach(0..<viewModel.contentList.count, id: \.self) { index in
|
||||
ContentNewAllItemView(item: viewModel.contentList[index])
|
||||
.onAppear {
|
||||
if index == viewModel.contentList.count - 1 {
|
||||
viewModel.getContentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.curationId = curationId
|
||||
viewModel.getContentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// ContentCurationViewModel.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class ContentCurationViewModel: ObservableObject {
|
||||
|
||||
enum Sort: String {
|
||||
case NEWEST, PRICE_HIGH, PRICE_LOW
|
||||
}
|
||||
|
||||
private let repository = ContentRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
@Published var isLoading = false
|
||||
|
||||
@Published var totalCount = 0
|
||||
@Published var sort = Sort.NEWEST {
|
||||
didSet {
|
||||
page = 1
|
||||
isLast = false
|
||||
getContentList()
|
||||
}
|
||||
}
|
||||
|
||||
@Published var contentList: [GetAudioContentMainItem] = []
|
||||
|
||||
var curationId = 0
|
||||
var page = 1
|
||||
var isLast = false
|
||||
private let pageSize = 10
|
||||
|
||||
func getContentList() {
|
||||
if (!isLast && !isLoading) {
|
||||
isLoading = true
|
||||
|
||||
repository.getAudioContentListByCurationId(
|
||||
curationId: curationId,
|
||||
page: page,
|
||||
size: pageSize,
|
||||
sort: sort
|
||||
)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<GetCurationContentResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
if page == 1 {
|
||||
self.contentList.removeAll()
|
||||
}
|
||||
|
||||
if !data.items.isEmpty {
|
||||
page += 1
|
||||
self.totalCount = data.totalCount
|
||||
self.contentList.append(contentsOf: data.items)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
|
||||
self.isLoading = false
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// GetCurationContentResponse.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/27.
|
||||
//
|
||||
|
||||
struct GetCurationContentResponse: Decodable {
|
||||
let totalCount: Int
|
||||
let items: [GetAudioContentMainItem]
|
||||
}
|
|
@ -9,51 +9,66 @@ import SwiftUI
|
|||
import Kingfisher
|
||||
|
||||
struct AudioContentCommentItemView: View {
|
||||
|
||||
let comment: GetAudioContentCommentListItem
|
||||
let contentCreatorId: Int
|
||||
let audioContentId: Int
|
||||
let commentItem: GetAudioContentCommentListItem
|
||||
let isReplyComment: Bool
|
||||
let isShowPopupMenuButton: Bool
|
||||
|
||||
let modifyComment: (Int, String) -> Void
|
||||
let onClickDelete: (Int) -> Void
|
||||
|
||||
@State var isShowPopupMenu: Bool = false
|
||||
@State var isModeModify: Bool = false
|
||||
@State var comment: String = ""
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 6.7) {
|
||||
KFImage(URL(string: comment.profileUrl))
|
||||
KFImage(URL(string: commentItem.profileUrl))
|
||||
.resizable()
|
||||
.frame(width: 40, height: 40)
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(comment.nickname)
|
||||
Text(commentItem.nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text(comment.date)
|
||||
Text(commentItem.date)
|
||||
.font(.custom(Font.medium.rawValue, size: 10.3))
|
||||
.foregroundColor(Color(hex: "525252"))
|
||||
.padding(.top, 4)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if isShowPopupMenuButton && (contentCreatorId == UserDefaults.int(forKey: .userId) || commentItem.writerId == UserDefaults.int(forKey: .userId)) {
|
||||
Image("ic_seemore_vertical")
|
||||
.onTapGesture { isShowPopupMenu.toggle() }
|
||||
}
|
||||
}
|
||||
|
||||
if comment.donationCan > 0 {
|
||||
if commentItem.donationCan > 0 {
|
||||
HStack(spacing: 3) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 13.3, height: 13.3)
|
||||
|
||||
Text("\(comment.donationCan)")
|
||||
Text("\(commentItem.donationCan)")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.horizontal, 6.7)
|
||||
.padding(.vertical, 2.7)
|
||||
.background(
|
||||
comment.donationCan >= 100000 ? Color(hex: "973a3a") :
|
||||
comment.donationCan >= 50000 ? Color(hex: "d85e37") :
|
||||
comment.donationCan >= 10000 ? Color(hex: "d38c38") :
|
||||
comment.donationCan >= 5000 ? Color(hex: "59548f") :
|
||||
comment.donationCan >= 1000 ? Color(hex: "4d6aa4") :
|
||||
comment.donationCan >= 500 ? Color(hex: "2d7390") :
|
||||
commentItem.donationCan >= 100000 ? Color(hex: "973a3a") :
|
||||
commentItem.donationCan >= 50000 ? Color(hex: "d85e37") :
|
||||
commentItem.donationCan >= 10000 ? Color(hex: "d38c38") :
|
||||
commentItem.donationCan >= 5000 ? Color(hex: "59548f") :
|
||||
commentItem.donationCan >= 1000 ? Color(hex: "4d6aa4") :
|
||||
commentItem.donationCan >= 500 ? Color(hex: "2d7390") :
|
||||
Color(hex: "548f7d")
|
||||
)
|
||||
.cornerRadius(10.7)
|
||||
|
@ -62,19 +77,61 @@ struct AudioContentCommentItemView: View {
|
|||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
if isModeModify {
|
||||
HStack(spacing: 0) {
|
||||
TextField("댓글을 입력해 보세요.", text: $comment)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("btn_message_send")
|
||||
.resizable()
|
||||
.frame(width: 35, height: 35)
|
||||
.padding(6.7)
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
if commentItem.comment != comment {
|
||||
modifyComment(commentItem.id, comment)
|
||||
}
|
||||
isModeModify = false
|
||||
}
|
||||
}
|
||||
.background(Color(hex: "232323"))
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
)
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
Text(comment.comment)
|
||||
Text(commentItem.comment)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, comment.donationCan > 0 ? 0 : 13.3)
|
||||
.padding(.top, commentItem.donationCan > 0 ? 0 : 13.3)
|
||||
|
||||
if !isReplyComment {
|
||||
Text(comment.replyCount > 0 ? "답글 \(comment.replyCount)개" : "답글 쓰기")
|
||||
NavigationLink(
|
||||
destination: AudioContentListReplyView(
|
||||
creatorId: contentCreatorId,
|
||||
audioContentId: audioContentId,
|
||||
parentComment: commentItem
|
||||
)
|
||||
) {
|
||||
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
@ -85,5 +142,35 @@ struct AudioContentCommentItemView: View {
|
|||
.frame(height: 0.5)
|
||||
.padding(.top, 16.7)
|
||||
}
|
||||
|
||||
if isShowPopupMenu {
|
||||
VStack(spacing: 10) {
|
||||
if commentItem.writerId == UserDefaults.int(forKey: .userId) {
|
||||
Text("수정")
|
||||
.font(.custom(Font.medium.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.onTapGesture {
|
||||
isModeModify = true
|
||||
isShowPopupMenu = false
|
||||
}
|
||||
}
|
||||
|
||||
if contentCreatorId == UserDefaults.int(forKey: .userId) ||
|
||||
commentItem.writerId == UserDefaults.int(forKey: .userId)
|
||||
{
|
||||
Text("삭제")
|
||||
.font(.custom(Font.medium.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.onTapGesture {
|
||||
onClickDelete(commentItem.id)
|
||||
isShowPopupMenu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(10)
|
||||
.background(Color(hex: "222222"))
|
||||
}
|
||||
}
|
||||
.onAppear { comment = commentItem.comment }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,15 @@ import Kingfisher
|
|||
struct AudioContentCommentListView: View {
|
||||
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
let creatorId: Int
|
||||
let audioContentId: Int
|
||||
|
||||
@StateObject var viewModel = AudioContentCommentListViewModel()
|
||||
|
||||
@State private var commentId: Int = 0
|
||||
@State private var isShowDeletePopup: Bool = false
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
|
@ -59,7 +64,7 @@ struct AudioContentCommentListView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
|
@ -79,7 +84,7 @@ struct AudioContentCommentListView: View {
|
|||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
@ -97,13 +102,22 @@ struct AudioContentCommentListView: View {
|
|||
LazyVStack(spacing: 13.3) {
|
||||
ForEach(0..<viewModel.commentList.count, id: \.self) { index in
|
||||
let comment = viewModel.commentList[index]
|
||||
NavigationLink {
|
||||
AudioContentListReplyView(
|
||||
VStack {
|
||||
AudioContentCommentItemView(
|
||||
contentCreatorId: creatorId,
|
||||
audioContentId: audioContentId,
|
||||
parentComment: comment
|
||||
commentItem: comment,
|
||||
isReplyComment: false,
|
||||
isShowPopupMenuButton: true,
|
||||
modifyComment: { commentId, comment in
|
||||
hideKeyboard()
|
||||
viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, comment: comment)
|
||||
},
|
||||
onClickDelete: {
|
||||
commentId = $0
|
||||
isShowDeletePopup = true
|
||||
}
|
||||
)
|
||||
} label: {
|
||||
AudioContentCommentItemView(comment: comment, isReplyComment: false)
|
||||
.padding(.horizontal, 26.7)
|
||||
.onAppear {
|
||||
if index == viewModel.commentList.count - 1 {
|
||||
|
@ -116,6 +130,24 @@ struct AudioContentCommentListView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if isShowDeletePopup && commentId > 0 {
|
||||
SodaDialog(
|
||||
title: "댓글 삭제",
|
||||
desc: "삭제하시겠습니까?",
|
||||
confirmButtonTitle: "삭제",
|
||||
confirmButtonAction: {
|
||||
viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, isActive: false)
|
||||
commentId = 0
|
||||
isShowDeletePopup = false
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonAction: {
|
||||
commentId = 0
|
||||
isShowDeletePopup = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
LoadingView()
|
||||
}
|
||||
|
|
|
@ -121,4 +121,77 @@ class AudioContentCommentListViewModel: ObservableObject {
|
|||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func modifyComment(
|
||||
commentId: Int,
|
||||
audioContentId: Int,
|
||||
comment: String? = nil,
|
||||
isActive: Bool? = nil
|
||||
) {
|
||||
if comment == nil && isActive == nil {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
errorMessage = "내용을 입력하세요."
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
|
||||
var request = ModifyCommentRequest(commentId: commentId)
|
||||
|
||||
if let comment = comment {
|
||||
request.comment = comment
|
||||
}
|
||||
|
||||
if let isActive = isActive {
|
||||
request.isActive = isActive
|
||||
}
|
||||
|
||||
repository.modifyComment(request: request)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.comment = ""
|
||||
self.page = 1
|
||||
self.isLast = false
|
||||
|
||||
self.commentList.removeAll()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
self.getCommentList()
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,16 @@ import Kingfisher
|
|||
|
||||
struct AudioContentListReplyView: View {
|
||||
|
||||
let creatorId: Int
|
||||
let audioContentId: Int
|
||||
let parentComment: GetAudioContentCommentListItem
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
|
||||
@StateObject var viewModel = AudioContentListReplyViewModel()
|
||||
|
||||
@State private var commentId: Int = 0
|
||||
@State private var isShowDeletePopup: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
|
@ -53,7 +57,7 @@ struct AudioContentListReplyView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
|
@ -73,7 +77,7 @@ struct AudioContentListReplyView: View {
|
|||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
@ -87,7 +91,15 @@ struct AudioContentListReplyView: View {
|
|||
.padding(.bottom, 13.3)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
AudioContentCommentItemView(comment: parentComment, isReplyComment: true)
|
||||
AudioContentCommentItemView(
|
||||
contentCreatorId: creatorId,
|
||||
audioContentId: audioContentId,
|
||||
commentItem: parentComment,
|
||||
isReplyComment: true,
|
||||
isShowPopupMenuButton: false,
|
||||
modifyComment: { _, _ in },
|
||||
onClickDelete: { _ in }
|
||||
)
|
||||
.padding(.horizontal, 26.7)
|
||||
.padding(.bottom, 13.3)
|
||||
|
||||
|
@ -95,7 +107,21 @@ struct AudioContentListReplyView: View {
|
|||
LazyVStack(spacing: 13.3) {
|
||||
ForEach(0..<viewModel.commentList.count, id: \.self) { index in
|
||||
let comment = viewModel.commentList[index]
|
||||
AudioContentCommentItemView(comment: comment, isReplyComment: true)
|
||||
AudioContentCommentItemView(
|
||||
contentCreatorId: creatorId,
|
||||
audioContentId: audioContentId,
|
||||
commentItem: comment,
|
||||
isReplyComment: true,
|
||||
isShowPopupMenuButton: true,
|
||||
modifyComment: { commentId, comment in
|
||||
hideKeyboard()
|
||||
viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, comment: comment)
|
||||
},
|
||||
onClickDelete: {
|
||||
commentId = $0
|
||||
isShowDeletePopup = true
|
||||
}
|
||||
)
|
||||
.padding(.horizontal, 40)
|
||||
.onAppear {
|
||||
if index == viewModel.commentList.count - 1 {
|
||||
|
@ -108,6 +134,28 @@ struct AudioContentListReplyView: View {
|
|||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarBackButtonHidden()
|
||||
|
||||
if isShowDeletePopup && commentId > 0 {
|
||||
SodaDialog(
|
||||
title: "댓글 삭제",
|
||||
desc: "삭제하시겠습니까?",
|
||||
confirmButtonTitle: "삭제",
|
||||
confirmButtonAction: {
|
||||
viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, isActive: false)
|
||||
commentId = 0
|
||||
isShowDeletePopup = false
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonAction: {
|
||||
commentId = 0
|
||||
isShowDeletePopup = false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if viewModel.isLoading {
|
||||
LoadingView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.audioContentId = audioContentId
|
||||
|
|
|
@ -121,4 +121,77 @@ final class AudioContentListReplyViewModel: ObservableObject {
|
|||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func modifyComment(
|
||||
commentId: Int,
|
||||
audioContentId: Int,
|
||||
comment: String? = nil,
|
||||
isActive: Bool? = nil
|
||||
) {
|
||||
if comment == nil && isActive == nil {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
errorMessage = "내용을 입력하세요."
|
||||
isShowPopup = true
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
|
||||
var request = ModifyCommentRequest(commentId: commentId)
|
||||
|
||||
if let comment = comment {
|
||||
request.comment = comment
|
||||
}
|
||||
|
||||
if let isActive = isActive {
|
||||
request.isActive = isActive
|
||||
}
|
||||
|
||||
repository.modifyComment(request: request)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.comment = ""
|
||||
self.page = 1
|
||||
self.isLast = false
|
||||
|
||||
self.commentList.removeAll()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
|
||||
self.getCommentList()
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.isLoading = false
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ struct ContentDetailCommentView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
|
@ -78,7 +78,7 @@ struct ContentDetailCommentView: View {
|
|||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// ModifyCommentRequest.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/09/08.
|
||||
//
|
||||
|
||||
struct ModifyCommentRequest: Encodable {
|
||||
let commentId: Int
|
||||
var comment: String? = nil
|
||||
var isActive: Bool? = nil
|
||||
}
|
|
@ -43,7 +43,7 @@ struct ContentDetailOtherContentView: View {
|
|||
}
|
||||
.padding(13.3)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color(hex: "2b2635"))
|
||||
.background(Color(hex: "13181b"))
|
||||
.cornerRadius(4.7)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import Sliders
|
|||
struct ContentDetailPlayView: View {
|
||||
|
||||
let audioContent: GetAudioContentDetailResponse
|
||||
let isAlertPreview: Bool
|
||||
@Binding var isShowPreviewAlert: Bool
|
||||
|
||||
@StateObject var contentPlayManager = ContentPlayManager.shared
|
||||
|
@ -34,7 +35,7 @@ struct ContentDetailPlayView: View {
|
|||
)
|
||||
.cornerRadius(10.7, corners: [.topLeft, .topRight])
|
||||
|
||||
Image(isPlaying() ? "btn_audio_content_pause" : "btn_audio_content_play")
|
||||
Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
|
||||
.onTapGesture {
|
||||
if isPlaying() {
|
||||
contentPlayManager.pauseAudio()
|
||||
|
|
|
@ -66,9 +66,9 @@ struct ContentDetailView: View {
|
|||
viewModel.getAudioContentDetail()
|
||||
}) {
|
||||
VStack(spacing: 0) {
|
||||
LazyVStack(spacing: 0) {
|
||||
ContentDetailPlayView(
|
||||
audioContent: audioContent,
|
||||
isAlertPreview: audioContent.price > 0 && !audioContent.existOrdered && audioContent.orderType == nil && audioContent.creator.creatorId != UserDefaults.int(forKey: .userId),
|
||||
isShowPreviewAlert: $viewModel.isShowPreviewAlert
|
||||
)
|
||||
|
||||
|
@ -85,6 +85,7 @@ struct ContentDetailView: View {
|
|||
},
|
||||
onClickDonation: { viewModel.isShowDonationPopup = true }
|
||||
)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
if audioContent.price > 0 &&
|
||||
!audioContent.existOrdered &&
|
||||
|
@ -92,6 +93,7 @@ struct ContentDetailView: View {
|
|||
audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
|
||||
ContentDetailPurchaseButton(price: audioContent.price)
|
||||
.contentShape(Rectangle())
|
||||
.padding(.horizontal, 13.3)
|
||||
.onTapGesture { isShowOrderView = true }
|
||||
}
|
||||
|
||||
|
@ -108,19 +110,13 @@ struct ContentDetailView: View {
|
|||
.cornerRadius(5.3)
|
||||
.padding(.top, 13.3)
|
||||
.contentShape(Rectangle())
|
||||
.padding(.horizontal, 13.3)
|
||||
.onTapGesture {
|
||||
if audioContent.commentCount > 0 {
|
||||
isShowCommentListView = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(Color(hex: "232323"))
|
||||
.frame(height: 6.7)
|
||||
.padding(.top, 24)
|
||||
|
||||
ContentDetailOtherContentView(
|
||||
title: "크리에이터의 다른 콘텐츠",
|
||||
|
@ -282,6 +278,7 @@ struct ContentDetailView: View {
|
|||
content: {
|
||||
AudioContentCommentListView(
|
||||
isPresented: $isShowCommentListView,
|
||||
creatorId: viewModel.audioContent!.creator.creatorId,
|
||||
audioContentId: viewModel.audioContent!.contentId
|
||||
)
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ struct ContentOrderConfirmDialogView: View {
|
|||
.resizable()
|
||||
.frame(width: 16.7, height: 16.7)
|
||||
|
||||
Text("\(orderType == .RENTAL ? Int(ceil(Double(audioContent.price) * 0.7)) : audioContent.price)")
|
||||
Text("\(orderType == .RENTAL ? Int(ceil(Double(audioContent.price) * 0.6)) : audioContent.price)")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ struct ContentOrderDialogView: View {
|
|||
.resizable()
|
||||
.frame(width: 16.7, height: 16.7)
|
||||
|
||||
Text("\(Int(ceil(Double(price) * 0.7)))")
|
||||
Text("\(Int(ceil(Double(price) * 0.6)))")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
|
|
|
@ -12,11 +12,12 @@ import Kingfisher
|
|||
|
||||
struct LiveRoomDonationDialogView: View {
|
||||
|
||||
@AppStorage("can") private var can: Int = UserDefaults.int(forKey: .can)
|
||||
|
||||
@State private var donationCan = ""
|
||||
@State private var donationMessage = ""
|
||||
@State private var isShowErrorPopup = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var can = 0
|
||||
|
||||
@Binding var isShowing: Bool
|
||||
let isAudioContentDonation: Bool
|
||||
|
@ -243,9 +244,6 @@ struct LiveRoomDonationDialogView: View {
|
|||
}
|
||||
.offset(y: isAudioContentDonation ? 0 : 0 - keyboardHandler.keyboardHeight)
|
||||
}
|
||||
.onAppear {
|
||||
self.can = UserDefaults.int(forKey: .can)
|
||||
}
|
||||
}
|
||||
|
||||
func limitText() {
|
||||
|
|
|
@ -13,10 +13,27 @@ struct ContentMainCurationItemView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text(item.title)
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("ic_forward")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.onTapGesture {
|
||||
AppState.shared
|
||||
.setAppStep(
|
||||
step: .curationAll(
|
||||
title: item.title,
|
||||
curationId: item.curationId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(item.description)
|
||||
.font(.custom(Font.medium.rawValue, size: 13))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
|
|
|
@ -14,8 +14,8 @@ struct ContentMainCurationView: View {
|
|||
var body: some View {
|
||||
LazyVStack(spacing: 40) {
|
||||
ForEach(0..<items.count, id: \.self) {
|
||||
let item = items[$0]
|
||||
ContentMainCurationItemView(item: item)
|
||||
ContentMainCurationItemView(item: items[$0])
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@ struct ContentMainMyStashView: View {
|
|||
Text("전체보기")
|
||||
.font(.custom(Font.light.rawValue, size: 11.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.onTapGesture {}
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .orderListAll)
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
|
|
|
@ -33,7 +33,11 @@ struct ContentMainNewContentThemeView: View {
|
|||
.stroke(lineWidth: 0.5)
|
||||
.foregroundColor(Color(hex: selectedTheme == theme ? "9970ff" : "eeeeee"))
|
||||
)
|
||||
.onTapGesture { selectTheme(theme) }
|
||||
.onTapGesture {
|
||||
if selectedTheme != theme {
|
||||
selectTheme(theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,21 @@ struct ContentMainNewContentView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("새로운 콘텐츠")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("ic_forward")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .newContentAll)
|
||||
}
|
||||
}
|
||||
|
||||
ContentMainNewContentThemeView(themes: themes, selectTheme: selectTheme, selectedTheme: $selectedTheme)
|
||||
.padding(.vertical, 16.7)
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// ContentMainRankingView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/10/15.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ContentMainRankingView: View {
|
||||
|
||||
let item: GetAudioContentRanking
|
||||
|
||||
let rows = [
|
||||
GridItem(.fixed(60), alignment: .leading),
|
||||
GridItem(.fixed(60), alignment: .leading),
|
||||
GridItem(.fixed(60), alignment: .leading)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(spacing: 0) {
|
||||
Text("인기 콘텐츠")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("ic_forward")
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .contentRankingAll)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(spacing: 8) {
|
||||
Text("\(item.startDate) ~ \(item.endDate)")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.")
|
||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.background(Color(hex: "222222"))
|
||||
.padding(.top, 13.3)
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
LazyHGrid(rows: rows, spacing: 13.3) {
|
||||
ForEach(0..<item.items.count, id: \.self) { index in
|
||||
let content = item.items[index]
|
||||
HStack(spacing: 0) {
|
||||
KFImage(URL(string: content.coverImageUrl))
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(2.7)
|
||||
|
||||
Text("\(index + 1)")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
.padding(.horizontal, 12)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(content.title)
|
||||
.lineLimit(2)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
|
||||
Text(content.creatorNickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 11))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: screenSize().width * 0.66, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
AppState
|
||||
.shared
|
||||
.setAppStep(step: .contentDetail(contentId: content.contentId))
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 207)
|
||||
}
|
||||
.padding(.top, 13.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentMainRankingView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentMainRankingView(
|
||||
item: GetAudioContentRanking(
|
||||
startDate: "2023년 10월 2일",
|
||||
endDate: "10월 9일",
|
||||
items: [
|
||||
GetAudioContentRankingItem(
|
||||
contentId: 10,
|
||||
title: "sdffsfddfs",
|
||||
coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456",
|
||||
themeStr: "커버곡",
|
||||
price: 10,
|
||||
duration: "00:30:20",
|
||||
creatorId: 1,
|
||||
creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ"
|
||||
),
|
||||
GetAudioContentRankingItem(
|
||||
contentId: 10,
|
||||
title: "sdffsfddfs",
|
||||
coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456",
|
||||
themeStr: "커버곡",
|
||||
price: 10,
|
||||
duration: "00:30:20",
|
||||
creatorId: 1,
|
||||
creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ"
|
||||
),
|
||||
GetAudioContentRankingItem(
|
||||
contentId: 10,
|
||||
title: "sdffsfddfs",
|
||||
coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456",
|
||||
themeStr: "커버곡",
|
||||
price: 10,
|
||||
duration: "00:30:20",
|
||||
creatorId: 1,
|
||||
creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ"
|
||||
),
|
||||
GetAudioContentRankingItem(
|
||||
contentId: 10,
|
||||
title: "sdffsfddfs",
|
||||
coverImageUrl: "https://test-cf.sodalive.net/audio_content_cover/27/27-cover-77e3a23f-23f2-467c-867f-3e6b7e08060c-9136-1696424061456",
|
||||
themeStr: "커버곡",
|
||||
price: 10,
|
||||
duration: "00:30:20",
|
||||
creatorId: 1,
|
||||
creatorNickname: "ㄹㄴ어ㅏㅣㅇㄴ런아ㅣ"
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -27,20 +27,24 @@ struct ContentMainView: View {
|
|||
.font(.custom(Font.bold.rawValue, size: 21.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(.bottom, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
if viewModel.newContentUploadCreatorList.count > 0 {
|
||||
ContentMainNewContentCreatorView(items: viewModel.newContentUploadCreatorList)
|
||||
.padding(.bottom, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
if viewModel.bannerList.count > 0 {
|
||||
ContentMainBannerView(items: viewModel.bannerList)
|
||||
.padding(.bottom, 40)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
if viewModel.orderList.count > 0 {
|
||||
ContentMainMyStashView(items: viewModel.orderList)
|
||||
.padding(.bottom, 40)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
ContentMainNewContentView(
|
||||
|
@ -49,6 +53,13 @@ struct ContentMainView: View {
|
|||
selectTheme: { viewModel.selectedTheme = $0 },
|
||||
selectedTheme: $viewModel.selectedTheme
|
||||
)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
if let contentRanking = viewModel.contentRanking {
|
||||
ContentMainRankingView(item: contentRanking)
|
||||
.padding(.top, 40)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
if viewModel.curationList.count > 0 {
|
||||
ContentMainCurationView(items: viewModel.curationList)
|
||||
|
@ -56,7 +67,7 @@ struct ContentMainView: View {
|
|||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
.padding(13.3)
|
||||
.padding(.vertical, 13.3)
|
||||
}
|
||||
|
||||
if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
|
||||
|
|
|
@ -24,6 +24,7 @@ final class ContentMainViewModel: ObservableObject {
|
|||
@Published var orderList = [GetAudioContentMainItem]()
|
||||
@Published var themeList = [String]()
|
||||
@Published var curationList = [GetAudioContentCurationResponse]()
|
||||
@Published var contentRanking: GetAudioContentRanking? = nil
|
||||
|
||||
@Published var selectedTheme = "전체" {
|
||||
didSet {
|
||||
|
@ -64,6 +65,7 @@ final class ContentMainViewModel: ObservableObject {
|
|||
self.bannerList.append(contentsOf: data.bannerList)
|
||||
self.orderList.append(contentsOf: data.orderList)
|
||||
self.curationList.append(contentsOf: data.curationList)
|
||||
self.contentRanking = data.contentRanking
|
||||
|
||||
self.themeList.append("전체")
|
||||
self.themeList.append(contentsOf: data.themeList)
|
||||
|
|
|
@ -14,6 +14,24 @@ struct GetAudioContentMainResponse: Decodable {
|
|||
let themeList: [String]
|
||||
let newContentList: [GetAudioContentMainItem]
|
||||
let curationList: [GetAudioContentCurationResponse]
|
||||
let contentRanking: GetAudioContentRanking
|
||||
}
|
||||
|
||||
struct GetAudioContentRanking: Decodable {
|
||||
let startDate: String
|
||||
let endDate: String
|
||||
let items: [GetAudioContentRankingItem]
|
||||
}
|
||||
|
||||
struct GetAudioContentRankingItem: Decodable {
|
||||
let contentId: Int
|
||||
let title: String
|
||||
let coverImageUrl: String
|
||||
let themeStr: String
|
||||
let price: Int
|
||||
let duration: String
|
||||
let creatorId: Int
|
||||
let creatorNickname: String
|
||||
}
|
||||
|
||||
struct GetNewContentUploadCreator: Decodable {
|
||||
|
@ -33,6 +51,7 @@ struct GetAudioContentMainItem: Decodable {
|
|||
}
|
||||
|
||||
struct GetAudioContentCurationResponse: Decodable {
|
||||
let curationId: Int
|
||||
let title: String
|
||||
let description: String
|
||||
let contents: [GetAudioContentMainItem]
|
||||
|
|
|
@ -151,6 +151,21 @@ struct ContentView: View {
|
|||
case .orderListAll:
|
||||
OrderListAllView()
|
||||
|
||||
case .userProfileDonationAll(let userId):
|
||||
UserProfileDonationAllView(userId: userId)
|
||||
|
||||
case .userProfileFanTalkAll(let userId):
|
||||
UserProfileFanTalkAllView(userId: userId)
|
||||
|
||||
case .newContentAll:
|
||||
ContentNewAllView()
|
||||
|
||||
case .curationAll(let title, let curationId):
|
||||
ContentCurationView(title: title, curationId: curationId)
|
||||
|
||||
case .contentRankingAll:
|
||||
ContentRankingAllView()
|
||||
|
||||
default:
|
||||
EmptyView()
|
||||
.frame(width: 0, height: 0, alignment: .topLeading)
|
||||
|
|
|
@ -15,8 +15,9 @@ enum ExplorerApi {
|
|||
case getFollowerList(userId: Int, page: Int, size: Int)
|
||||
case getCreatorProfileCheers(userId: Int, page: Int, size: Int)
|
||||
case writeCheers(parentCheersId: Int?, creatorId: Int, content: String)
|
||||
case modifyCheers(cheersId: Int, content: String)
|
||||
case modifyCheers(request: PutModifyCheersRequest)
|
||||
case writeCreatorNotice(request: PostCreatorNoticeRequest)
|
||||
case getCreatorProfileDonationRanking(userId: Int, page: Int, size: Int)
|
||||
}
|
||||
|
||||
extension ExplorerApi: TargetType {
|
||||
|
@ -35,6 +36,9 @@ extension ExplorerApi: TargetType {
|
|||
case .getCreatorProfile(let userId):
|
||||
return "/explorer/profile/\(userId)"
|
||||
|
||||
case .getCreatorProfileDonationRanking(let userId, _, _):
|
||||
return "/explorer/profile/\(userId)/donation-rank"
|
||||
|
||||
case .getFollowerList(let userId, _, _):
|
||||
return "/explorer/profile/\(userId)/follower-list"
|
||||
|
||||
|
@ -54,7 +58,7 @@ extension ExplorerApi: TargetType {
|
|||
|
||||
var method: Moya.Method {
|
||||
switch self {
|
||||
case .getExplorer, .searchChannel, .getCreatorProfile, .getFollowerList, .getCreatorProfileCheers:
|
||||
case .getExplorer, .searchChannel, .getCreatorProfile, .getFollowerList, .getCreatorProfileCheers, .getCreatorProfileDonationRanking:
|
||||
return .get
|
||||
|
||||
case .writeCheers, .writeCreatorNotice:
|
||||
|
@ -98,12 +102,19 @@ extension ExplorerApi: TargetType {
|
|||
let request = PostWriteCheersRequest(parentId: parentCheersId, creatorId: creatorId, content: content)
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .modifyCheers(let cheersId, let content):
|
||||
let request = PutModifyCheersRequest(cheersId: cheersId, content: content)
|
||||
case .modifyCheers(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .writeCreatorNotice(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .getCreatorProfileDonationRanking(_, let page, let size):
|
||||
let parameters = [
|
||||
"page": page - 1,
|
||||
"size": size
|
||||
] as [String : Any]
|
||||
|
||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,11 +37,16 @@ final class ExplorerRepository {
|
|||
return api.requestPublisher(.writeCheers(parentCheersId: parentCheersId, creatorId: creatorId, content: content))
|
||||
}
|
||||
|
||||
func modifyCheers(cheersId: Int, content: String) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.modifyCheers(cheersId: cheersId, content: content))
|
||||
func modifyCheers(cheersId: Int, content: String?, isActive: Bool?) -> AnyPublisher<Response, MoyaError> {
|
||||
let request = PutModifyCheersRequest(cheersId: cheersId, content: content, isActive: isActive)
|
||||
return api.requestPublisher(.modifyCheers(request: request))
|
||||
}
|
||||
|
||||
func writeCreatorNotice(notice: String) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.writeCreatorNotice(request: PostCreatorNoticeRequest(notice: notice)))
|
||||
}
|
||||
|
||||
func getCreatorProfileDonationRanking(userId: Int, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.getCreatorProfileDonationRanking(userId: userId, page: page, size: size))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// ExplorerSectionView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/10/14.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct ExplorerSectionView: View {
|
||||
|
||||
let section: GetExplorerSectionResponse
|
||||
let rankingCrawns = ["ic_crown_1", "ic_crown_2", "ic_crown_3"]
|
||||
let rankingColors = [
|
||||
[Color(hex: "ffdc00"), Color(hex: "ffb600")],
|
||||
[Color(hex: "ffffff"), Color(hex: "9f9f9f")],
|
||||
[Color(hex: "e6a77a"), Color(hex: "c67e4a")],
|
||||
[Color(hex: "ffffff").opacity(0), Color(hex: "ffffff").opacity(0)]
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
if let coloredTitle = section.coloredTitle, let color = section.color {
|
||||
let titleArray = section.title.components(separatedBy: coloredTitle)
|
||||
HStack(spacing: 0) {
|
||||
Text(titleArray[0])
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text(coloredTitle)
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: color))
|
||||
|
||||
if titleArray.count > 1 {
|
||||
Text(titleArray[1])
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(section.title)
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
|
||||
if let desc = section.desc {
|
||||
VStack(spacing: 8) {
|
||||
Text("\(desc)")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("※ 인기 크리에이터의 순위는 매주 업데이트됩니다.")
|
||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.background(Color(hex: "222222"))
|
||||
.padding(.top, 13.3)
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 13.3) {
|
||||
ForEach(0..<section.creators.count, id: \.self) { index in
|
||||
let creator = section.creators[index]
|
||||
VStack(spacing: 0) {
|
||||
if let _ = section.desc {
|
||||
ZStack {
|
||||
KFImage(URL(string: creator.profileImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 90, height: 90))
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 90, height: 90)
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(
|
||||
AngularGradient(colors: rankingColors[index < 4 ? index : 3], center: .center),
|
||||
lineWidth: 3
|
||||
)
|
||||
)
|
||||
|
||||
if index < 3 {
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Image(rankingCrawns[index])
|
||||
.resizable()
|
||||
.frame(width: 37, height: 37)
|
||||
}
|
||||
.frame(width: 93.3, height: 93.3, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
.frame(width: 93.3, height: 93.3)
|
||||
} else {
|
||||
KFImage(URL(string: creator.profileImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 93, height: 93))
|
||||
.resizable()
|
||||
.clipShape(Circle())
|
||||
.frame(width: 93, height: 93)
|
||||
}
|
||||
|
||||
Text(creator.nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.lineLimit(1)
|
||||
.frame(width: 93.3)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text(creator.tags)
|
||||
.font(.custom(Font.medium.rawValue, size: 10))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.lineLimit(1)
|
||||
.frame(width: 93.3)
|
||||
.padding(.top, 3.3)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
AppState.shared
|
||||
.setAppStep(step: .creatorDetail(userId: creator.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ExplorerSectionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ExplorerSectionView(
|
||||
section: GetExplorerSectionResponse(
|
||||
title: "",
|
||||
coloredTitle: nil,
|
||||
color: nil,
|
||||
desc: nil,
|
||||
creators: []
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ struct ExplorerView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
|
@ -83,68 +83,8 @@ struct ExplorerView: View {
|
|||
} else {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 26.7) {
|
||||
ForEach(viewModel.explorerSections, id: \.self) { section in
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
if let coloredTitle = section.coloredTitle, let color = section.color {
|
||||
let titleArray = section.title.components(separatedBy: coloredTitle)
|
||||
HStack(spacing: 0) {
|
||||
Text(titleArray[0])
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text(coloredTitle)
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: color))
|
||||
|
||||
if titleArray.count > 1 {
|
||||
Text(titleArray[1])
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||
} else {
|
||||
Text(section.title)
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||
}
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: 13.3) {
|
||||
ForEach(section.creators, id: \.self) { creator in
|
||||
VStack(spacing: 0) {
|
||||
KFImage(URL(string: creator.profileImageUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 93.3, height: 93.3))
|
||||
.resizable()
|
||||
.frame(width: 93.3, height: 93.3)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(creator.nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.lineLimit(1)
|
||||
.frame(width: 93.3)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Text(creator.tags)
|
||||
.font(.custom(Font.medium.rawValue, size: 10))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.lineLimit(1)
|
||||
.frame(width: 93.3)
|
||||
.padding(.top, 3.3)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
AppState.shared
|
||||
.setAppStep(step: .creatorDetail(userId: creator.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||
}
|
||||
ForEach(0..<viewModel.explorerSections.count, id: \.self) { index in
|
||||
ExplorerSectionView(section: viewModel.explorerSections[index])
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 40)
|
||||
|
|
|
@ -15,6 +15,7 @@ struct GetExplorerSectionResponse: Decodable, Hashable {
|
|||
let title: String
|
||||
let coloredTitle: String?
|
||||
let color: String?
|
||||
let desc: String?
|
||||
let creators: [GetExplorerSectionCreatorResponse]
|
||||
}
|
||||
|
||||
|
|
|
@ -15,5 +15,6 @@ struct PostWriteCheersRequest: Encodable {
|
|||
|
||||
struct PutModifyCheersRequest: Encodable {
|
||||
let cheersId: Int
|
||||
let content: String
|
||||
let content: String?
|
||||
let isActive: Bool?
|
||||
}
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// UserProfileFanTalkAllView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/08/29.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct UserProfileFanTalkAllView: View {
|
||||
|
||||
let userId: Int
|
||||
@StateObject var viewModel = UserProfileFanTalkViewModel()
|
||||
|
||||
@State private var cheersContent: String = ""
|
||||
@State private var cheersId: Int = 0
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "팬 Talk 전체보기")
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(spacing: 6.7) {
|
||||
Text("응원")
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("\(viewModel.cheersTotalCount)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
}
|
||||
.padding(.top, 20)
|
||||
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
||||
.padding(.top, 13.3)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
TextField("응원댓글을 입력하세요", text: $cheersContent)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image("btn_message_send")
|
||||
.resizable()
|
||||
.frame(width: 35, height: 35)
|
||||
.padding(6.7)
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
viewModel.writeCheers(creatorId: userId, cheersContent: cheersContent)
|
||||
cheersContent = ""
|
||||
}
|
||||
}
|
||||
.background(Color(hex: "232323"))
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
)
|
||||
.padding(.top, 13.3)
|
||||
|
||||
Rectangle()
|
||||
.frame(height: 1)
|
||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
||||
.padding(.top, 13.3)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVStack(spacing: 20) {
|
||||
if viewModel.cheersTotalCount > 0 {
|
||||
ForEach(0..<viewModel.cheersList.count, id: \.self) { index in
|
||||
let cheer = viewModel.cheersList[index]
|
||||
UserProfileFanTalkCheersItemView(
|
||||
userId: userId,
|
||||
cheersItem: cheer,
|
||||
writeCheerReply: { cheersReplyContent in
|
||||
viewModel.writeCheersReply(parentCheersId: cheer.cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent)
|
||||
},
|
||||
modifyCheer: { cheersId, cheersReplyContent in
|
||||
viewModel.modifyCheers(cheersId: cheersId, creatorId: userId, cheersContent: cheersReplyContent)
|
||||
},
|
||||
reportPopup: { cheersId in
|
||||
viewModel.reportCheersId = cheersId
|
||||
viewModel.isShowCheersReportView = true
|
||||
},
|
||||
onClickDelete: { cheersId in
|
||||
self.cheersId = cheersId
|
||||
viewModel.isShowCheersDeleteView = true
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
if index == viewModel.cheersList.count - 1 {
|
||||
viewModel.getCheersList(creatorId: userId)
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
|
||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.vertical, 60)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 20)
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.onAppear {
|
||||
viewModel.getCheersList(creatorId: userId)
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
.onTapGesture { hideKeyboard() }
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.background(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
.cornerRadius(20)
|
||||
.padding(.bottom, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
ZStack {
|
||||
if viewModel.isShowCheersReportView {
|
||||
CheersReportDialogView(
|
||||
isShowing: $viewModel.isShowCheersReportView,
|
||||
confirmAction: { reason in
|
||||
viewModel.report(type: .CHEERS, reason: reason)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if viewModel.isShowCheersDeleteView {
|
||||
if viewModel.isShowCheersDeleteView {
|
||||
SodaDialog(
|
||||
title: "응원글 삭제",
|
||||
desc: "삭제하시겠습니까?",
|
||||
confirmButtonTitle: "삭제",
|
||||
confirmButtonAction: {
|
||||
viewModel.modifyCheers(cheersId: cheersId, creatorId: userId, isActive: false)
|
||||
viewModel.isShowCheersDeleteView = false
|
||||
self.cheersId = 0
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonAction: { viewModel.isShowCheersDeleteView = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,18 +11,24 @@ import Kingfisher
|
|||
struct UserProfileFanTalkCheersItemView: View {
|
||||
|
||||
let userId: Int
|
||||
let cheer: GetCheersResponseItem
|
||||
let cheersItem: GetCheersResponseItem
|
||||
let writeCheerReply: (String) -> Void
|
||||
let modifyCheer: (Int, String) -> Void
|
||||
let reportPopup: (Int) -> Void
|
||||
let onClickDelete: (Int) -> Void
|
||||
|
||||
@State var replyContent: String = ""
|
||||
@State var isShowInputReply = false
|
||||
|
||||
@State var isShowPopupMenu: Bool = false
|
||||
@State var isModeModify: Bool = false
|
||||
@State var cheers: String = ""
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack(alignment: .top, spacing: 6.7) {
|
||||
KFImage(URL(string: cheer.profileUrl))
|
||||
KFImage(URL(string: cheersItem.profileUrl))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 33.3, height: 33.3))
|
||||
.resizable()
|
||||
|
@ -30,19 +36,61 @@ struct UserProfileFanTalkCheersItemView: View {
|
|||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text("\(cheer.nickname)")
|
||||
Text("\(cheersItem.nickname)")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("\(cheer.date)")
|
||||
Text("\(cheersItem.date)")
|
||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||
.foregroundColor(Color(hex: "525252"))
|
||||
.padding(.top, 8.3)
|
||||
|
||||
Text("\(cheer.content)")
|
||||
if isModeModify {
|
||||
HStack(spacing: 10) {
|
||||
TextField("", text: $cheers)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(13.3)
|
||||
.background(Color(hex: "232323"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
)
|
||||
|
||||
Text("수정")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(13.3)
|
||||
.background(Color(hex: "9970ff"))
|
||||
.cornerRadius(6.7)
|
||||
.onTapGesture {
|
||||
modifyCheer(cheersItem.cheersId, cheers)
|
||||
isModeModify = false
|
||||
}
|
||||
|
||||
Text("취소")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(13.3)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(6.7)
|
||||
.onTapGesture {
|
||||
isModeModify = false
|
||||
}
|
||||
}
|
||||
.padding(.top, 13.3)
|
||||
} else {
|
||||
Text("\(cheersItem.content)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.padding(.top, 13.3)
|
||||
}
|
||||
|
||||
if isShowInputReply {
|
||||
HStack(spacing: 10) {
|
||||
|
@ -53,24 +101,24 @@ struct UserProfileFanTalkCheersItemView: View {
|
|||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(13.3)
|
||||
.background(Color(hex: "232323"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
)
|
||||
|
||||
Text("등록")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(13.3)
|
||||
.background(Color(hex: "9970ff"))
|
||||
.background(Color(hex: "3bb9f1"))
|
||||
.cornerRadius(6.7)
|
||||
.onTapGesture {
|
||||
if cheer.replyList.count > 0 {
|
||||
modifyCheer(cheer.replyList[0].cheersId, replyContent)
|
||||
if cheersItem.replyList.count > 0 {
|
||||
modifyCheer(cheersItem.replyList[0].cheersId, replyContent)
|
||||
} else {
|
||||
writeCheerReply(replyContent)
|
||||
}
|
||||
|
@ -78,7 +126,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
|||
}
|
||||
.padding(.top, 10)
|
||||
} else {
|
||||
if cheer.replyList.count <= 0 {
|
||||
if cheersItem.replyList.count <= 0 {
|
||||
if userId == UserDefaults.int(forKey: .userId) {
|
||||
Text("답글쓰기")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
|
@ -89,7 +137,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
let reply = cheer.replyList[0]
|
||||
let reply = cheersItem.replyList[0]
|
||||
VStack(alignment: .leading, spacing: 8.3) {
|
||||
Text(reply.content)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
|
@ -123,8 +171,10 @@ struct UserProfileFanTalkCheersItemView: View {
|
|||
|
||||
Spacer()
|
||||
|
||||
if !isModeModify {
|
||||
Image("ic_seemore_vertical")
|
||||
.onTapGesture { reportPopup(cheer.cheersId) }
|
||||
.onTapGesture { isShowPopupMenu = true }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle()
|
||||
|
@ -133,5 +183,49 @@ struct UserProfileFanTalkCheersItemView: View {
|
|||
.padding(.top, 13.3)
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
|
||||
if isShowPopupMenu {
|
||||
VStack(spacing: 10) {
|
||||
if cheersItem.memberId != UserDefaults.int(forKey: .userId) {
|
||||
Text("신고하기")
|
||||
.font(.custom(Font.medium.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.onTapGesture {
|
||||
reportPopup(cheersItem.cheersId)
|
||||
isShowPopupMenu = false
|
||||
}
|
||||
}
|
||||
|
||||
if cheersItem.memberId == UserDefaults.int(forKey: .userId) {
|
||||
Text("수정")
|
||||
.font(.custom(Font.medium.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.onTapGesture {
|
||||
isModeModify = true
|
||||
isShowPopupMenu = false
|
||||
cheers = cheersItem.content
|
||||
}
|
||||
}
|
||||
|
||||
if userId == UserDefaults.int(forKey: .userId) ||
|
||||
cheersItem.memberId == UserDefaults.int(forKey: .userId)
|
||||
{
|
||||
Text("삭제")
|
||||
.font(.custom(Font.medium.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.onTapGesture {
|
||||
onClickDelete(cheersItem.cheersId)
|
||||
isShowPopupMenu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(10)
|
||||
.background(Color(hex: "222222"))
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
isShowPopupMenu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ struct UserProfileFanTalkView: View {
|
|||
let cheers: GetCheersResponse
|
||||
let errorPopup: (String) -> Void
|
||||
let reportPopup: (Int) -> Void
|
||||
let deletePopup: (Int) -> Void
|
||||
|
||||
@Binding var isLoading: Bool
|
||||
@State private var cheersContent: String = ""
|
||||
|
@ -61,7 +62,7 @@ struct UserProfileFanTalkView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
|
@ -97,15 +98,18 @@ struct UserProfileFanTalkView: View {
|
|||
let cheer = viewModel.cheersList[$0]
|
||||
UserProfileFanTalkCheersItemView(
|
||||
userId: userId,
|
||||
cheer: cheer,
|
||||
cheersItem: cheer,
|
||||
writeCheerReply: { cheersReplyContent in
|
||||
viewModel.writeCheersReply(parentCheersId: cheer.cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent)
|
||||
},
|
||||
modifyCheer: { cheersId, cheersReplyContent in
|
||||
viewModel.modifyCheersReply(cheersId: cheersId, creatorId: userId, cheersReplyContent: cheersReplyContent)
|
||||
viewModel.modifyCheers(cheersId: cheersId, creatorId: userId, cheersContent: cheersReplyContent)
|
||||
},
|
||||
reportPopup: { cheersId in
|
||||
reportPopup(cheersId)
|
||||
},
|
||||
onClickDelete: { cheersId in
|
||||
deletePopup(cheersId)
|
||||
}
|
||||
)
|
||||
.onTapGesture {
|
||||
|
|
|
@ -17,8 +17,8 @@ final class UserProfileFanTalkViewModel: ObservableObject {
|
|||
@Published var cheersList = [GetCheersResponseItem]()
|
||||
|
||||
@Published var reportCheersId = 0
|
||||
@Published var isShowCheersReportMenu = false
|
||||
@Published var isShowCheersReportView = false
|
||||
@Published var isShowCheersDeleteView = false
|
||||
|
||||
var errorPopup: ((String) -> Void)?
|
||||
var setLoading: ((Bool) -> Void)?
|
||||
|
@ -224,8 +224,17 @@ final class UserProfileFanTalkViewModel: ObservableObject {
|
|||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func modifyCheersReply(cheersId: Int, creatorId: Int, cheersReplyContent: String) {
|
||||
if cheersReplyContent.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
func modifyCheers(cheersId: Int, creatorId: Int, cheersContent: String? = nil, isActive: Bool? = nil) {
|
||||
if cheersContent == nil && isActive == nil {
|
||||
if let errorPopup = errorPopup {
|
||||
errorPopup("변경사항이 없습니다.")
|
||||
} else {
|
||||
errorMessage = "변경사항이 없습니다."
|
||||
isShowPopup = true
|
||||
}
|
||||
}
|
||||
|
||||
if let cheersContent = cheersContent, cheersContent.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||
if let errorPopup = errorPopup {
|
||||
errorPopup("내용을 입력하세요")
|
||||
} else {
|
||||
|
@ -241,7 +250,7 @@ final class UserProfileFanTalkViewModel: ObservableObject {
|
|||
}
|
||||
isLoading = true
|
||||
|
||||
repository.modifyCheers(cheersId: cheersId, content: cheersReplyContent)
|
||||
repository.modifyCheers(cheersId: cheersId, content: cheersContent, isActive: isActive)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
|
|
|
@ -14,6 +14,7 @@ struct GetCheersResponse: Decodable {
|
|||
|
||||
struct GetCheersResponseItem: Decodable {
|
||||
let cheersId: Int
|
||||
let memberId: Int
|
||||
let nickname: String
|
||||
let profileUrl: String
|
||||
let content: String
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// GetDonationAllResponse.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/08/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct GetDonationAllResponse: Decodable {
|
||||
let accumulatedCansToday: Int
|
||||
let accumulatedCansLastWeek: Int
|
||||
let accumulatedCansThisMonth: Int
|
||||
let isVisibleDonationRank: Bool
|
||||
let totalCount: Int
|
||||
let userDonationRanking: [UserDonationRankingResponse]
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
//
|
||||
// UserProfileDonationAllView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/08/29.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct UserProfileDonationAllView: View {
|
||||
|
||||
let userId: Int
|
||||
@StateObject var viewModel = UserProfileDonationAllViewModel()
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
DetailNavigationBar(title: "후원랭킹 전체보기")
|
||||
|
||||
if userId == UserDefaults.int(forKey: .userId) {
|
||||
VStack(spacing: 10.7) {
|
||||
HStack(spacing: 10) {
|
||||
Spacer()
|
||||
|
||||
Text("채널에 후원랭킹 활성화")
|
||||
.font(.custom(Font.bold.rawValue, size: 16))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Image(viewModel.isVisibleDonationRank ? "btn_toggle_on_big" : "btn_toggle_off_big")
|
||||
.resizable()
|
||||
.frame(width: 46.7, height: 27)
|
||||
.onTapGesture {
|
||||
viewModel.toggleVisibleDonationRank()
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Text("※ 비활성화하면 채널 내 후원랭킹이 표시되지 않으며,\n라이브 중에도 후원랭킹에 따른 뱃지가 반영되지 않습니다.")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "555555"))
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
.padding(.top, 13.3)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
HStack(spacing: 0) {
|
||||
Text("오늘")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.accumulatedCansToday.comma())")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text(" 캔")
|
||||
.font(.custom(Font.light.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("지난주")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.accumulatedCansLastWeek.comma())")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text(" 캔")
|
||||
.font(.custom(Font.light.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Text("이번 달 어제까지")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("\(viewModel.accumulatedCansThisMonth.comma())")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text(" 캔")
|
||||
.font(.custom(Font.light.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
}
|
||||
.padding(16.7)
|
||||
.background(Color(hex: "13181b"))
|
||||
.cornerRadius(8)
|
||||
.padding(.top, 13.3)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
HStack(alignment: .center, spacing: 0) {
|
||||
Text("전체")
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Text("\(viewModel.totalCount)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "80d8ff"))
|
||||
.padding(.leading, 6.7)
|
||||
|
||||
Text("개")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding(.top, 13.3)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
Rectangle()
|
||||
.frame(width: screenSize().width - 26.7, height: 1)
|
||||
.foregroundColor(Color(hex: "595959"))
|
||||
.padding(.top, 6.7)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(0..<viewModel.userDonationRanking.count, id: \.self) { index in
|
||||
let item = viewModel.userDonationRanking[index]
|
||||
UserProfileDonationAllItemView(
|
||||
index: index,
|
||||
item: item,
|
||||
withDonationCan: userId == UserDefaults.int(forKey: .userId),
|
||||
itemCount: viewModel.userDonationRanking.count
|
||||
)
|
||||
.onAppear {
|
||||
if index == viewModel.userDonationRanking.count - 1 {
|
||||
viewModel.getCreatorProfileDonationRanking()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 16.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.userId = userId
|
||||
viewModel.getCreatorProfileDonationRanking()
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||
HStack {
|
||||
Spacer()
|
||||
Text(viewModel.errorMessage)
|
||||
.padding(.vertical, 13.3)
|
||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.background(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color.white)
|
||||
.multilineTextAlignment(.leading)
|
||||
.cornerRadius(20)
|
||||
.padding(.bottom, 66.7)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct UserProfileDonationAllItemView: View {
|
||||
|
||||
let index: Int
|
||||
let item: UserDonationRankingResponse
|
||||
let withDonationCan: Bool
|
||||
let itemCount: Int
|
||||
|
||||
let rankingCrawns = ["ic_crown_1", "ic_crown_2", "ic_crown_3"]
|
||||
let rankingColors = [
|
||||
[Color(hex: "ffdc00"), Color(hex: "ffb600")],
|
||||
[Color(hex: "ffffff"), Color(hex: "9f9f9f")],
|
||||
[Color(hex: "e6a77a"), Color(hex: "c67e4a")],
|
||||
[Color(hex: "ffffff").opacity(0), Color(hex: "ffffff").opacity(0)]
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
ZStack {
|
||||
KFImage(URL(string: item.profileImage))
|
||||
.cancelOnDisappear(true)
|
||||
.downsampling(size: CGSize(width: 60, height: 60))
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 60, height: 60, alignment: .top)
|
||||
.clipShape(Circle())
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(
|
||||
AngularGradient(colors: rankingColors[index < 4 ? index : 3], center: .center),
|
||||
lineWidth: 3
|
||||
)
|
||||
)
|
||||
|
||||
if index < 3 {
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
Image(rankingCrawns[index])
|
||||
.resizable()
|
||||
.frame(width: 25, height: 25)
|
||||
}
|
||||
.frame(width: 63, height: 63, alignment: .trailing)
|
||||
}
|
||||
}
|
||||
.frame(width: 63, height: 63)
|
||||
|
||||
Text("\(index + 1)")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.padding(.leading, 20)
|
||||
.padding(.trailing, 13.3)
|
||||
|
||||
let nickname = item.nickname.count > 10 ? "\(String(item.nickname.prefix(10)))..." : item.nickname
|
||||
Text(nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
if let donationCan = item.donationCan, donationCan > 0, withDonationCan {
|
||||
Text("\(donationCan) 캔")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, isTop3Index(index: index) ? 20 : 0)
|
||||
.padding(.top, getTopPadding(index: index))
|
||||
.padding(.bottom, getBottomPadding(index: index))
|
||||
.background(Color(hex: "13181b").opacity(isTop3Index(index: index) ? 1 : 0))
|
||||
.cornerRadius(4.7, corners: cornerRadiusConers(index: index))
|
||||
.padding(.horizontal, isTop3Index(index: index) ? 0 : 6.7)
|
||||
}
|
||||
|
||||
private func isTop3Index(index: Int) -> Bool {
|
||||
return index == 0 || index == 1 || index == 2
|
||||
}
|
||||
|
||||
private func getTopPadding(index: Int) -> CGFloat {
|
||||
if index == 0 || index == 3 {
|
||||
return 20
|
||||
} else {
|
||||
return 6.7
|
||||
}
|
||||
}
|
||||
|
||||
private func getBottomPadding(index: Int) -> CGFloat {
|
||||
if (index == 0 && itemCount == 1) || (index == 1 && itemCount == 2) || index == 2 {
|
||||
return 20
|
||||
} else {
|
||||
return 6.7
|
||||
}
|
||||
}
|
||||
|
||||
private func cornerRadiusConers(index: Int) -> UIRectCorner {
|
||||
if (index == 0 && itemCount == 1) {
|
||||
return [.topLeft, .topRight, .bottomLeft, .bottomRight]
|
||||
} else if index == 0 {
|
||||
return [.topLeft, .topRight]
|
||||
} else if (index == 1 && itemCount == 2) || index == 2 {
|
||||
return [.bottomLeft, .bottomRight]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// UserProfileDonationAllViewModel.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/08/29.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
final class UserProfileDonationAllViewModel: ObservableObject {
|
||||
private var repository = ExplorerRepository()
|
||||
private var memberRepository = UserRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
@Published var isLoading = false
|
||||
|
||||
@Published var totalCount = 0
|
||||
@Published var accumulatedCansToday = 0
|
||||
@Published var accumulatedCansLastWeek = 0
|
||||
@Published var accumulatedCansThisMonth = 0
|
||||
@Published var isVisibleDonationRank = false
|
||||
@Published var userDonationRanking: [UserDonationRankingResponse] = []
|
||||
|
||||
var userId = 0
|
||||
var page = 1
|
||||
var isLast = false
|
||||
private let pageSize = 10
|
||||
|
||||
func getCreatorProfileDonationRanking() {
|
||||
if (!isLast && !isLoading) {
|
||||
isLoading = true
|
||||
|
||||
repository.getCreatorProfileDonationRanking(userId: userId, page: page, size: pageSize)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<GetDonationAllResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
self.accumulatedCansToday = data.accumulatedCansToday
|
||||
self.accumulatedCansLastWeek = data.accumulatedCansLastWeek
|
||||
self.accumulatedCansThisMonth = data.accumulatedCansThisMonth
|
||||
self.isVisibleDonationRank = data.isVisibleDonationRank
|
||||
|
||||
if !data.userDonationRanking.isEmpty {
|
||||
page += 1
|
||||
self.totalCount = data.totalCount
|
||||
self.userDonationRanking.append(contentsOf: data.userDonationRanking)
|
||||
} else {
|
||||
isLast = true
|
||||
}
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
||||
func toggleVisibleDonationRank() {
|
||||
isLoading = true
|
||||
|
||||
memberRepository.profileUpdate(
|
||||
request: ProfileUpdateRequest(
|
||||
email: UserDefaults.string(forKey: .email),
|
||||
isVisibleDonationRank: !isVisibleDonationRank
|
||||
)
|
||||
)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<GetProfileResponse>.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
isVisibleDonationRank.toggle()
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
|
@ -32,7 +32,9 @@ struct UserProfileDonationView: View {
|
|||
Text("전체보기")
|
||||
.font(.custom(Font.light.rawValue, size: 11.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.onTapGesture {}
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .userProfileDonationAll(userId: userId))
|
||||
}
|
||||
}
|
||||
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
|
|
|
@ -45,6 +45,7 @@ struct UserProfileView: View {
|
|||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 0) {
|
||||
if let creatorProfile = viewModel.creatorProfile {
|
||||
VStack(spacing: 0) {
|
||||
UserProfileCreatorView(
|
||||
creator: creatorProfile.creator) {
|
||||
viewModel.creatorFollow()
|
||||
|
@ -87,6 +88,7 @@ struct UserProfileView: View {
|
|||
viewModel.isExpandNotice.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if creatorProfile.contentList.count > 0 ||
|
||||
userId == UserDefaults.int(forKey: .userId)
|
||||
|
@ -95,7 +97,7 @@ struct UserProfileView: View {
|
|||
userId: userId,
|
||||
items: creatorProfile.contentList
|
||||
)
|
||||
.padding(.top, 46.7)
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
|
@ -123,7 +125,7 @@ struct UserProfileView: View {
|
|||
}
|
||||
}
|
||||
)
|
||||
.padding(.top, 46.7)
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
|
@ -153,6 +155,8 @@ struct UserProfileView: View {
|
|||
.padding(.top, 26.7)
|
||||
}
|
||||
|
||||
VStack(spacing: 0) {
|
||||
if creatorProfile.similarCreatorList.count > 0 {
|
||||
VStack(spacing: 26.7) {
|
||||
UserProfileSimilarCreatorView(
|
||||
creators: creatorProfile.similarCreatorList,
|
||||
|
@ -165,6 +169,7 @@ struct UserProfileView: View {
|
|||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
||||
}
|
||||
.padding(.top, 26.7)
|
||||
}
|
||||
|
||||
UserProfileFanTalkView(
|
||||
userId: userId,
|
||||
|
@ -174,8 +179,12 @@ struct UserProfileView: View {
|
|||
viewModel.isShowPopup = true
|
||||
},
|
||||
reportPopup: { cheerId in
|
||||
viewModel.reportCheersId = cheerId
|
||||
viewModel.isShowCheersReportMenu = true
|
||||
viewModel.cheersId = cheerId
|
||||
viewModel.isShowCheersReportView = true
|
||||
},
|
||||
deletePopup: { cheerId in
|
||||
viewModel.cheersId = cheerId
|
||||
viewModel.isShowCheersDeleteView = true
|
||||
},
|
||||
isLoading: $viewModel.isLoading
|
||||
)
|
||||
|
@ -184,6 +193,7 @@ struct UserProfileView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
@ -220,20 +230,18 @@ struct UserProfileView: View {
|
|||
)
|
||||
}
|
||||
|
||||
if viewModel.isShowCheersReportMenu {
|
||||
VStack(spacing: 0) {
|
||||
CheersReportMenuView(
|
||||
isShowing: $viewModel.isShowCheersReportMenu,
|
||||
onClickReport: { viewModel.isShowCheersReportView = true }
|
||||
if viewModel.isShowCheersDeleteView {
|
||||
SodaDialog(
|
||||
title: "응원글 삭제",
|
||||
desc: "삭제하시겠습니까?",
|
||||
confirmButtonTitle: "삭제",
|
||||
confirmButtonAction: {
|
||||
viewModel.deleteCheers(creatorId: userId)
|
||||
viewModel.isShowCheersDeleteView = false
|
||||
},
|
||||
cancelButtonTitle: "취소",
|
||||
cancelButtonAction: { viewModel.isShowCheersDeleteView = false }
|
||||
)
|
||||
|
||||
if proxy.safeAreaInsets.bottom > 0 {
|
||||
Rectangle()
|
||||
.foregroundColor(Color(hex: "222222"))
|
||||
.frame(width: proxy.size.width, height: 15.3)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
if viewModel.isShowCheersReportView {
|
||||
|
|
|
@ -47,10 +47,9 @@ final class UserProfileViewModel: ObservableObject {
|
|||
@Published var isShowUesrReportView = false
|
||||
@Published var isShowProfileReportConfirm = false
|
||||
|
||||
@Published var reportCheersId = 0
|
||||
@Published var isShowCheersReportMenu = false
|
||||
@Published var cheersId = 0
|
||||
@Published var isShowCheersReportView = false
|
||||
|
||||
@Published var isShowCheersDeleteView = false
|
||||
|
||||
let paymentDialogCancelTitle = "취소"
|
||||
|
||||
|
@ -488,7 +487,7 @@ final class UserProfileViewModel: ObservableObject {
|
|||
func report(type: ReportType, userId: Int? = nil, reason: String = "프로필 신고") {
|
||||
isLoading = true
|
||||
|
||||
let request = ReportRequest(type: type, reason: reason, reportedMemberId: userId, cheersId: reportCheersId > 0 && type == .CHEERS ? reportCheersId : nil, audioContentId: nil)
|
||||
let request = ReportRequest(type: type, reason: reason, reportedMemberId: userId, cheersId: cheersId > 0 && type == .CHEERS ? cheersId : nil, audioContentId: nil)
|
||||
reportRepository.report(request: request)
|
||||
.sink { result in
|
||||
switch result {
|
||||
|
@ -501,7 +500,7 @@ final class UserProfileViewModel: ObservableObject {
|
|||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
self.reportCheersId = 0
|
||||
self.cheersId = 0
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
|
@ -521,4 +520,43 @@ final class UserProfileViewModel: ObservableObject {
|
|||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func deleteCheers(creatorId: Int) {
|
||||
isLoading = true
|
||||
|
||||
repository.modifyCheers(cheersId: cheersId, content: nil, isActive: false)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.cheersId = 0
|
||||
self.isLoading = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.getCreatorProfile(userId: creatorId)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
}
|
||||
|
||||
self.isShowPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||
self.isShowPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,3 +30,15 @@ extension Int64 {
|
|||
return timeText.joined(separator: ":")
|
||||
}
|
||||
}
|
||||
|
||||
extension Int {
|
||||
func comma() -> String {
|
||||
let numberFormatter = NumberFormatter()
|
||||
numberFormatter.groupingSeparator = ","
|
||||
numberFormatter.groupingSize = 3
|
||||
numberFormatter.usesGroupingSeparator = true
|
||||
numberFormatter.decimalSeparator = "."
|
||||
numberFormatter.numberStyle = .decimal
|
||||
return numberFormatter.string(from: self as NSNumber)!
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ enum UserDefaultsKey: String, CaseIterable {
|
|||
case nickname
|
||||
case pushToken
|
||||
case profileImage
|
||||
case noChatRoomList
|
||||
case devicePushToken
|
||||
case isContentPlayLoop
|
||||
case isFollowedChannel
|
||||
|
|
|
@ -32,6 +32,7 @@ struct FollowCreatorView: View {
|
|||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.top, 6.7)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
VStack(spacing: 13.3) {
|
||||
|
|
|
@ -29,6 +29,7 @@ struct LiveView: View {
|
|||
if viewModel.recommendLiveItems.count > 0 {
|
||||
SectionRecommendLiveView(items: viewModel.recommendLiveItems)
|
||||
.padding(.top, 13.3)
|
||||
.padding(.bottom, 40)
|
||||
}
|
||||
|
||||
if viewModel.recommendChannelItems.count > 0 {
|
||||
|
@ -38,21 +39,18 @@ struct LiveView: View {
|
|||
viewModel.recommendChannelItems,
|
||||
isFollowingList: $viewModel.isFollowingList
|
||||
)
|
||||
.padding(.top, 40)
|
||||
}
|
||||
|
||||
if viewModel.liveNowItems.count > 0 {
|
||||
SectionLiveNowView(
|
||||
items: viewModel.liveNowItems,
|
||||
onClickParticipant: {
|
||||
viewModel.enterRoom(roomId: $0)
|
||||
viewModel.enterLiveRoom(roomId: $0)
|
||||
},
|
||||
onTapCreateLive: {
|
||||
AppState.shared.setAppStep(step: .createLive(timeSettingMode: .NOW, onSuccess: onCreateSuccess))
|
||||
}
|
||||
)
|
||||
.padding(.top, 40)
|
||||
}
|
||||
|
||||
if viewModel.eventBannerItems.count > 0 {
|
||||
SectionEventBannerView(items: viewModel.eventBannerItems)
|
||||
|
@ -64,7 +62,6 @@ struct LiveView: View {
|
|||
.padding(.top, 40)
|
||||
}
|
||||
|
||||
if viewModel.liveReservationItems.count > 0 {
|
||||
SectionLiveReservationView(
|
||||
items: viewModel.liveReservationItems,
|
||||
onClickCancel: { viewModel.getSummary() },
|
||||
|
@ -79,7 +76,6 @@ struct LiveView: View {
|
|||
.padding(.top, 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
.onAppear {
|
||||
viewModel.getSummary()
|
||||
|
@ -141,44 +137,6 @@ struct LiveView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.valueChanged(value: appState.pushRoomId) { value in
|
||||
DispatchQueue.main.async {
|
||||
appState.setAppStep(step: .main)
|
||||
|
||||
if value > 0 {
|
||||
viewModel.enterRoom(roomId: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
.valueChanged(value: appState.pushChannelId) { value in
|
||||
DispatchQueue.main.async {
|
||||
appState.setAppStep(step: .main)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
if value > 0 {
|
||||
appState.setAppStep(step: .creatorDetail(userId: value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.valueChanged(value: appState.pushAudioContentId) { value in
|
||||
appState.setAppStep(step: .main)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
if value > 0 {
|
||||
appState.setAppStep(step: .contentDetail(contentId: value))
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if appState.pushRoomId > 0 {
|
||||
viewModel.enterRoom(roomId: appState.pushRoomId)
|
||||
} else if appState.pushChannelId > 0 {
|
||||
appState.setAppStep(step: .creatorDetail(userId: appState.pushChannelId))
|
||||
} else if appState.pushAudioContentId > 0 {
|
||||
appState.setAppStep(step: .contentDetail(contentId: appState.pushAudioContentId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func onCreateSuccess(response: CreateLiveRoomResponse) {
|
||||
|
|
|
@ -404,6 +404,56 @@ final class LiveViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func enterLiveRoom(roomId: Int) {
|
||||
getRoomDetail(roomId: roomId) {
|
||||
if let _ = $0.channelName {
|
||||
if $0.manager.id == UserDefaults.int(forKey: .userId) {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
|
||||
self.enterRoom(roomId: roomId)
|
||||
}
|
||||
} else if ($0.price == 0 || $0.isPaid) {
|
||||
if $0.isPrivateRoom {
|
||||
self.passwordDialogConfirmAction = { password in
|
||||
self.enterRoom(roomId: roomId, password: password)
|
||||
}
|
||||
self.isShowPasswordDialog = true
|
||||
} else {
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
|
||||
self.enterRoom(roomId: roomId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if $0.isPrivateRoom {
|
||||
self.secretOrPasswordDialogCan = $0.price
|
||||
self.passwordDialogConfirmAction = { password in
|
||||
self.enterRoom(roomId: roomId, password: password)
|
||||
}
|
||||
self.isShowPasswordDialog = true
|
||||
} else {
|
||||
self.paymentDialogTitle = "\($0.price)캔으로 입장"
|
||||
self.paymentDialogDesc = "'\($0.title)' 라이브에 참여하기 위해 결제합니다."
|
||||
self.paymentDialogConfirmTitle = "결제 후 참여하기"
|
||||
self.paymentDialogConfirmAction = { [unowned self] in
|
||||
hidePopup()
|
||||
self.enterRoom(roomId: roomId)
|
||||
}
|
||||
self.isShowPaymentDialog = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AppState.shared.setAppStep(
|
||||
step: .liveDetail(
|
||||
roomId: roomId,
|
||||
onClickParticipant: {},
|
||||
onClickReservation: { self.reservationLiveRoom(roomId: roomId) },
|
||||
onClickStart: { self.startLive(roomId: roomId) },
|
||||
onClickCancel: { self.getSummary() }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getRoomDetail(roomId: Int, onSuccess: @escaping (GetRoomDetailResponse) -> Void) {
|
||||
isLoading = true
|
||||
repository.getRoomDetail(roomId: roomId)
|
||||
|
|
|
@ -70,31 +70,16 @@ struct SectionLiveNowView: View {
|
|||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
|
||||
Text("🙀지금 참여가능한 라이브가 없습니다.\n직접 라이브를 만들어 보세요!")
|
||||
Text("🙀지금 참여가능한 라이브가 없습니다.\n채널을 팔로잉 하고 라이브 알림을 받아 보세요.")
|
||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 8)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Image("ic_plus_no_bg")
|
||||
.resizable()
|
||||
.frame(width: 33.3, height: 33.3, alignment: .center)
|
||||
|
||||
Text("라이브 만들기")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
.frame(width: 200, height: 33.3, alignment: .center)
|
||||
.background(Color(hex: "9970ff"))
|
||||
.cornerRadius(4.7)
|
||||
.padding(.top, 10.7)
|
||||
.onTapGesture { onTapCreateLive() }
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.padding(.vertical, 16.7)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.background(Color(hex: "2b2635"))
|
||||
.background(Color(hex: "13181b"))
|
||||
.cornerRadius(4.7)
|
||||
.padding(.top, 28.3)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ struct SectionRecommendLiveView: View {
|
|||
height: (screenSize().width - 26.7) * 0.53
|
||||
)
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId))
|
||||
}
|
||||
.cornerRadius(4.7)
|
||||
} else {
|
||||
|
@ -50,6 +51,7 @@ struct SectionRecommendLiveView: View {
|
|||
height: (screenSize().width - 26.7) * 0.53
|
||||
)
|
||||
.onTapGesture {
|
||||
AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId))
|
||||
}
|
||||
.cornerRadius(4.7)
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ struct LiveReservationAllView: View {
|
|||
}
|
||||
.padding(.vertical, 16.7)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.background(Color(hex: "2b2635"))
|
||||
.background(Color(hex: "13181b"))
|
||||
.cornerRadius(4.7)
|
||||
.padding(.top, 28.3)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ struct WeekCalendarView: View {
|
|||
}
|
||||
.frame(width: 53.3)
|
||||
.frame(minHeight: 66.7)
|
||||
.background(index == selectedIndex ? Color(hex: "9970ff") : Color.clear)
|
||||
.background(index == selectedIndex ? Color(hex: "3bb9f1") : Color.clear)
|
||||
.cornerRadius(6.7)
|
||||
.onTapGesture {
|
||||
if self.selectedIndex != index {
|
||||
|
|
|
@ -21,7 +21,7 @@ struct MyLiveReservationItemView: View {
|
|||
|
||||
Text("내가 개설한 라이브")
|
||||
.font(.custom(Font.bold.rawValue, size: 16))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ struct MyLiveReservationItemView: View {
|
|||
}
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color(hex: "9970ff"), lineWidth: 1)
|
||||
.stroke(Color(hex: "3bb9f1"), lineWidth: 1)
|
||||
)
|
||||
|
||||
Divider()
|
||||
|
|
|
@ -25,7 +25,7 @@ struct SectionLiveReservationView: View {
|
|||
|
||||
Text("예약중")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.foregroundColor(Color(hex: "3bb9f1"))
|
||||
|
||||
Spacer()
|
||||
|
||||
|
@ -75,7 +75,7 @@ struct SectionLiveReservationView: View {
|
|||
step: .liveDetail(
|
||||
roomId: item.roomId,
|
||||
onClickParticipant: {},
|
||||
onClickReservation: {},
|
||||
onClickReservation: { onClickReservation(item.roomId) },
|
||||
onClickStart: { onClickStart(item.roomId) },
|
||||
onClickCancel: onClickCancel
|
||||
)
|
||||
|
@ -93,31 +93,16 @@ struct SectionLiveReservationView: View {
|
|||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
|
||||
Text("지금 예약중인 라이브가 없습니다.\n직접 라이브를 만들어 보세요!")
|
||||
Text("지금 예약중인 라이브가 없습니다.\n채널을 팔로잉 하고 라이브 알림을 받아 보세요.")
|
||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 8)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Image("ic_plus_no_bg")
|
||||
.resizable()
|
||||
.frame(width: 33.3, height: 33.3, alignment: .center)
|
||||
|
||||
Text("라이브 만들기")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.white)
|
||||
}
|
||||
.frame(width: 200, height: 33.3, alignment: .center)
|
||||
.background(Color(hex: "9970ff"))
|
||||
.cornerRadius(4.7)
|
||||
.padding(.top, 10.7)
|
||||
.onTapGesture { onTapCreateLive() }
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.padding(.vertical, 16.7)
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
.background(Color(hex: "2b2635"))
|
||||
.background(Color(hex: "13181b"))
|
||||
.cornerRadius(4.7)
|
||||
.padding(.top, 28.3)
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ struct LiveRoomCreateView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.top, 12)
|
||||
.padding(.horizontal, 6.7)
|
||||
|
@ -480,7 +480,7 @@ struct LiveRoomCreateView: View {
|
|||
.multilineTextAlignment(.center)
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.numberPad)
|
||||
.padding(.vertical, 15.7)
|
||||
.frame(width: screenSize().width - 26.7, alignment: .center)
|
||||
|
@ -626,7 +626,7 @@ struct LiveRoomCreateView: View {
|
|||
.multilineTextAlignment(.center)
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.numberPad)
|
||||
.padding(.vertical, 15.7)
|
||||
.frame(width: screenSize().width - 26.7, alignment: .center)
|
||||
|
@ -720,7 +720,7 @@ struct LiveRoomCreateView: View {
|
|||
.multilineTextAlignment(.center)
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.numberPad)
|
||||
|
||||
Spacer()
|
||||
|
|
|
@ -305,11 +305,6 @@ struct LiveDetailView: View {
|
|||
if room.manager.id == UserDefaults.int(forKey: .userId) {
|
||||
VStack(spacing: 16.7) {
|
||||
HStack(spacing: 13.3) {
|
||||
Image("btn_big_share")
|
||||
.onTapGesture {
|
||||
viewModel.shareRoom(roomId: room.roomId)
|
||||
}
|
||||
|
||||
Text("수정")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color.white)
|
||||
|
@ -355,13 +350,6 @@ struct LiveDetailView: View {
|
|||
}
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
} else if room.isPaid {
|
||||
HStack(spacing: 13.3) {
|
||||
Button {
|
||||
viewModel.shareRoom(roomId: room.roomId)
|
||||
} label: {
|
||||
Image("btn_big_share")
|
||||
}
|
||||
|
||||
Text("예약완료")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
|
@ -369,16 +357,8 @@ struct LiveDetailView: View {
|
|||
.padding(.horizontal, 99)
|
||||
.background(Color(hex: "525252"))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
} else {
|
||||
HStack(spacing: 13.3) {
|
||||
Button {
|
||||
viewModel.shareRoom(roomId: room.roomId)
|
||||
} label: {
|
||||
Image("btn_big_share")
|
||||
}
|
||||
|
||||
Button {
|
||||
onClickReservation()
|
||||
AppState.shared.back()
|
||||
|
@ -391,17 +371,9 @@ struct LiveDetailView: View {
|
|||
.background(Color(hex: "9970ff"))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
}
|
||||
} else {
|
||||
HStack(spacing: 13.3) {
|
||||
Button {
|
||||
viewModel.shareRoom(roomId: room.roomId)
|
||||
} label: {
|
||||
Image("btn_big_share")
|
||||
}
|
||||
|
||||
Button {
|
||||
onClickParticipant()
|
||||
AppState.shared.back()
|
||||
|
@ -414,7 +386,6 @@ struct LiveDetailView: View {
|
|||
.background(Color(hex: "ff5c49"))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width - 26.7)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ struct LiveRoomDonationRankingItemView: View {
|
|||
.padding(.horizontal, isTop3Index(index: index) ? 20 : 0)
|
||||
.padding(.top, getTopPadding(index: index))
|
||||
.padding(.bottom, getBottomPadding(index: index))
|
||||
.background(Color(hex: "2b2635").opacity(isTop3Index(index: index) ? 1 : 0))
|
||||
.background(Color(hex: "13181b").opacity(isTop3Index(index: index) ? 1 : 0))
|
||||
.cornerRadius(4.7, corners: cornerRadiusConers(index: index))
|
||||
.padding(.horizontal, isTop3Index(index: index) ? 0 : 6.7)
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ struct LiveRoomDonationRankingTotalCanView: View {
|
|||
}
|
||||
.padding(.horizontal, 18.7)
|
||||
.padding(.vertical, 10.7)
|
||||
.background(Color(hex: "2b2635"))
|
||||
.background(Color(hex: "13181b"))
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ struct LiveRoomInfoEditDialog: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.top, 12)
|
||||
.padding(.horizontal, 6.7)
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// LiveRoomNoChattingDialogView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2023/10/11.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct LiveRoomNoChattingDialogView: View {
|
||||
|
||||
let nickname: String
|
||||
let profileUrl: String
|
||||
let confirmAction: () -> Void
|
||||
let cancelAction: () -> Void
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black
|
||||
.opacity(0.5)
|
||||
.frame(width: screenSize().width, height: screenSize().height)
|
||||
|
||||
VStack(spacing: 21) {
|
||||
Text("채팅금지")
|
||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
|
||||
HStack(spacing: 8) {
|
||||
KFImage(URL(string: profileUrl))
|
||||
.resizable()
|
||||
.frame(width: 26.7, height: 26.7)
|
||||
|
||||
Text(nickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
}
|
||||
|
||||
Text("3분간 채팅금지를 하겠습니까?")
|
||||
.font(.custom(Font.medium.rawValue, size: 15))
|
||||
.foregroundColor(Color(hex: "bbbbbb"))
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
Text("취소")
|
||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.padding(.vertical, 16)
|
||||
.frame(width: (screenSize().width - 80) / 2)
|
||||
.background(Color(hex: "9970ff").opacity(0.13))
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color(hex: "9970ff"), lineWidth: 1)
|
||||
)
|
||||
.onTapGesture { cancelAction() }
|
||||
|
||||
Text("확인")
|
||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.vertical, 16)
|
||||
.frame(width: (screenSize().width - 80) / 2)
|
||||
.background(Color(hex: "9970ff"))
|
||||
.cornerRadius(8)
|
||||
.onTapGesture { confirmAction() }
|
||||
}
|
||||
}
|
||||
.padding(.top, 40)
|
||||
.padding(.bottom, 16.7)
|
||||
.padding(.horizontal, 16.7)
|
||||
.background(Color(hex: "222222"))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomNoChattingDialogView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomNoChattingDialogView(
|
||||
nickname: "닉네임",
|
||||
profileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
confirmAction: {},
|
||||
cancelAction: {}
|
||||
)
|
||||
}
|
||||
}
|
|
@ -74,6 +74,7 @@ struct LiveRoomProfileItemMasterView: View {
|
|||
struct LiveRoomProfileItemUserView: View {
|
||||
let isStaff: Bool
|
||||
let userId: Int
|
||||
let creatorId: Int
|
||||
let nickname: String
|
||||
let profileUrl: String
|
||||
let role: LiveRoomMemberRole
|
||||
|
@ -82,6 +83,7 @@ struct LiveRoomProfileItemUserView: View {
|
|||
let onClickInviteSpeaker: (Int) -> Void
|
||||
let onClickKickOut: (Int) -> Void
|
||||
let onClickProfile: (Int) -> Void
|
||||
let onClickNoChatting: (Int, String, String) -> Void
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
@ -145,6 +147,25 @@ struct LiveRoomProfileItemUserView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if role != .MANAGER && creatorId == UserDefaults.int(forKey: .userId) {
|
||||
Text("채금")
|
||||
.font(.custom(Font.medium.rawValue, size: 10))
|
||||
.foregroundColor(Color(hex: "ffffff"))
|
||||
.padding(.horizontal, 5.5)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color(hex: "9970ff").opacity(0.3))
|
||||
.cornerRadius(6.7)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 6.7)
|
||||
.stroke(Color(hex: "9970ff"), lineWidth: 1)
|
||||
)
|
||||
.cornerRadius(6.7)
|
||||
.padding(.leading, 10)
|
||||
.onTapGesture {
|
||||
onClickNoChatting(userId, nickname, profileUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if role != .MANAGER && isStaff {
|
||||
Image("ic_kick_out")
|
||||
.padding(.leading, 10)
|
||||
|
|
|
@ -22,16 +22,16 @@ struct LiveRoomProfilesDialogView: View {
|
|||
isShowing: Binding<Bool>,
|
||||
viewModel: LiveRoomViewModel,
|
||||
roomInfo: GetRoomInfoResponse,
|
||||
isShowRequestSpeaker: Bool,
|
||||
onClickRequestSpeaker: @escaping () -> Void,
|
||||
registerNotification: @escaping () -> Void,
|
||||
unRegisterNotification: @escaping () -> Void,
|
||||
onClickProfile: @escaping (Int) -> Void
|
||||
onClickProfile: @escaping (Int) -> Void,
|
||||
onClickNoChatting: @escaping (Int, String, String) -> Void
|
||||
) {
|
||||
self._isShowing = isShowing
|
||||
self.viewModel = viewModel
|
||||
self.roomInfo = roomInfo
|
||||
|
||||
if !roomInfo.managerList.isEmpty {
|
||||
self.profiles.append(
|
||||
AnyView(
|
||||
LiveRoomProfileItemTitleView(
|
||||
|
@ -42,6 +42,7 @@ struct LiveRoomProfilesDialogView: View {
|
|||
.padding(.vertical, 14)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let isStaff = viewModel.isEqualToStaffId(creatorId: UserDefaults.int(forKey: .userId)) ||
|
||||
roomInfo.creatorId == UserDefaults.int(forKey: .userId)
|
||||
|
@ -52,18 +53,21 @@ struct LiveRoomProfilesDialogView: View {
|
|||
LiveRoomProfileItemUserView(
|
||||
isStaff: isStaff ,
|
||||
userId: manager.id,
|
||||
creatorId: roomInfo.creatorId,
|
||||
nickname: manager.nickname,
|
||||
profileUrl: manager.profileImage,
|
||||
role: manager.role,
|
||||
onClickChangeListener: { _ in },
|
||||
onClickInviteSpeaker: { _ in },
|
||||
onClickKickOut: { _ in },
|
||||
onClickProfile: onClickProfile
|
||||
onClickProfile: onClickProfile,
|
||||
onClickNoChatting: { _, _, _ in }
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (roomInfo.speakerList.count - 1) > 0 {
|
||||
self.profiles.append(
|
||||
AnyView(
|
||||
LiveRoomProfileItemTitleView(
|
||||
|
@ -74,6 +78,7 @@ struct LiveRoomProfilesDialogView: View {
|
|||
.padding(.vertical, 14)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
for speaker in roomInfo.speakerList {
|
||||
if speaker.id == roomInfo.creatorId {
|
||||
|
@ -94,6 +99,7 @@ struct LiveRoomProfilesDialogView: View {
|
|||
LiveRoomProfileItemUserView(
|
||||
isStaff: isStaff,
|
||||
userId: speaker.id,
|
||||
creatorId: roomInfo.creatorId,
|
||||
nickname: speaker.nickname,
|
||||
profileUrl: speaker.profileImage,
|
||||
role: speaker.role,
|
||||
|
@ -110,23 +116,14 @@ struct LiveRoomProfilesDialogView: View {
|
|||
viewModel.kickOutId = $0
|
||||
viewModel.isShowKickOutPopup = true
|
||||
},
|
||||
onClickProfile: onClickProfile
|
||||
onClickProfile: onClickProfile,
|
||||
onClickNoChatting: onClickNoChatting
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if isShowRequestSpeaker {
|
||||
self.profiles.append(
|
||||
AnyView(
|
||||
LiveRoomProfileRequestSpeakerView {
|
||||
onClickRequestSpeaker()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
self.profiles.append(
|
||||
AnyView(
|
||||
LiveRoomProfileItemTitleView(
|
||||
|
@ -145,6 +142,7 @@ struct LiveRoomProfilesDialogView: View {
|
|||
LiveRoomProfileItemUserView(
|
||||
isStaff: isStaff,
|
||||
userId: listener.id,
|
||||
creatorId: roomInfo.creatorId,
|
||||
nickname: listener.nickname,
|
||||
profileUrl: listener.profileImage,
|
||||
role: listener.role,
|
||||
|
@ -163,7 +161,8 @@ struct LiveRoomProfilesDialogView: View {
|
|||
viewModel.kickOutId = $0
|
||||
viewModel.isShowKickOutPopup = true
|
||||
},
|
||||
onClickProfile: onClickProfile
|
||||
onClickProfile: onClickProfile,
|
||||
onClickNoChatting: onClickNoChatting
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -23,6 +23,7 @@ struct LiveRoomUserProfileDialogView: View {
|
|||
let onClickInviteSpeaker: (Int) -> Void
|
||||
let onClickChangeListener: (Int) -> Void
|
||||
let onClickMenu: (Int, String, Bool) -> Void
|
||||
let onClickNoChatting: (Int, String, String) -> Void
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
@ -210,6 +211,22 @@ struct LiveRoomUserProfileDialogView: View {
|
|||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, 21.3)
|
||||
|
||||
if let _ = userProfile.isManager {
|
||||
Text("3분간 채팅금지")
|
||||
.font(.custom(Font.bold.rawValue, size: 15))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 13)
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
)
|
||||
.onTapGesture { onClickNoChatting(userProfile.userId, userProfile.nickname, userProfile.profileUrl) }
|
||||
.padding(.top, 21.3)
|
||||
}
|
||||
|
||||
Text(userProfile.tags)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "9970ff"))
|
||||
|
|
|
@ -115,7 +115,7 @@ struct LiveRoomEditView: View {
|
|||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.default)
|
||||
.padding(.top, 12)
|
||||
.padding(.horizontal, 6.7)
|
||||
|
@ -219,7 +219,7 @@ struct LiveRoomEditView: View {
|
|||
.multilineTextAlignment(.center)
|
||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.accentColor(Color(hex: "9970ff"))
|
||||
.accentColor(Color(hex: "3bb9f1"))
|
||||
.keyboardType(.numberPad)
|
||||
.padding(.vertical, 15.7)
|
||||
.frame(width: screenSize().width - 26.7, alignment: .center)
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
import Foundation
|
||||
|
||||
enum LiveRoomRequestType: String {
|
||||
case REQUEST_SPEAKER, REQUEST_SPEAKER_ALLOW, INVITE_SPEAKER, CHANGE_LISTENER, KICK_OUT, SET_MANAGER, RELEASE_MANAGER
|
||||
case REQUEST_SPEAKER, REQUEST_SPEAKER_ALLOW, INVITE_SPEAKER, CHANGE_LISTENER, KICK_OUT, SET_MANAGER, RELEASE_MANAGER, NO_CHATTING
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ struct LiveRoomTopCreatorView: View {
|
|||
.scaledToFill()
|
||||
.frame(width: 33.3, height: 33.3)
|
||||
.clipShape(Circle())
|
||||
.contentShape(Circle())
|
||||
.onTapGesture { onClickProfile() }
|
||||
|
||||
Image("ic_crown")
|
||||
|
@ -33,11 +34,11 @@ struct LiveRoomTopCreatorView: View {
|
|||
.font(.custom(Font.light.rawValue, size: 12))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
|
||||
Spacer()
|
||||
|
||||
if creatorId != UserDefaults.int(forKey: .userId) {
|
||||
Image(isFollowing ? "btn_following" : "btn_follow")
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { onClickFollow(isFollowing) }
|
||||
.padding(.leading, 13.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|