Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
68675ebfe8 | ||
![]() |
0079f248ee | ||
![]() |
6583e07f45 | ||
![]() |
902b772267 | ||
![]() |
cdad53ae7b | ||
![]() |
995c6adab7 | ||
![]() |
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 | ||
![]() |
594bcc27ca | ||
![]() |
bcb934a155 | ||
![]() |
b31dcf901b | ||
![]() |
2c8485a1cd | ||
![]() |
f0d2bda024 | ||
![]() |
828c507742 | ||
![]() |
65b43e4c0d | ||
![]() |
728510a72d | ||
![]() |
89fd2f5f0d | ||
![]() |
52921e78b6 |
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 |
@@ -5,11 +5,11 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "loading_4.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_alarm.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_alarm.imageset/ic_alarm.png
vendored
Normal file
After Width: | Height: | Size: 781 B |
21
SodaLive/Resources/Assets.xcassets/ic_alarm_selected.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_alarm_selected.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_alarm_selected.imageset/ic_alarm_selected.png
vendored
Normal file
After Width: | Height: | Size: 804 B |
21
SodaLive/Resources/Assets.xcassets/ic_blog_purple.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_blog_purple.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_blog_purple.imageset/ic_blog_purple.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.3 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_heart_777.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_heart_777.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_heart_777.imageset/ic_heart_777.png
vendored
Normal file
After Width: | Height: | Size: 830 B |
21
SodaLive/Resources/Assets.xcassets/ic_instagram_purple.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_instagram_purple.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_instagram_purple.imageset/ic_instagram_purple.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_kick_out.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/ic_kick_out.png
vendored
Normal file
After Width: | Height: | Size: 2.5 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_message_send.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_message_send.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_message_send.imageset/ic_message_send.png
vendored
Normal file
After Width: | Height: | Size: 787 B |
@@ -9,7 +9,7 @@
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_headphones_purple.png",
|
||||
"filename" : "ic_message_square_777.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_message_square_777 1.imageset/ic_message_square_777.png
vendored
Normal file
After Width: | Height: | Size: 471 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_mic_paint.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_mic_paint.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_mic_paint.imageset/ic_mic_paint.png
vendored
Normal file
After Width: | Height: | Size: 694 B |
@@ -5,11 +5,11 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "loading_2.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_mute.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_mute.imageset/ic_mute.png
vendored
Normal file
After Width: | Height: | Size: 7.6 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_place_holder.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_place_holder.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_place_holder.imageset/ic_place_holder.png
vendored
Normal file
After Width: | Height: | Size: 70 KiB |
@@ -5,11 +5,11 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "loading_3.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_record.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_record.imageset/ic_record.png
vendored
Normal file
After Width: | Height: | Size: 2.2 KiB |
21
SodaLive/Resources/Assets.xcassets/ic_record_pause.imageset/Contents.json
vendored
Normal file
@@ -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 |
21
SodaLive/Resources/Assets.xcassets/ic_record_play.imageset/Contents.json
vendored
Normal file
@@ -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 |
21
SodaLive/Resources/Assets.xcassets/ic_record_stop.imageset/Contents.json
vendored
Normal file
@@ -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 |
@@ -5,11 +5,11 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "loading_1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_save.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_save.imageset/ic_save.png
vendored
Normal file
After Width: | Height: | Size: 443 B |
21
SodaLive/Resources/Assets.xcassets/ic_website_purple.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_website_purple.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_website_purple.imageset/ic_website_purple.png
vendored
Normal file
After Width: | Height: | Size: 926 B |
21
SodaLive/Resources/Assets.xcassets/ic_youtube_play_purple.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "ic_youtube_play_purple.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_youtube_play_purple.imageset/ic_youtube_play_purple.png
vendored
Normal file
After Width: | Height: | Size: 858 B |
21
SodaLive/Resources/Assets.xcassets/img_compleate_book.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img_compleate_book.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/img_compleate_book.imageset/img_compleate_book.png
vendored
Normal file
After Width: | Height: | Size: 21 KiB |
21
SodaLive/Resources/Assets.xcassets/img_noti_mute.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img_noti_mute.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/img_noti_mute.imageset/img_noti_mute.png
vendored
Normal file
After Width: | Height: | Size: 23 KiB |
21
SodaLive/Resources/Assets.xcassets/img_thumb_default.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "img_thumb_default.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
SodaLive/Resources/Assets.xcassets/img_thumb_default.imageset/img_thumb_default.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 5.1 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "loading_5.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -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 {
|
||||
|
@@ -105,4 +105,12 @@ enum AppStep {
|
||||
case profileUpdate(refresh: () -> Void)
|
||||
|
||||
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
|
||||
}
|
||||
|
54
SodaLive/Sources/Content/All/ContentNewAllItemView.swift
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
86
SodaLive/Sources/Content/All/ContentNewAllView.swift
Normal file
@@ -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()
|
||||
}
|
||||
}
|
130
SodaLive/Sources/Content/All/ContentNewAllViewModel.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
153
SodaLive/Sources/Content/All/ContentRankingAllView.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
11
SodaLive/Sources/Content/All/GetNewContentAllResponse.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -49,7 +49,7 @@ struct ContentListItemView: View {
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
HStack(spacing: 6) {
|
||||
Image("ic_heart")
|
||||
Image("ic_heart_777")
|
||||
.resizable()
|
||||
.frame(width: 13.3, height: 13.3)
|
||||
|
||||
@@ -77,7 +77,7 @@ struct ContentListItemView: View {
|
||||
|
||||
if item.price > 0 {
|
||||
HStack(spacing: 8) {
|
||||
Image("ic_coin_w")
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 17, height: 17)
|
||||
|
||||
|
@@ -29,8 +29,8 @@ final class ContentRepository {
|
||||
return api.requestPublisher(.registerComment(request: RegisterAudioContentCommentRequest(comment: comment, contentId: audioContentId, parentId: parentId)))
|
||||
}
|
||||
|
||||
func orderAudioContent(audioContentId: Int, orderType: OrderType) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.orderAudioContent(request: OrderRequest(audioContentId: audioContentId, orderType: orderType)))
|
||||
func orderAudioContent(contentId: Int, orderType: OrderType) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.orderAudioContent(request: OrderRequest(contentId: contentId, orderType: orderType)))
|
||||
}
|
||||
|
||||
func getOrderList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
@@ -74,6 +74,26 @@ final class ContentRepository {
|
||||
}
|
||||
|
||||
func donation(contentId: Int, can: Int, comment: String) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.donation(request: AudioContentDonationRequest(audioContentId: contentId, donationCan: can, comment: comment)))
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@@ -214,14 +214,36 @@ struct ContentCreateView: View {
|
||||
}
|
||||
|
||||
if !viewModel.isFree {
|
||||
VStack(spacing: 13.3) {
|
||||
Text("소장 설정")
|
||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
HStack(spacing: 13.3) {
|
||||
SelectButtonView(title: "소장/대여", isChecked: !viewModel.isOnlyRental) {
|
||||
if viewModel.isOnlyRental {
|
||||
viewModel.isOnlyRental = false
|
||||
}
|
||||
}
|
||||
|
||||
SelectButtonView(title: "대여만", isChecked: viewModel.isOnlyRental) {
|
||||
if !viewModel.isOnlyRental {
|
||||
viewModel.isOnlyRental = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 13.3)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("소장가격")
|
||||
Text(viewModel.isOnlyRental ? "대여 가격" : "소장 가격")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "d2d2d2"))
|
||||
.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))
|
||||
@@ -247,18 +269,18 @@ struct ContentCreateView: View {
|
||||
.frame(height: 1)
|
||||
.padding(.top, 11)
|
||||
|
||||
Text("※ 이용기간 대여 (7일) | 소장 (서비스종료시까지)")
|
||||
Text("※ 이용기간 대여 (15일) | 소장 (서비스종료시까지)")
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "777777"))
|
||||
.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 +343,63 @@ struct ContentCreateView: View {
|
||||
.padding(.top, 26.7)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
if !viewModel.isFree {
|
||||
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("등록")
|
||||
|
@@ -54,10 +54,16 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
didSet {
|
||||
if isFree {
|
||||
priceString = "0"
|
||||
isOnlyRental = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Published var isOnlyRental = false
|
||||
|
||||
@Published var previewStartTime: String = ""
|
||||
@Published var previewEndTime: String = ""
|
||||
|
||||
var placeholder = "내용을 입력하세요"
|
||||
|
||||
func uploadAudioContent() {
|
||||
@@ -71,7 +77,10 @@ final class ContentCreateViewModel: ObservableObject {
|
||||
price: price,
|
||||
themeId: theme!.id,
|
||||
isAdult: isAdult,
|
||||
isCommentAvailable: isAvailableComment
|
||||
isOnlyRental: isOnlyRental,
|
||||
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 +214,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
|
||||
}
|
||||
}
|
||||
|
@@ -14,5 +14,8 @@ struct CreateAudioContentRequest: Encodable {
|
||||
let price: Int
|
||||
let themeId: Int
|
||||
let isAdult: Bool
|
||||
let isOnlyRental: Bool
|
||||
let isCommentAvailable: Bool
|
||||
let previewStartTime: String?
|
||||
let previewEndTime: String?
|
||||
}
|
||||
|
110
SodaLive/Sources/Content/Curation/ContentCurationView.swift
Normal file
@@ -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()
|
||||
|
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
struct ContentDetailPurchaseButton: View {
|
||||
|
||||
let price: Int
|
||||
let isOnlyRental: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
@@ -26,7 +27,7 @@ struct ContentDetailPurchaseButton: View {
|
||||
.font(.custom(Font.light.rawValue, size: 12))
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text(" 구매하기")
|
||||
Text(isOnlyRental ? " 대여하기" : " 구매하기")
|
||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
@@ -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,13 +85,15 @@ struct ContentDetailView: View {
|
||||
},
|
||||
onClickDonation: { viewModel.isShowDonationPopup = true }
|
||||
)
|
||||
.padding(.horizontal, 13.3)
|
||||
|
||||
if audioContent.price > 0 &&
|
||||
!audioContent.existOrdered &&
|
||||
audioContent.orderType == nil &&
|
||||
audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
|
||||
ContentDetailPurchaseButton(price: audioContent.price)
|
||||
ContentDetailPurchaseButton(price: audioContent.price, isOnlyRental: audioContent.isOnlyRental)
|
||||
.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: "크리에이터의 다른 콘텐츠",
|
||||
@@ -165,6 +161,7 @@ struct ContentDetailView: View {
|
||||
ContentOrderDialogView(
|
||||
isShowing: $isShowOrderView,
|
||||
price: audioContent.price,
|
||||
isOnlyRental: audioContent.isOnlyRental,
|
||||
onTapPurchase: {
|
||||
viewModel.orderType = $0
|
||||
isShowOrderConfirmView = true
|
||||
@@ -190,6 +187,7 @@ struct ContentDetailView: View {
|
||||
isShowing: $isShowOrderConfirmView,
|
||||
audioContent: audioContent,
|
||||
orderType: orderType,
|
||||
isOnlyRental: audioContent.isOnlyRental,
|
||||
onClickConfirm: {
|
||||
viewModel.order(orderType: orderType)
|
||||
}
|
||||
@@ -282,6 +280,7 @@ struct ContentDetailView: View {
|
||||
content: {
|
||||
AudioContentCommentListView(
|
||||
isPresented: $isShowCommentListView,
|
||||
creatorId: viewModel.audioContent!.creator.creatorId,
|
||||
audioContentId: viewModel.audioContent!.contentId
|
||||
)
|
||||
}
|
||||
|
@@ -280,7 +280,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
isShowPreviewAlert = false
|
||||
isLoading = true
|
||||
|
||||
repository.orderAudioContent(audioContentId: contentId, orderType: orderType)
|
||||
repository.orderAudioContent(contentId: contentId, orderType: orderType)
|
||||
.sink { result in
|
||||
switch result {
|
||||
case .finished:
|
||||
@@ -298,7 +298,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||
|
||||
if decoded.success {
|
||||
self.orderType = nil
|
||||
self.errorMessage = "구매가 완료되었습니다."
|
||||
self.errorMessage = orderType == .RENTAL ? "대여가 완료되었습니다." : "구매가 완료되었습니다."
|
||||
self.isShowPopup = true
|
||||
self.getAudioContentDetail()
|
||||
ContentPlayManager.shared.conditionalStopAudio(contentId: contentId)
|
||||
|
@@ -14,6 +14,7 @@ struct ContentOrderConfirmDialogView: View {
|
||||
|
||||
let audioContent: GetAudioContentDetailResponse
|
||||
let orderType: OrderType
|
||||
let isOnlyRental: Bool
|
||||
let onClickConfirm: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -90,9 +91,15 @@ struct ContentOrderConfirmDialogView: View {
|
||||
.resizable()
|
||||
.frame(width: 16.7, height: 16.7)
|
||||
|
||||
Text("\(orderType == .RENTAL ? Int(ceil(Double(audioContent.price) * 0.7)) : audioContent.price)")
|
||||
if orderType == .RENTAL {
|
||||
Text("\(isOnlyRental ? audioContent.price : Int(ceil(Double(audioContent.price) * 0.6)))")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
} else {
|
||||
Text("\(audioContent.price)")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ struct ContentOrderDialogView: View {
|
||||
@Binding var isShowing: Bool
|
||||
|
||||
let price: Int
|
||||
let isOnlyRental: Bool
|
||||
let onTapPurchase: (OrderType) -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -31,7 +32,7 @@ struct ContentOrderDialogView: View {
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(.white)
|
||||
|
||||
Text("(이용기간 7일)")
|
||||
Text("(이용기간 15일)")
|
||||
.font(.custom(Font.light.rawValue, size: 12))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
@@ -43,7 +44,7 @@ struct ContentOrderDialogView: View {
|
||||
.resizable()
|
||||
.frame(width: 16.7, height: 16.7)
|
||||
|
||||
Text("\(Int(ceil(Double(price) * 0.7)))")
|
||||
Text(isOnlyRental ? "\(price)" : "\(Int(ceil(Double(price) * 0.6)))")
|
||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||
.foregroundColor(Color(hex: "eeeeee"))
|
||||
}
|
||||
@@ -57,6 +58,7 @@ struct ContentOrderDialogView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if !isOnlyRental {
|
||||
HStack(spacing: 0) {
|
||||
VStack(alignment: .leading, spacing: 5.3) {
|
||||
Text("소장")
|
||||
@@ -71,7 +73,7 @@ struct ContentOrderDialogView: View {
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 8) {
|
||||
Image("ic_coin_w")
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 16.7, height: 16.7)
|
||||
|
||||
@@ -89,6 +91,7 @@ struct ContentOrderDialogView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
.background(Color(hex: "222222"))
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ struct GetAudioContentDetailResponse: Decodable {
|
||||
let duration: String
|
||||
let isAdult: Bool
|
||||
let isMosaic: Bool
|
||||
let isOnlyRental: Bool
|
||||
let existOrdered: Bool
|
||||
let orderType: OrderType?
|
||||
let remainingTime: String?
|
||||
|
@@ -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() {
|
||||
|
@@ -8,7 +8,7 @@
|
||||
import Foundation
|
||||
|
||||
struct AudioContentDonationRequest: Encodable {
|
||||
let audioContentId: Int
|
||||
let contentId: Int
|
||||
let donationCan: Int
|
||||
let comment: String
|
||||
let container: String = "ios"
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -57,7 +57,6 @@ struct ContentMainItemView_Previews: PreviewProvider {
|
||||
contentId: 2,
|
||||
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
title: "ㅓ처랴햐햫햐햐",
|
||||
isAdult: true,
|
||||
creatorId: 8,
|
||||
creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||
creatorNickname: "유저2"
|
||||
|