Compare commits
52 Commits
043a583985
...
f444f0bfb0
Author | SHA1 | Date |
---|---|---|
![]() |
f444f0bfb0 | |
![]() |
ac304e7d17 | |
![]() |
3db59e6236 | |
![]() |
8aa69f02fc | |
![]() |
7c5b30335e | |
![]() |
17ead38524 | |
![]() |
bc8d3e6d70 | |
![]() |
66ce37defa | |
![]() |
7408288d85 | |
![]() |
2bea2365a0 | |
![]() |
f4d95e6755 | |
![]() |
30a480232b | |
![]() |
468a876e33 | |
![]() |
0f1de35e62 | |
![]() |
24f09d068d | |
![]() |
d1a90ad599 | |
![]() |
fa849dd5b6 | |
![]() |
ca9dee5574 | |
![]() |
f5445a3c48 | |
![]() |
534a6e737e | |
![]() |
81846f7f7b | |
![]() |
47cd685f80 | |
![]() |
835ece8a6b | |
![]() |
ef3494dcb1 | |
![]() |
85a871693c | |
![]() |
0be8d0d98a | |
![]() |
22ab76d664 | |
![]() |
a3beb9c9fe | |
![]() |
e41549ea07 | |
![]() |
89dc0b4540 | |
![]() |
b17a037b0a | |
![]() |
c1d63675db | |
![]() |
6d83869cae | |
![]() |
6884a060e4 | |
![]() |
313af1949e | |
![]() |
9f402c8ec8 | |
![]() |
45b600ac41 | |
![]() |
e1d42b5495 | |
![]() |
03d86ee2d5 | |
![]() |
9533e97f89 | |
![]() |
5fc0f1789a | |
![]() |
9819f00d0d | |
![]() |
27ee83f74b | |
![]() |
987c96bee8 | |
![]() |
76c20c2658 | |
![]() |
db828cf44d | |
![]() |
ee1664cf19 | |
![]() |
0a1b1865dd | |
![]() |
e7cbabb285 | |
![]() |
3ae5ea776c | |
![]() |
0a96509b35 | |
![]() |
2268b0c1bc |
4
Podfile
|
@ -6,7 +6,7 @@ target 'SodaLive' do
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
|
|
||||||
# Pods for SodaLive
|
# Pods for SodaLive
|
||||||
pod 'BootpayUI', '4.3.0'
|
pod 'BootpayUI', '4.4.0'
|
||||||
pod 'ObjectBox'
|
pod 'ObjectBox'
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -16,7 +16,7 @@ target 'SodaLive-dev' do
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
|
|
||||||
# Pods for SodaLive-dev
|
# Pods for SodaLive-dev
|
||||||
pod 'BootpayUI', '4.3.0'
|
pod 'BootpayUI', '4.4.0'
|
||||||
pod 'ObjectBox'
|
pod 'ObjectBox'
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
42
Podfile.lock
|
@ -1,27 +1,31 @@
|
||||||
PODS:
|
PODS:
|
||||||
- Alamofire (5.7.1)
|
- Alamofire (5.9.1)
|
||||||
- Bootpay (4.2.9):
|
- Bootpay (4.4.4):
|
||||||
- CryptoSwift
|
- CryptoSwift
|
||||||
|
- NVActivityIndicatorView
|
||||||
- ObjectMapper
|
- ObjectMapper
|
||||||
- BootpayUI (4.3.0):
|
- BootpayUI (4.4.0):
|
||||||
- Alamofire
|
- Alamofire
|
||||||
- Bootpay (~> 4.2.8)
|
- Bootpay (~> 4.4.0)
|
||||||
- CryptoSwift
|
- CryptoSwift
|
||||||
- JGProgressHUD
|
- JGProgressHUD
|
||||||
- ObjectMapper
|
- ObjectMapper
|
||||||
- SCLAlertView
|
- SCLAlertView
|
||||||
- SnapKit
|
- SnapKit
|
||||||
- SwiftyJSON
|
- SwiftyJSON
|
||||||
- CryptoSwift (1.7.1)
|
- CryptoSwift (1.8.3)
|
||||||
- JGProgressHUD (2.2)
|
- JGProgressHUD (2.2)
|
||||||
|
- NVActivityIndicatorView (5.2.0):
|
||||||
|
- NVActivityIndicatorView/Base (= 5.2.0)
|
||||||
|
- NVActivityIndicatorView/Base (5.2.0)
|
||||||
- ObjectBox (1.8.1)
|
- ObjectBox (1.8.1)
|
||||||
- ObjectMapper (4.2.0)
|
- ObjectMapper (4.4.2)
|
||||||
- SCLAlertView (0.8)
|
- SCLAlertView (0.8)
|
||||||
- SnapKit (5.6.0)
|
- SnapKit (5.7.1)
|
||||||
- SwiftyJSON (5.0.1)
|
- SwiftyJSON (5.0.2)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- BootpayUI (= 4.3.0)
|
- BootpayUI (= 4.4.0)
|
||||||
- ObjectBox
|
- ObjectBox
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
|
@ -31,6 +35,7 @@ SPEC REPOS:
|
||||||
- BootpayUI
|
- BootpayUI
|
||||||
- CryptoSwift
|
- CryptoSwift
|
||||||
- JGProgressHUD
|
- JGProgressHUD
|
||||||
|
- NVActivityIndicatorView
|
||||||
- ObjectBox
|
- ObjectBox
|
||||||
- ObjectMapper
|
- ObjectMapper
|
||||||
- SCLAlertView
|
- SCLAlertView
|
||||||
|
@ -38,17 +43,18 @@ SPEC REPOS:
|
||||||
- SwiftyJSON
|
- SwiftyJSON
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Alamofire: 0123a34370cb170936ae79a8df46cc62b2edeb88
|
Alamofire: f36a35757af4587d8e4f4bfa223ad10be2422b8c
|
||||||
Bootpay: d753088334a16ce99094142beb66a6610a15d84b
|
Bootpay: ed9b04d0061931d4bb0c6a2e14dc44222168fde6
|
||||||
BootpayUI: 54dcbe59a23e0d91b07a8add8115e1a6deace0f0
|
BootpayUI: 58e4c9a23ffb65b8023ef9f3dcb1d70090599e69
|
||||||
CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1
|
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
||||||
JGProgressHUD: d83d7a981b85d11205e19ff8ad5bb9c40571c847
|
JGProgressHUD: d83d7a981b85d11205e19ff8ad5bb9c40571c847
|
||||||
|
NVActivityIndicatorView: fe52a6a68664c2df8991d7d9e3d86d8d19453c53
|
||||||
ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4
|
ObjectBox: a7900d5335218cd437cbc080b7ccc38a5211f7b4
|
||||||
ObjectMapper: 1eb41f610210777375fa806bf161dc39fb832b81
|
ObjectMapper: e6e4d91ff7f2861df7aecc536c92d8363f4c9677
|
||||||
SCLAlertView: 6a77bb2edfc65e04dbe57725546cb4107a506b85
|
SCLAlertView: 6a77bb2edfc65e04dbe57725546cb4107a506b85
|
||||||
SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25
|
SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
|
||||||
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
|
SwiftyJSON: f5b1bf1cd8dd53cd25887ac0eabcfd92301c6a5a
|
||||||
|
|
||||||
PODFILE CHECKSUM: cdff30c96e85662f4de75ddd8d54358311c1e629
|
PODFILE CHECKSUM: 48980f586cd82e7704768a64fd3ca78a3c3cb0c6
|
||||||
|
|
||||||
COCOAPODS: 1.14.3
|
COCOAPODS: 1.15.2
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "launcher_icon_1024px.png",
|
"filename" : "launcher_1024x1024.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"platform" : "ios",
|
"platform" : "ios",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
|
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.2 KiB |
21
SodaLive/Resources/Assets.xcassets/btn_square_select_checked.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "btn_square_select_checked.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/btn_square_select_checked.imageset/btn_square_select_checked.png
vendored
Normal file
After Width: | Height: | Size: 888 B |
21
SodaLive/Resources/Assets.xcassets/btn_square_select_normal.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "btn_square_select_normal.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/btn_square_select_normal.imageset/btn_square_select_normal.png
vendored
Normal file
After Width: | Height: | Size: 835 B |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_content_keep.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_content_keep.imageset/ic_content_keep.png
vendored
Normal file
After Width: | Height: | Size: 824 B |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "splash_text_2.png",
|
"filename" : "ic_kakaopay.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
After Width: | Height: | Size: 3.7 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_lock_bb.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.6 MiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "splash_bg.png",
|
"filename" : "splash_bg.jpg",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 3.3 MiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 3.9 KiB |
21
SodaLive/Resources/Assets.xcassets/splash/splash_title.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "splash_title.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/splash/splash_title.imageset/splash_title.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
|
@ -4,11 +4,212 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>GADApplicationIdentifier</key>
|
||||||
|
<string>ca-app-pub-1299501215847962~8852459715</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>SKAdNetworkItems</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>cstr6suwn9.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4fzdc2evr5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4pfyvq9l8r.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>2fnua5tdw4.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ydx93a7ass.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>5a6flpkh64.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>p78axxw29g.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>v72qych5uu.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ludvb6z3bs.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>cp8zw746q7.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3sh42y64q3.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>c6k4g5qg8m.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>s39g8k73mm.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3qy4746246.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>f38h382jlk.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>hs6bdukanm.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>v4nxqhlyqp.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>wzmmz9fp6w.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>yclnxrl5pm.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>t38b2kh725.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>7ug5zh24hu.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>gta9lk7p23.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>vutu7akeur.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>y5ghdn5j9k.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>n6fk4nfna4.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>v9wttpbfk9.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>n38lu8286q.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>47vhws6wlr.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>kbd757ywx3.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>9t245vhmpl.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>eh6m2bh4zr.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>a2p9lx4jpn.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>22mmun2rn5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4468km3ulz.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>2u9pt9hc89.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>8s468mfl3y.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>klf5c3l5u5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ppxm28t8ap.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>ecpz2srf59.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>uw77j35x4d.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>pwa73g5rt2.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>mlmmfzh3r3.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>578prtvx9j.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>4dzt52r2t5.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>e5fvkxwrpn.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>8c4e2ghe7u.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>zq492l623r.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3rd42ekr43.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>SKAdNetworkIdentifier</key>
|
||||||
|
<string>3qcr597p9d.skadnetwork</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>UIAppFonts</key>
|
<key>UIAppFonts</key>
|
||||||
<array>
|
<array>
|
||||||
<string>gmarket_sans_bold.otf</string>
|
<string>gmarket_sans_bold.otf</string>
|
||||||
|
@ -21,206 +222,5 @@
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
<string>remote-notification</string>
|
<string>remote-notification</string>
|
||||||
</array>
|
</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>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -111,6 +111,23 @@ final class Agora {
|
||||||
rtmKit?.send(message, toPeer: peerId, completion: completion)
|
rtmKit?.send(message, toPeer: peerId, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendRawMessageToPeer(peerId: String, rawMessage: LiveRoomChatRawMessage, completion: AgoraRtmSendPeerMessageBlock? = nil, fail: (() -> Void)? = nil) {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
let jsonMessageData = try? encoder.encode(rawMessage)
|
||||||
|
let option = AgoraRtmSendMessageOptions()
|
||||||
|
option.enableOfflineMessaging = false
|
||||||
|
option.enableHistoricalMessaging = false
|
||||||
|
|
||||||
|
if let jsonMessageData = jsonMessageData {
|
||||||
|
let message = AgoraRtmRawMessage(rawData: jsonMessageData, description: "")
|
||||||
|
rtmKit?.send(message, toPeer: peerId, sendMessageOptions: option, completion: completion)
|
||||||
|
} else {
|
||||||
|
if let fail = fail {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mute(_ isMute: Bool) {
|
func mute(_ isMute: Bool) {
|
||||||
rtcEngine?.muteLocalAudioStream(isMute)
|
rtcEngine?.muteLocalAudioStream(isMute)
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,4 +129,6 @@ enum AppStep {
|
||||||
case seriesContentAll(seriesId: Int, seriesTitle: String)
|
case seriesContentAll(seriesId: Int, seriesTitle: String)
|
||||||
|
|
||||||
case tempCanPayment(orderType: OrderType, contentId: Int, title: String, can: Int)
|
case tempCanPayment(orderType: OrderType, contentId: Int, title: String, can: Int)
|
||||||
|
|
||||||
|
case blockList
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,9 @@ struct SodaLiveApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in
|
||||||
|
CreatorCommunityMediaPlayerManager.shared.pauseContent()
|
||||||
|
}
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
|
||||||
UIApplication.shared.applicationIconBadgeNumber = 0
|
UIApplication.shared.applicationIconBadgeNumber = 0
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,34 @@ struct ContentListItemView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if item.price > 0 {
|
if item.isOwned {
|
||||||
|
Text("소장중")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.gray11)
|
||||||
|
.padding(.horizontal, 5.3)
|
||||||
|
.padding(.vertical, 2.7)
|
||||||
|
.background(Color(hex: "b1ef2c"))
|
||||||
|
.cornerRadius(2.6)
|
||||||
|
} else if item.isRented {
|
||||||
|
Text("대여중")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.padding(.horizontal, 5.3)
|
||||||
|
.padding(.vertical, 2.7)
|
||||||
|
.background(Color(hex: "660fd4"))
|
||||||
|
.cornerRadius(2.6)
|
||||||
|
} else if item.isSoldOut {
|
||||||
|
Text("Sold Out")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayd2)
|
||||||
|
.padding(.horizontal, 5.3)
|
||||||
|
.padding(.vertical, 2.7)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 2.6)
|
||||||
|
.stroke(Color.grayd2, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.cornerRadius(2.6)
|
||||||
|
} else if item.price > 0 {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Image("ic_can")
|
Image("ic_can")
|
||||||
.resizable()
|
.resizable()
|
||||||
|
@ -130,7 +157,10 @@ struct ContentListItemView_Previews: PreviewProvider {
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
isPin: true,
|
isPin: true,
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
isScheduledToOpen: true
|
isScheduledToOpen: true,
|
||||||
|
isRented: false,
|
||||||
|
isOwned: false,
|
||||||
|
isSoldOut: true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ extension ContentPlayManager {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let audioSession = AVAudioSession.sharedInstance()
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
try audioSession.setCategory(.playback, mode: .default)
|
try audioSession.setCategory(.playback, mode: .moviePlayback)
|
||||||
try audioSession.setActive(true)
|
try audioSession.setActive(true)
|
||||||
|
|
||||||
self.player = try AVAudioPlayer(data: audioData)
|
self.player = try AVAudioPlayer(data: audioData)
|
||||||
|
|
|
@ -26,8 +26,8 @@ final class ContentRepository {
|
||||||
return api.requestPublisher(.likeContent(request: PutAudioContentLikeRequest(contentId: audioContentId)))
|
return api.requestPublisher(.likeContent(request: PutAudioContentLikeRequest(contentId: audioContentId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerComment(audioContentId: Int, comment: String, parentId: Int? = nil) -> AnyPublisher<Response, MoyaError> {
|
func registerComment(audioContentId: Int, comment: String, parentId: Int? = nil, isSecret: Bool = false) -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.registerComment(request: RegisterAudioContentCommentRequest(comment: comment, contentId: audioContentId, parentId: parentId)))
|
return api.requestPublisher(.registerComment(request: RegisterAudioContentCommentRequest(comment: comment, contentId: audioContentId, parentId: parentId, isSecret: isSecret)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func orderAudioContent(contentId: Int, orderType: OrderType) -> AnyPublisher<Response, MoyaError> {
|
func orderAudioContent(contentId: Int, orderType: OrderType) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
|
|
@ -219,7 +219,7 @@ struct ContentCreateView: View {
|
||||||
VStack(spacing: 13.3) {
|
VStack(spacing: 13.3) {
|
||||||
Text("소장 설정")
|
Text("소장 설정")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -241,7 +241,7 @@ struct ContentCreateView: View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Text(viewModel.isOnlyRental ? "대여 가격" : "소장 가격")
|
Text(viewModel.isOnlyRental ? "대여 가격" : "소장 가격")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "d2d2d2"))
|
.foregroundColor(Color.grayd2)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
@ -249,7 +249,7 @@ struct ContentCreateView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
.padding(.trailing, 10)
|
.padding(.trailing, 10)
|
||||||
|
@ -258,16 +258,16 @@ struct ContentCreateView: View {
|
||||||
|
|
||||||
Text("캔")
|
Text("캔")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
.padding(.vertical, 17)
|
.padding(.vertical, 17)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(5.3)
|
.cornerRadius(5.3)
|
||||||
.padding(.top, 5.3)
|
.padding(.top, 5.3)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "232323"))
|
.foregroundColor(Color.gray23)
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.padding(.top, 11)
|
.padding(.top, 11)
|
||||||
|
|
||||||
|
@ -289,6 +289,44 @@ struct ContentCreateView: View {
|
||||||
}
|
}
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
|
if viewModel.price > 0 && !viewModel.isOnlyRental {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
Text("한정판 설정")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
SelectButtonView(title: "무제한", isChecked: !viewModel.isLimited) {
|
||||||
|
if viewModel.isLimited {
|
||||||
|
viewModel.isLimited = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectButtonView(title: "한정판", isChecked: viewModel.isLimited) {
|
||||||
|
if !viewModel.isLimited {
|
||||||
|
viewModel.isLimited = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.isLimited {
|
||||||
|
TextField("한정판 개수를 입력하세요", text: $viewModel.limitedString)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.cornerRadius(6.7)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
.padding(.vertical, 17)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 26.7)
|
||||||
|
}
|
||||||
|
|
||||||
VStack(spacing: 13.3) {
|
VStack(spacing: 13.3) {
|
||||||
Text("미리듣기")
|
Text("미리듣기")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
|
|
@ -55,15 +55,48 @@ final class ContentCreateViewModel: ObservableObject {
|
||||||
didSet {
|
didSet {
|
||||||
if isFree {
|
if isFree {
|
||||||
priceString = "0"
|
priceString = "0"
|
||||||
|
isLimited = false
|
||||||
isOnlyRental = false
|
isOnlyRental = false
|
||||||
isGeneratePreview = true
|
isGeneratePreview = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var isOnlyRental = false
|
@Published var isOnlyRental = false {
|
||||||
|
didSet {
|
||||||
|
if isOnlyRental {
|
||||||
|
isLimited = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Published var isGeneratePreview = true
|
@Published var isGeneratePreview = true
|
||||||
|
|
||||||
|
@Published var isLimited = false {
|
||||||
|
didSet {
|
||||||
|
if !isLimited {
|
||||||
|
limitedString = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var limited: Int? = nil {
|
||||||
|
didSet {
|
||||||
|
if let limited = limited, limited <= 0 {
|
||||||
|
limitedString = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var limitedString: String = "" {
|
||||||
|
didSet {
|
||||||
|
if limitedString == "" {
|
||||||
|
limited = nil
|
||||||
|
} else {
|
||||||
|
limited = Int(limitedString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Published var previewStartTime: String = ""
|
@Published var previewStartTime: String = ""
|
||||||
@Published var previewEndTime: String = ""
|
@Published var previewEndTime: String = ""
|
||||||
@Published var releaseDateString: String = Date().convertDateFormat(dateFormat: "yyyy.MM.dd")
|
@Published var releaseDateString: String = Date().convertDateFormat(dateFormat: "yyyy.MM.dd")
|
||||||
|
@ -92,6 +125,7 @@ final class ContentCreateViewModel: ObservableObject {
|
||||||
detail: detail,
|
detail: detail,
|
||||||
tags: hashtags,
|
tags: hashtags,
|
||||||
price: price,
|
price: price,
|
||||||
|
limited: limited,
|
||||||
releaseDate: isActiveReservation ? "\(releaseDate.convertDateFormat(dateFormat: "yyyy-MM-dd")) \(releaseTime.convertDateFormat(dateFormat: "HH:mm"))" : nil,
|
releaseDate: isActiveReservation ? "\(releaseDate.convertDateFormat(dateFormat: "yyyy-MM-dd")) \(releaseTime.convertDateFormat(dateFormat: "HH:mm"))" : nil,
|
||||||
timezone: TimeZone.current.identifier,
|
timezone: TimeZone.current.identifier,
|
||||||
themeId: theme!.id,
|
themeId: theme!.id,
|
||||||
|
|
|
@ -12,6 +12,7 @@ struct CreateAudioContentRequest: Encodable {
|
||||||
let detail: String
|
let detail: String
|
||||||
let tags: String
|
let tags: String
|
||||||
let price: Int
|
let price: Int
|
||||||
|
let limited: Int?
|
||||||
let releaseDate: String?
|
let releaseDate: String?
|
||||||
let timezone: String
|
let timezone: String
|
||||||
let themeId: Int
|
let themeId: Int
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct AudioContentCommentItemView: View {
|
||||||
|
|
||||||
let modifyComment: (Int, String) -> Void
|
let modifyComment: (Int, String) -> Void
|
||||||
let onClickDelete: (Int) -> Void
|
let onClickDelete: (Int) -> Void
|
||||||
|
let onClickProfile: (Int) -> Void
|
||||||
|
|
||||||
@State var isShowPopupMenu: Bool = false
|
@State var isShowPopupMenu: Bool = false
|
||||||
@State var isModeModify: Bool = false
|
@State var isModeModify: Bool = false
|
||||||
|
@ -30,15 +31,32 @@ struct AudioContentCommentItemView: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 40, height: 40)
|
.frame(width: 40, height: 40)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
.onTapGesture {
|
||||||
|
if UserDefaults.int(forKey: .userId) != commentItem.writerId {
|
||||||
|
onClickProfile(commentItem.writerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(commentItem.nickname)
|
HStack(spacing: 6.7) {
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
Text(commentItem.nickname)
|
||||||
.foregroundColor(Color.gray90)
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color.gray90)
|
||||||
|
|
||||||
|
if commentItem.isSecret {
|
||||||
|
Text("비밀댓글")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
.padding(.vertical, 2)
|
||||||
|
.background(Color.button.opacity(0.2))
|
||||||
|
.cornerRadius(3.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(commentItem.date)
|
Text(commentItem.date)
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.3))
|
.font(.custom(Font.medium.rawValue, size: 10.3))
|
||||||
.foregroundColor(Color(hex: "525252"))
|
.foregroundColor(Color.gray52)
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,8 +101,8 @@ struct AudioContentCommentItemView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
@ -102,7 +120,7 @@ struct AudioContentCommentItemView: View {
|
||||||
isModeModify = false
|
isModeModify = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
@ -139,7 +157,7 @@ struct AudioContentCommentItemView: View {
|
||||||
.padding(.leading, 46.7)
|
.padding(.leading, 46.7)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "595959"))
|
.foregroundColor(Color.gray59)
|
||||||
.frame(height: 0.5)
|
.frame(height: 0.5)
|
||||||
.padding(.top, 16.7)
|
.padding(.top, 16.7)
|
||||||
}
|
}
|
||||||
|
@ -149,7 +167,7 @@ struct AudioContentCommentItemView: View {
|
||||||
if commentItem.writerId == UserDefaults.int(forKey: .userId) {
|
if commentItem.writerId == UserDefaults.int(forKey: .userId) {
|
||||||
Text("수정")
|
Text("수정")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14))
|
.font(.custom(Font.medium.rawValue, size: 14))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isModeModify = true
|
isModeModify = true
|
||||||
isShowPopupMenu = false
|
isShowPopupMenu = false
|
||||||
|
@ -161,7 +179,7 @@ struct AudioContentCommentItemView: View {
|
||||||
{
|
{
|
||||||
Text("삭제")
|
Text("삭제")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14))
|
.font(.custom(Font.medium.rawValue, size: 14))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
onClickDelete(commentItem.id)
|
onClickDelete(commentItem.id)
|
||||||
isShowPopupMenu = false
|
isShowPopupMenu = false
|
||||||
|
@ -169,7 +187,7 @@ struct AudioContentCommentItemView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear { comment = commentItem.comment }
|
.onAppear { comment = commentItem.comment }
|
||||||
|
|
|
@ -14,12 +14,16 @@ struct AudioContentCommentListView: View {
|
||||||
|
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
let audioContentId: Int
|
let audioContentId: Int
|
||||||
|
let isShowSecret: Bool
|
||||||
|
|
||||||
@StateObject var viewModel = AudioContentCommentListViewModel()
|
@StateObject var viewModel = AudioContentCommentListViewModel()
|
||||||
|
|
||||||
@State private var commentId: Int = 0
|
@State private var commentId: Int = 0
|
||||||
@State private var isShowDeletePopup: Bool = false
|
@State private var isShowDeletePopup: Bool = false
|
||||||
|
|
||||||
|
@State private var memberId: Int = 0
|
||||||
|
@State private var isShowMemberProfilePopup: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -50,6 +54,28 @@ struct AudioContentCommentListView: View {
|
||||||
.padding(.bottom, 13.3)
|
.padding(.bottom, 13.3)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
if isShowSecret {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(viewModel.isSecret ? "btn_square_select_checked" : "btn_square_select_normal")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.isSecret.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("비밀댓글")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(viewModel.isSecret ? Color.button : Color.grayee)
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.isSecret.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 13.3)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
KFImage(URL(string: UserDefaults.string(forKey: .profileImage)))
|
KFImage(URL(string: UserDefaults.string(forKey: .profileImage)))
|
||||||
.cancelOnDisappear(true)
|
.cancelOnDisappear(true)
|
||||||
|
@ -63,8 +89,8 @@ struct AudioContentCommentListView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
@ -79,20 +105,18 @@ struct AudioContentCommentListView: View {
|
||||||
viewModel.registerComment()
|
viewModel.registerComment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer()
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "595959"))
|
.foregroundColor(Color.gray59)
|
||||||
.frame(height: 0.5)
|
.frame(height: 0.5)
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
.padding(.bottom, 13.3)
|
.padding(.bottom, 13.3)
|
||||||
|
@ -116,6 +140,10 @@ struct AudioContentCommentListView: View {
|
||||||
onClickDelete: {
|
onClickDelete: {
|
||||||
commentId = $0
|
commentId = $0
|
||||||
isShowDeletePopup = true
|
isShowDeletePopup = true
|
||||||
|
},
|
||||||
|
onClickProfile: {
|
||||||
|
memberId = $0
|
||||||
|
isShowMemberProfilePopup = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 26.7)
|
.padding(.horizontal, 26.7)
|
||||||
|
@ -148,6 +176,10 @@ struct AudioContentCommentListView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMemberProfilePopup {
|
||||||
|
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
|
||||||
|
}
|
||||||
|
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
LoadingView()
|
LoadingView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||||
@Published var totalCommentCount = 0
|
@Published var totalCommentCount = 0
|
||||||
@Published var commentList = [GetAudioContentCommentListItem]()
|
@Published var commentList = [GetAudioContentCommentListItem]()
|
||||||
|
|
||||||
|
@Published var isSecret = false
|
||||||
|
|
||||||
var audioContentId = 0
|
var audioContentId = 0
|
||||||
var page = 1
|
var page = 1
|
||||||
var isLast = false
|
var isLast = false
|
||||||
|
@ -84,7 +86,7 @@ class AudioContentCommentListViewModel: ObservableObject {
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
repository.registerComment(audioContentId: audioContentId, comment: comment)
|
repository.registerComment(audioContentId: audioContentId, comment: comment, isSecret: isSecret)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
|
|
@ -20,6 +20,9 @@ struct AudioContentListReplyView: View {
|
||||||
@State private var commentId: Int = 0
|
@State private var commentId: Int = 0
|
||||||
@State private var isShowDeletePopup: Bool = false
|
@State private var isShowDeletePopup: Bool = false
|
||||||
|
|
||||||
|
@State private var memberId: Int = 0
|
||||||
|
@State private var isShowMemberProfilePopup: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
@ -98,7 +101,11 @@ struct AudioContentListReplyView: View {
|
||||||
isReplyComment: true,
|
isReplyComment: true,
|
||||||
isShowPopupMenuButton: false,
|
isShowPopupMenuButton: false,
|
||||||
modifyComment: { _, _ in },
|
modifyComment: { _, _ in },
|
||||||
onClickDelete: { _ in }
|
onClickDelete: { _ in },
|
||||||
|
onClickProfile: {
|
||||||
|
memberId = $0
|
||||||
|
isShowMemberProfilePopup = true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 26.7)
|
.padding(.horizontal, 26.7)
|
||||||
.padding(.bottom, 13.3)
|
.padding(.bottom, 13.3)
|
||||||
|
@ -120,6 +127,10 @@ struct AudioContentListReplyView: View {
|
||||||
onClickDelete: {
|
onClickDelete: {
|
||||||
commentId = $0
|
commentId = $0
|
||||||
isShowDeletePopup = true
|
isShowDeletePopup = true
|
||||||
|
},
|
||||||
|
onClickProfile: {
|
||||||
|
memberId = $0
|
||||||
|
isShowMemberProfilePopup = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 40)
|
.padding(.horizontal, 40)
|
||||||
|
@ -153,6 +164,10 @@ struct AudioContentListReplyView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMemberProfilePopup {
|
||||||
|
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
|
||||||
|
}
|
||||||
|
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
LoadingView()
|
LoadingView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,12 @@ struct ContentDetailCommentView: View {
|
||||||
|
|
||||||
let commentCount: Int
|
let commentCount: Int
|
||||||
let commentList: [GetAudioContentCommentListItem]
|
let commentList: [GetAudioContentCommentListItem]
|
||||||
|
let isShowSecret: Bool
|
||||||
|
|
||||||
let registerComment: (String) -> Void
|
let registerComment: (String, Bool) -> Void
|
||||||
|
|
||||||
@State private var comment = ""
|
@State private var comment = ""
|
||||||
|
@State private var isSecret = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 10.3) {
|
VStack(alignment: .leading, spacing: 10.3) {
|
||||||
|
@ -26,9 +28,24 @@ struct ContentDetailCommentView: View {
|
||||||
|
|
||||||
Text("\(commentCount)")
|
Text("\(commentCount)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "909090"))
|
.foregroundColor(Color.gray90)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if isShowSecret && commentCount <= 0 {
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(isSecret ? "btn_square_select_checked" : "btn_square_select_normal")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
|
||||||
|
Text("비밀댓글")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(isSecret ? Color.button : Color.grayee)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
isSecret.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
|
@ -48,7 +65,7 @@ struct ContentDetailCommentView: View {
|
||||||
if commentCount > 0 {
|
if commentCount > 0 {
|
||||||
Text(commentList[0].comment)
|
Text(commentList[0].comment)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.lineSpacing(8)
|
.lineSpacing(8)
|
||||||
.padding(.leading, 3)
|
.padding(.leading, 3)
|
||||||
|
@ -58,8 +75,8 @@ struct ContentDetailCommentView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
@ -71,15 +88,15 @@ struct ContentDetailCommentView: View {
|
||||||
.padding(6.7)
|
.padding(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
registerComment(comment)
|
registerComment(comment, isSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct GetAudioContentCommentListItem: Decodable {
|
||||||
let nickname: String
|
let nickname: String
|
||||||
let profileUrl: String
|
let profileUrl: String
|
||||||
let comment: String
|
let comment: String
|
||||||
|
let isSecret: Bool
|
||||||
let donationCan: Int
|
let donationCan: Int
|
||||||
let date: String
|
let date: String
|
||||||
let replyCount: Int
|
let replyCount: Int
|
||||||
|
|
|
@ -11,4 +11,5 @@ struct RegisterAudioContentCommentRequest: Encodable {
|
||||||
let comment: String
|
let comment: String
|
||||||
let contentId: Int
|
let contentId: Int
|
||||||
let parentId: Int?
|
let parentId: Int?
|
||||||
|
let isSecret: Bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,8 +136,9 @@ struct ContentDetailView: View {
|
||||||
ContentDetailCommentView(
|
ContentDetailCommentView(
|
||||||
commentCount: audioContent.commentCount,
|
commentCount: audioContent.commentCount,
|
||||||
commentList: audioContent.commentList,
|
commentList: audioContent.commentList,
|
||||||
registerComment: { comment in
|
isShowSecret: audioContent.existOrdered,
|
||||||
self.viewModel.registerComment(comment: comment)
|
registerComment: { comment, isSecret in
|
||||||
|
self.viewModel.registerComment(comment: comment, isSecret: isSecret)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(10.3)
|
.padding(10.3)
|
||||||
|
@ -233,7 +234,7 @@ struct ContentDetailView: View {
|
||||||
orderType: orderType,
|
orderType: orderType,
|
||||||
contentId: audioContent.contentId,
|
contentId: audioContent.contentId,
|
||||||
title: audioContent.title,
|
title: audioContent.title,
|
||||||
can: orderType == .RENTAL ? Int(ceil(Double(audioContent.price) * 0.6)) : audioContent.price
|
can: !audioContent.isOnlyRental && orderType == .RENTAL ? Int(ceil(Double(audioContent.price) * 0.6)) : audioContent.price
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -323,7 +324,7 @@ struct ContentDetailView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if viewModel.isShowDonationPopup {
|
if viewModel.isShowDonationPopup {
|
||||||
LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: true) { can, comment in
|
LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: true) { can, comment, _ in
|
||||||
viewModel.donation(can: can, comment: comment)
|
viewModel.donation(can: can, comment: comment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -360,7 +361,8 @@ struct ContentDetailView: View {
|
||||||
AudioContentCommentListView(
|
AudioContentCommentListView(
|
||||||
isPresented: $isShowCommentListView,
|
isPresented: $isShowCommentListView,
|
||||||
creatorId: viewModel.audioContent!.creator.creatorId,
|
creatorId: viewModel.audioContent!.creator.creatorId,
|
||||||
audioContentId: viewModel.audioContent!.contentId
|
audioContentId: viewModel.audioContent!.contentId,
|
||||||
|
isShowSecret: viewModel.audioContent!.existOrdered
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -239,14 +239,14 @@ final class ContentDetailViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerComment(comment: String) {
|
func registerComment(comment: String, isSecret: Bool) {
|
||||||
if comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
if comment.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
repository.registerComment(audioContentId: contentId, comment: comment)
|
repository.registerComment(audioContentId: contentId, comment: comment, isSecret: isSecret)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
|
|
@ -46,9 +46,15 @@ struct ContentOrderDialogView: View {
|
||||||
.frame(width: 16.7, height: 16.7)
|
.frame(width: 16.7, height: 16.7)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(isOnlyRental ? "\(price * 110)" : "\(Int(ceil(Double(price) * 0.6)) * 110)")
|
if UserDefaults.int(forKey: .userId) == 17958 {
|
||||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
Text(isOnlyRental ? "\(price * 110)" : "\(Int(ceil(Double(price) * 0.6)) * 110)")
|
||||||
.foregroundColor(Color.grayee)
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
} else {
|
||||||
|
Text(isOnlyRental ? "\(price)" : "\(Int(ceil(Double(price) * 0.6)))")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
}
|
||||||
|
|
||||||
if UserDefaults.int(forKey: .userId) == 17958 {
|
if UserDefaults.int(forKey: .userId) == 17958 {
|
||||||
Text("원")
|
Text("원")
|
||||||
|
@ -87,9 +93,15 @@ struct ContentOrderDialogView: View {
|
||||||
.frame(width: 16.7, height: 16.7)
|
.frame(width: 16.7, height: 16.7)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("\(price * 110)")
|
if UserDefaults.int(forKey: .userId) == 17958 {
|
||||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
Text("\(price * 110)")
|
||||||
.foregroundColor(Color.grayee)
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
} else {
|
||||||
|
Text("\(price)")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
}
|
||||||
|
|
||||||
if UserDefaults.int(forKey: .userId) == 17958 {
|
if UserDefaults.int(forKey: .userId) == 17958 {
|
||||||
Text("원")
|
Text("원")
|
||||||
|
|
|
@ -18,10 +18,11 @@ struct LiveRoomDonationDialogView: View {
|
||||||
@State private var donationMessage = ""
|
@State private var donationMessage = ""
|
||||||
@State private var isShowErrorPopup = false
|
@State private var isShowErrorPopup = false
|
||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
|
@State private var isSecret = false
|
||||||
|
|
||||||
@Binding var isShowing: Bool
|
@Binding var isShowing: Bool
|
||||||
let isAudioContentDonation: Bool
|
let isAudioContentDonation: Bool
|
||||||
let onClickDonation: (Int, String) -> Void
|
let onClickDonation: (Int, String, Bool) -> Void
|
||||||
|
|
||||||
@StateObject var keyboardHandler = KeyboardHandler()
|
@StateObject var keyboardHandler = KeyboardHandler()
|
||||||
|
|
||||||
|
@ -82,6 +83,27 @@ struct LiveRoomDonationDialogView: View {
|
||||||
.foregroundColor(Color.gray90)
|
.foregroundColor(Color.gray90)
|
||||||
.padding(.top, 16)
|
.padding(.top, 16)
|
||||||
|
|
||||||
|
if !isAudioContentDonation {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Image(isSecret ? "btn_square_select_checked" : "btn_square_select_normal")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
|
||||||
|
Text("비밀후원")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
|
.foregroundColor(isSecret ? Color.button : Color.grayee)
|
||||||
|
}
|
||||||
|
.onTapGesture {
|
||||||
|
isSecret.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.top, 16)
|
||||||
|
}
|
||||||
|
|
||||||
TextField("몇 캔을 후원할까요?", text: $donationCan)
|
TextField("몇 캔을 후원할까요?", text: $donationCan)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color.grayee)
|
.foregroundColor(Color.grayee)
|
||||||
|
@ -221,7 +243,7 @@ struct LiveRoomDonationDialogView: View {
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty,
|
if !donationCan.trimmingCharacters(in: .whitespaces).isEmpty,
|
||||||
let can = Int(donationCan) {
|
let can = Int(donationCan) {
|
||||||
onClickDonation(can, donationMessage)
|
onClickDonation(can, donationMessage, isSecret)
|
||||||
isShowing = false
|
isShowing = false
|
||||||
} else {
|
} else {
|
||||||
errorMessage = "1캔 이상 후원하실 수 있습니다."
|
errorMessage = "1캔 이상 후원하실 수 있습니다."
|
||||||
|
|
|
@ -17,19 +17,28 @@ struct ContentMainView: View {
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text("콘텐츠 마켓")
|
HStack(spacing: 0) {
|
||||||
.font(.custom(Font.bold.rawValue, size: 21.3))
|
Text("콘텐츠 마켓")
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.font(.custom(Font.bold.rawValue, size: 21.3))
|
||||||
.padding(.bottom, 26.7)
|
.foregroundColor(Color.button)
|
||||||
.padding(.horizontal, 13.3)
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image("ic_content_keep")
|
||||||
|
.onTapGesture {
|
||||||
|
AppState.shared.setAppStep(step: .orderListAll)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 26.7)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
if !viewModel.isLoading {
|
if !viewModel.isLoading {
|
||||||
ContentMainRecommendSeriesView()
|
|
||||||
|
|
||||||
ContentMainBannerView()
|
ContentMainBannerView()
|
||||||
.padding(.bottom, 26.7)
|
.padding(.bottom, 26.7)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
ContentMainRecommendSeriesView()
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
ZStack {
|
ZStack {
|
||||||
Image("img_bg_short_play")
|
Image("img_bg_short_play")
|
||||||
|
@ -78,9 +87,6 @@ struct ContentMainView: View {
|
||||||
.padding(.bottom, 40)
|
.padding(.bottom, 40)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
ContentMainMyStashView()
|
|
||||||
.padding(.horizontal, 13.3)
|
|
||||||
|
|
||||||
ContentMainNewContentView()
|
ContentMainNewContentView()
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
@ -92,6 +98,26 @@ struct ContentMainView: View {
|
||||||
ContentMainCurationView()
|
ContentMainCurationView()
|
||||||
.padding(.top, 40)
|
.padding(.top, 40)
|
||||||
.padding(.bottom, 20)
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
Text("""
|
||||||
|
- 회사명 : 주식회사 소다라이브
|
||||||
|
|
||||||
|
- 대표자 : 이재형
|
||||||
|
|
||||||
|
- 주소 : 경기도 성남시 분당구 황새울로335번길 10, 5층 563A호
|
||||||
|
|
||||||
|
- 사업자등록번호 : 870-81-03220
|
||||||
|
|
||||||
|
- 통신판매업신고 : 제2024-성남분당B-1012호
|
||||||
|
|
||||||
|
- 고객센터 : 02.2055.1477 (이용시간 10:00~19:00)
|
||||||
|
|
||||||
|
- 대표 이메일 : sodalive.official@gmail.com
|
||||||
|
""")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
|
.foregroundColor(Color.gray77)
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
|
|
|
@ -20,6 +20,37 @@ struct SeriesContentAllView: View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
DetailNavigationBar(title: "\(seriesTitle) - 전체회차 듣기")
|
DetailNavigationBar(title: "\(seriesTitle) - 전체회차 듣기")
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("최신순")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(
|
||||||
|
Color.graye2
|
||||||
|
.opacity(viewModel.sortType == .NEWEST ? 1 : 0.5)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if viewModel.sortType != .NEWEST {
|
||||||
|
viewModel.sortType = .NEWEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("등록순")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(
|
||||||
|
Color.graye2
|
||||||
|
.opacity(viewModel.sortType == .OLDEST ? 1 : 0.5)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if viewModel.sortType != .OLDEST {
|
||||||
|
viewModel.sortType = .OLDEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.background(Color.gray16)
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
ForEach(0..<viewModel.seriesContentList.count, id: \.self) { index in
|
ForEach(0..<viewModel.seriesContentList.count, id: \.self) { index in
|
||||||
|
|
|
@ -19,14 +19,24 @@ final class SeriesContentAllViewModel: ObservableObject {
|
||||||
@Published var isShowPopup = false
|
@Published var isShowPopup = false
|
||||||
@Published var seriesContentList = [GetSeriesContentListItem]()
|
@Published var seriesContentList = [GetSeriesContentListItem]()
|
||||||
|
|
||||||
|
@Published var sortType: SeriesListAllViewModel.SeriesSortType = .NEWEST {
|
||||||
|
didSet {
|
||||||
|
page = 1
|
||||||
|
isLast = false
|
||||||
|
getSeriesContentList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var page = 1
|
var page = 1
|
||||||
var isLast = false
|
var isLast = false
|
||||||
private let pageSize = 10
|
private let pageSize = 10
|
||||||
|
|
||||||
func getSeriesContentList() {
|
func getSeriesContentList() {
|
||||||
if !isLoading && !isLast {
|
if !isLoading && !isLast {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
repository
|
repository
|
||||||
.getSeriesContentList(seriesId: seriesId, page: page, size: pageSize)
|
.getSeriesContentList(seriesId: seriesId, page: page, size: pageSize, sortType: sortType)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
|
|
@ -11,7 +11,7 @@ import Moya
|
||||||
enum SeriesApi {
|
enum SeriesApi {
|
||||||
case getSeriesList(creatorId: Int, sortType: SeriesListAllViewModel.SeriesSortType, page: Int, size: Int)
|
case getSeriesList(creatorId: Int, sortType: SeriesListAllViewModel.SeriesSortType, page: Int, size: Int)
|
||||||
case getSeriesDetail(seriesId: Int)
|
case getSeriesDetail(seriesId: Int)
|
||||||
case getSeriesContentList(seriesId: Int, page: Int, size: Int)
|
case getSeriesContentList(seriesId: Int, page: Int, size: Int, sortType: SeriesListAllViewModel.SeriesSortType)
|
||||||
case getRecommendSeriesList
|
case getRecommendSeriesList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ extension SeriesApi: TargetType {
|
||||||
case .getSeriesDetail(let seriesId):
|
case .getSeriesDetail(let seriesId):
|
||||||
return "/audio-content/series/\(seriesId)"
|
return "/audio-content/series/\(seriesId)"
|
||||||
|
|
||||||
case .getSeriesContentList(let seriesId, _, _):
|
case .getSeriesContentList(let seriesId, _, _, _):
|
||||||
return "/audio-content/series/\(seriesId)/content"
|
return "/audio-content/series/\(seriesId)/content"
|
||||||
|
|
||||||
case .getRecommendSeriesList:
|
case .getRecommendSeriesList:
|
||||||
|
@ -58,10 +58,11 @@ extension SeriesApi: TargetType {
|
||||||
case .getSeriesDetail, .getRecommendSeriesList:
|
case .getSeriesDetail, .getRecommendSeriesList:
|
||||||
return .requestPlain
|
return .requestPlain
|
||||||
|
|
||||||
case .getSeriesContentList(_, let page, let size):
|
case .getSeriesContentList(_, let page, let size, let sortType):
|
||||||
let parameters = [
|
let parameters = [
|
||||||
"page": page - 1,
|
"page": page - 1,
|
||||||
"size": size
|
"size": size,
|
||||||
|
"sortType": sortType
|
||||||
] as [String : Any]
|
] as [String : Any]
|
||||||
|
|
||||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
|
@ -13,7 +13,7 @@ final class SeriesListAllViewModel: ObservableObject {
|
||||||
private var subscription = Set<AnyCancellable>()
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
|
||||||
enum SeriesSortType: String {
|
enum SeriesSortType: String {
|
||||||
case NEWEST, POPULAR
|
case NEWEST, OLDEST
|
||||||
}
|
}
|
||||||
|
|
||||||
var creatorId: Int = 0
|
var creatorId: Int = 0
|
||||||
|
|
|
@ -21,8 +21,8 @@ class SeriesRepository {
|
||||||
return api.requestPublisher(.getSeriesDetail(seriesId: seriesId))
|
return api.requestPublisher(.getSeriesDetail(seriesId: seriesId))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSeriesContentList(seriesId: Int, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
|
func getSeriesContentList(seriesId: Int, page: Int, size: Int, sortType: SeriesListAllViewModel.SeriesSortType) -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.getSeriesContentList(seriesId: seriesId, page: page, size: size))
|
return api.requestPublisher(.getSeriesContentList(seriesId: seriesId, page: page, size: size, sortType: sortType))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRecommendSeriesList() -> AnyPublisher<Response, MoyaError> {
|
func getRecommendSeriesList() -> AnyPublisher<Response, MoyaError> {
|
||||||
|
|
|
@ -189,7 +189,9 @@ struct ContentView: View {
|
||||||
|
|
||||||
case .tempCanPayment(let orderType, let contentId, let title, let can):
|
case .tempCanPayment(let orderType, let contentId, let title, let can):
|
||||||
CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can)
|
CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can)
|
||||||
|
|
||||||
|
case .blockList:
|
||||||
|
BlockMemberListView()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
|
|
@ -14,3 +14,4 @@ let AGORA_APP_ID = "b96574e191a9430fa54c605528aa3ef7"
|
||||||
let AGORA_APP_CERTIFICATE = "ae18ade3afcf4086bd4397726eb0654c"
|
let AGORA_APP_CERTIFICATE = "ae18ade3afcf4086bd4397726eb0654c"
|
||||||
|
|
||||||
let BOOTPAY_APP_ID = "6242a7772701800023f68b2f"
|
let BOOTPAY_APP_ID = "6242a7772701800023f68b2f"
|
||||||
|
let BOOTPAY_APP_HECTO_ID = "667fca5d3bab7404f831c3e5"
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
//
|
||||||
|
// CommunityPostPurchaseDialog.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/24/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CommunityPostPurchaseDialog: View {
|
||||||
|
|
||||||
|
@Binding var isShowing: Bool
|
||||||
|
|
||||||
|
let can: Int
|
||||||
|
let confirmAction: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geo in
|
||||||
|
ZStack {
|
||||||
|
Color.black
|
||||||
|
.opacity(0.5)
|
||||||
|
.frame(width: geo.size.width, height: geo.size.height)
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Text("게시글 보기")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.graybb)
|
||||||
|
.padding(.top, 40)
|
||||||
|
|
||||||
|
Text("게시글을\n확인하시겠습니까?")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 15))
|
||||||
|
.foregroundColor(Color.graybb)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding(.top, 12)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
Text("취소")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.frame(width: (geo.size.width - 66.7) / 3)
|
||||||
|
.background(Color.bg)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.stroke(Color.button, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("\(can)캔으로 보기")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.padding(.vertical, 16)
|
||||||
|
.frame(width: (geo.size.width - 66.7) * 2 / 3)
|
||||||
|
.background(Color.button)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.onTapGesture {
|
||||||
|
confirmAction()
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 26.7)
|
||||||
|
.padding(.bottom, 16.7)
|
||||||
|
}
|
||||||
|
.frame(width: geo.size.width - 26.7, alignment: .center)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.cornerRadius(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
CommunityPostPurchaseDialog(
|
||||||
|
isShowing: .constant(true),
|
||||||
|
can: 10,
|
||||||
|
confirmAction: {}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
//
|
||||||
|
// MemberProfileDialog.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 9/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct MemberProfileDialog: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel = UserViewModel()
|
||||||
|
|
||||||
|
@Binding var isShowing: Bool
|
||||||
|
let memberId: Int
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.black.opacity(0.7).ignoresSafeArea()
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 21) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("프로필")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image("ic_close_white")
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let profile = viewModel.memberProfile {
|
||||||
|
Text(profile.nickname)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
KFImage(URL(string: profile.profileImageUrl))
|
||||||
|
.resizable()
|
||||||
|
.frame(maxWidth: screenSize().width - 66.7, maxHeight: screenSize().width - 66.7)
|
||||||
|
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit)
|
||||||
|
.cornerRadius(8)
|
||||||
|
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
Text(profile.isBlocked ? "차단 해제" : "차단")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 13)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.strokeBorder(lineWidth: 1)
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if profile.isBlocked {
|
||||||
|
viewModel.memberUnBlock()
|
||||||
|
} else {
|
||||||
|
viewModel.isShowUesrBlockConfirm = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("사용자 신고")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 13)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.strokeBorder(lineWidth: 1)
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
)
|
||||||
|
.onTapGesture { viewModel.isShowUesrReportView = true }
|
||||||
|
|
||||||
|
Text("프로필 신고")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 15))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.vertical, 13)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.strokeBorder(lineWidth: 1)
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
)
|
||||||
|
.onTapGesture { viewModel.isShowProfileReportConfirm = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
.background(Color.gray22)
|
||||||
|
.cornerRadius(8)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.frame(maxWidth: screenSize().width - 33.3)
|
||||||
|
.onAppear {
|
||||||
|
if memberId <= 1 {
|
||||||
|
viewModel.errorMessage = "잘못된 요청입니다."
|
||||||
|
viewModel.isShowPopup = true
|
||||||
|
} else {
|
||||||
|
viewModel.getMemberProfile(memberId: memberId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text(viewModel.errorMessage)
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.background(Color.button)
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.multilineTextAlignment(.leading)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.padding(.bottom, 66.7)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
if viewModel.dismissDialog {
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowUesrBlockConfirm {
|
||||||
|
UserBlockConfirmDialogView(
|
||||||
|
isShowing: $viewModel.isShowUesrBlockConfirm,
|
||||||
|
nickname: viewModel.nickname,
|
||||||
|
confirmAction: {
|
||||||
|
viewModel.memberBlock()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowUesrReportView {
|
||||||
|
UserReportDialogView(
|
||||||
|
isShowing: $viewModel.isShowUesrReportView,
|
||||||
|
confirmAction: { reason in
|
||||||
|
viewModel.report(type: .USER, reason: reason)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowProfileReportConfirm {
|
||||||
|
ProfileReportDialogView(
|
||||||
|
isShowing: $viewModel.isShowProfileReportConfirm,
|
||||||
|
confirmAction: {
|
||||||
|
viewModel.report(type: .PROFILE)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewModel.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
MemberProfileDialog(isShowing: .constant(true), memberId: 1)
|
||||||
|
}
|
|
@ -42,13 +42,13 @@ struct SodaDialog: View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.padding(.top, 40)
|
.padding(.top, 40)
|
||||||
|
|
||||||
Text(desc)
|
Text(desc)
|
||||||
.font(.custom(Font.medium.rawValue, size: 15))
|
.font(.custom(Font.medium.rawValue, size: 15))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.leading)
|
||||||
.padding(.top, 12)
|
.padding(.top, 12)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
@ -57,14 +57,14 @@ struct SodaDialog: View {
|
||||||
if cancelButtonTitle.count > 0 {
|
if cancelButtonTitle.count > 0 {
|
||||||
Text(cancelButtonTitle)
|
Text(cancelButtonTitle)
|
||||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.foregroundColor(Color.button)
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, 16)
|
||||||
.frame(width: (geo.size.width - 66.7) / 3)
|
.frame(width: (geo.size.width - 66.7) / 3)
|
||||||
.background(Color(hex: "13181b"))
|
.background(Color.bg)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.stroke(Color(hex: "3bb9f1"), lineWidth: 1)
|
.stroke(Color.button, lineWidth: 1)
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
cancelButtonAction()
|
cancelButtonAction()
|
||||||
|
@ -73,10 +73,10 @@ struct SodaDialog: View {
|
||||||
|
|
||||||
Text(confirmButtonTitle)
|
Text(confirmButtonTitle)
|
||||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
.font(.custom(Font.bold.rawValue, size: 15.3))
|
||||||
.foregroundColor(Color(hex: "ffffff"))
|
.foregroundColor(Color.white)
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, 16)
|
||||||
.frame(width: (geo.size.width - 66.7) * 2 / 3)
|
.frame(width: (geo.size.width - 66.7) * 2 / 3)
|
||||||
.background(Color(hex: "3bb9f1"))
|
.background(Color.button)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
confirmButtonAction()
|
confirmButtonAction()
|
||||||
|
@ -86,7 +86,7 @@ struct SodaDialog: View {
|
||||||
.padding(.bottom, 16.7)
|
.padding(.bottom, 16.7)
|
||||||
}
|
}
|
||||||
.frame(width: geo.size.width - 26.7, alignment: .center)
|
.frame(width: geo.size.width - 26.7, alignment: .center)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
|
|
||||||
let modifyComment: (Int, String) -> Void
|
let modifyComment: (Int, String) -> Void
|
||||||
let onClickDelete: (Int) -> Void
|
let onClickDelete: (Int) -> Void
|
||||||
|
let onClickProfile: (Int) -> Void
|
||||||
|
|
||||||
@State var isShowPopupMenu: Bool = false
|
@State var isShowPopupMenu: Bool = false
|
||||||
@State var isModeModify: Bool = false
|
@State var isModeModify: Bool = false
|
||||||
|
@ -30,6 +31,11 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 40, height: 40)
|
.frame(width: 40, height: 40)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
.onTapGesture {
|
||||||
|
if UserDefaults.int(forKey: .userId) != commentItem.writerId {
|
||||||
|
onClickProfile(commentItem.writerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(commentItem.nickname)
|
Text(commentItem.nickname)
|
||||||
|
@ -38,7 +44,7 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
|
|
||||||
Text(commentItem.date)
|
Text(commentItem.date)
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.3))
|
.font(.custom(Font.medium.rawValue, size: 10.3))
|
||||||
.foregroundColor(Color(hex: "525252"))
|
.foregroundColor(Color.gray52)
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +63,8 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,10 @@ struct CreatorCommunityCommentListView: View {
|
||||||
|
|
||||||
@State private var commentId: Int = 0
|
@State private var commentId: Int = 0
|
||||||
@State private var isShowDeletePopup: Bool = false
|
@State private var isShowDeletePopup: Bool = false
|
||||||
|
|
||||||
|
@State private var memberId: Int = 0
|
||||||
|
@State private var isShowMemberProfilePopup: Bool = false
|
||||||
|
|
||||||
@StateObject var viewModel = CreatorCommunityCommentListViewModel()
|
@StateObject var viewModel = CreatorCommunityCommentListViewModel()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -115,6 +119,10 @@ struct CreatorCommunityCommentListView: View {
|
||||||
onClickDelete: {
|
onClickDelete: {
|
||||||
commentId = $0
|
commentId = $0
|
||||||
isShowDeletePopup = true
|
isShowDeletePopup = true
|
||||||
|
},
|
||||||
|
onClickProfile: {
|
||||||
|
memberId = $0
|
||||||
|
isShowMemberProfilePopup = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 26.7)
|
.padding(.horizontal, 26.7)
|
||||||
|
@ -147,6 +155,10 @@ struct CreatorCommunityCommentListView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMemberProfilePopup {
|
||||||
|
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
|
||||||
|
}
|
||||||
|
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
LoadingView()
|
LoadingView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ struct CreatorCommunityCommentReplyView: View {
|
||||||
@State private var commentId: Int = 0
|
@State private var commentId: Int = 0
|
||||||
@State private var isShowDeletePopup: Bool = false
|
@State private var isShowDeletePopup: Bool = false
|
||||||
|
|
||||||
|
@State private var memberId: Int = 0
|
||||||
|
@State private var isShowMemberProfilePopup: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
@ -98,7 +101,11 @@ struct CreatorCommunityCommentReplyView: View {
|
||||||
isReplyComment: true,
|
isReplyComment: true,
|
||||||
isShowPopupMenuButton: false,
|
isShowPopupMenuButton: false,
|
||||||
modifyComment: { _, _ in },
|
modifyComment: { _, _ in },
|
||||||
onClickDelete: { _ in }
|
onClickDelete: { _ in },
|
||||||
|
onClickProfile: {
|
||||||
|
memberId = $0
|
||||||
|
isShowMemberProfilePopup = true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 26.7)
|
.padding(.horizontal, 26.7)
|
||||||
.padding(.bottom, 13.3)
|
.padding(.bottom, 13.3)
|
||||||
|
@ -120,6 +127,10 @@ struct CreatorCommunityCommentReplyView: View {
|
||||||
onClickDelete: {
|
onClickDelete: {
|
||||||
commentId = $0
|
commentId = $0
|
||||||
isShowDeletePopup = true
|
isShowDeletePopup = true
|
||||||
|
},
|
||||||
|
onClickProfile: {
|
||||||
|
memberId = $0
|
||||||
|
isShowMemberProfilePopup = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 26.7)
|
.padding(.horizontal, 26.7)
|
||||||
|
@ -154,6 +165,10 @@ struct CreatorCommunityCommentReplyView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMemberProfilePopup {
|
||||||
|
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
|
||||||
|
}
|
||||||
|
|
||||||
if viewModel.isLoading {
|
if viewModel.isLoading {
|
||||||
LoadingView()
|
LoadingView()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// CreatorCommunityAllItemLockView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/24/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CreatorCommunityAllItemLockView: View {
|
||||||
|
|
||||||
|
let price: Int
|
||||||
|
let onClickPurchaseContent: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 26.7) {
|
||||||
|
Image("ic_lock_bb")
|
||||||
|
|
||||||
|
Text("\(price)캔으로 게시글 보기")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.padding(.horizontal, 21)
|
||||||
|
.padding(.vertical, 11)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 26.7)
|
||||||
|
.stroke(Color.button, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.onTapGesture { onClickPurchaseContent() }
|
||||||
|
}
|
||||||
|
.frame(width: screenSize().width - 42, height: screenSize().width - 42)
|
||||||
|
.background(Color.gray33)
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
CreatorCommunityAllItemLockView(price: 100) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,23 +15,29 @@ struct CreatorCommunityAllItemView: View {
|
||||||
let onClickComment: () -> Void
|
let onClickComment: () -> Void
|
||||||
let onClickWriteComment: (String) -> Void
|
let onClickWriteComment: (String) -> Void
|
||||||
let onClickShowReportMenu: () -> Void
|
let onClickShowReportMenu: () -> Void
|
||||||
|
let onClickPurchaseContent: () -> Void
|
||||||
|
|
||||||
@State var isLike = false
|
@State var isLike = false
|
||||||
@State var likeCount = 0
|
@State var likeCount = 0
|
||||||
@State private var textHeight: CGFloat = .zero
|
@State private var textHeight: CGFloat = .zero
|
||||||
|
|
||||||
|
@StateObject var playManager = CreatorCommunityMediaPlayerManager.shared
|
||||||
|
@StateObject var contentPlayManager = ContentPlayManager.shared
|
||||||
|
|
||||||
init(
|
init(
|
||||||
item: GetCommunityPostListResponse,
|
item: GetCommunityPostListResponse,
|
||||||
onClickLike: @escaping () -> Void,
|
onClickLike: @escaping () -> Void,
|
||||||
onClickComment: @escaping () -> Void,
|
onClickComment: @escaping () -> Void,
|
||||||
onClickWriteComment: @escaping (String) -> Void,
|
onClickWriteComment: @escaping (String) -> Void,
|
||||||
onClickShowReportMenu: @escaping () -> Void
|
onClickShowReportMenu: @escaping () -> Void,
|
||||||
|
onClickPurchaseContent: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.item = item
|
self.item = item
|
||||||
self.onClickLike = onClickLike
|
self.onClickLike = onClickLike
|
||||||
self.onClickComment = onClickComment
|
self.onClickComment = onClickComment
|
||||||
self.onClickWriteComment = onClickWriteComment
|
self.onClickWriteComment = onClickWriteComment
|
||||||
self.onClickShowReportMenu = onClickShowReportMenu
|
self.onClickShowReportMenu = onClickShowReportMenu
|
||||||
|
self.onClickPurchaseContent = onClickPurchaseContent
|
||||||
|
|
||||||
self._isLike = State(initialValue: item.isLike)
|
self._isLike = State(initialValue: item.isLike)
|
||||||
self._likeCount = State(initialValue: item.likeCount)
|
self._likeCount = State(initialValue: item.likeCount)
|
||||||
|
@ -58,72 +64,91 @@ struct CreatorCommunityAllItemView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image("ic_seemore_vertical")
|
if item.price <= 0 || item.existOrdered {
|
||||||
.padding(.trailing, 8.3)
|
Image("ic_seemore_vertical")
|
||||||
.onTapGesture { onClickShowReportMenu() }
|
.padding(.trailing, 8.3)
|
||||||
|
.onTapGesture { onClickShowReportMenu() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DetectableTextView(text: item.content, textSize: 13.3, font: Font.medium.rawValue)
|
DetectableTextView(text: item.content, textSize: 13.3, font: Font.medium.rawValue)
|
||||||
.frame(
|
.frame(
|
||||||
width: screenSize().width - 16,
|
width: screenSize().width - 42,
|
||||||
height: textHeight
|
height: textHeight
|
||||||
)
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.textHeight = self.estimatedHeight(
|
self.textHeight = self.estimatedHeight(
|
||||||
for: item.content,
|
for: item.content,
|
||||||
width: screenSize().width - 16
|
width: screenSize().width - 42
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onChange(of: item.content) { newText in
|
.onChange(of: item.content) { newText in
|
||||||
self.textHeight = self.estimatedHeight(
|
self.textHeight = self.estimatedHeight(
|
||||||
for: newText,
|
for: newText,
|
||||||
width: screenSize().width - 16
|
width: screenSize().width - 42
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let imageUrl = item.imageUrl {
|
if item.price <= 0 || item.existOrdered {
|
||||||
KFImage(URL(string: imageUrl))
|
if let imageUrl = item.imageUrl {
|
||||||
.resizable()
|
ZStack {
|
||||||
.frame(maxWidth: .infinity)
|
KFImage(URL(string: imageUrl))
|
||||||
.scaledToFit()
|
.resizable()
|
||||||
}
|
.frame(maxWidth: .infinity)
|
||||||
|
.scaledToFit()
|
||||||
HStack(spacing: 8) {
|
|
||||||
IconAndTitleToggleButton(
|
if let audioUrl = item.audioUrl {
|
||||||
isChecked: isLike,
|
Image(playManager.isPlaying && playManager.currentPlayingContentId == item.postId ? "btn_audio_content_pause" : "btn_audio_content_play")
|
||||||
title: "\(likeCount)",
|
.onTapGesture {
|
||||||
normalIconName: "ic_audio_content_heart_normal",
|
contentPlayManager.pauseAudio()
|
||||||
checkedIconName: "ic_audio_content_heart_pressed"
|
playManager.toggleContent(item: CreatorCommunityContentItem(contentId: item.postId, url: audioUrl))
|
||||||
) {
|
}
|
||||||
if isLike {
|
}
|
||||||
isLike = false
|
|
||||||
likeCount -= 1
|
|
||||||
} else {
|
|
||||||
isLike = true
|
|
||||||
likeCount += 1
|
|
||||||
}
|
|
||||||
onClickLike()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
|
|
||||||
if item.isCommentAvailable {
|
|
||||||
CreatorCommunityCommentView(
|
|
||||||
commentCount: item.commentCount,
|
|
||||||
commentItem: item.firstComment,
|
|
||||||
onClickWriteComment: onClickWriteComment
|
|
||||||
)
|
|
||||||
.onTapGesture {
|
|
||||||
if item.commentCount > 0 {
|
|
||||||
onClickComment()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
IconAndTitleToggleButton(
|
||||||
|
isChecked: isLike,
|
||||||
|
title: "\(likeCount)",
|
||||||
|
normalIconName: "ic_audio_content_heart_normal",
|
||||||
|
checkedIconName: "ic_audio_content_heart_pressed"
|
||||||
|
) {
|
||||||
|
if isLike {
|
||||||
|
isLike = false
|
||||||
|
likeCount -= 1
|
||||||
|
} else {
|
||||||
|
isLike = true
|
||||||
|
likeCount += 1
|
||||||
|
}
|
||||||
|
onClickLike()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
if item.isCommentAvailable {
|
||||||
|
CreatorCommunityCommentView(
|
||||||
|
commentCount: item.commentCount,
|
||||||
|
commentItem: item.firstComment,
|
||||||
|
onClickWriteComment: onClickWriteComment
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if item.commentCount > 0 {
|
||||||
|
onClickComment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CreatorCommunityAllItemLockView(
|
||||||
|
price: item.price,
|
||||||
|
onClickPurchaseContent: onClickPurchaseContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
.padding(.vertical, 11)
|
.padding(.vertical, 11)
|
||||||
.background(Color.gray22)
|
.background(Color.gray22)
|
||||||
.cornerRadius(5.3)
|
.cornerRadius(5.3)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat {
|
private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat {
|
||||||
|
@ -143,11 +168,14 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
|
||||||
creatorNickname: "민하나",
|
creatorNickname: "민하나",
|
||||||
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
audioUrl: nil,
|
||||||
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
|
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
|
||||||
|
price: 10,
|
||||||
date: "3일전",
|
date: "3일전",
|
||||||
isCommentAvailable: false,
|
isCommentAvailable: false,
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
isLike: true,
|
isLike: true,
|
||||||
|
existOrdered: false,
|
||||||
likeCount: 10,
|
likeCount: 10,
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
firstComment: nil
|
firstComment: nil
|
||||||
|
@ -155,7 +183,8 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
|
||||||
onClickLike: {},
|
onClickLike: {},
|
||||||
onClickComment: {},
|
onClickComment: {},
|
||||||
onClickWriteComment: { _ in },
|
onClickWriteComment: { _ in },
|
||||||
onClickShowReportMenu: {}
|
onClickShowReportMenu: {},
|
||||||
|
onClickPurchaseContent: {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ struct CreatorCommunityAllView: View {
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
|
|
||||||
@StateObject var viewModel = CreatorCommunityAllViewModel()
|
@StateObject var viewModel = CreatorCommunityAllViewModel()
|
||||||
|
@StateObject var playerManager = CreatorCommunityMediaPlayerManager.shared
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
|
@ -41,6 +42,12 @@ struct CreatorCommunityAllView: View {
|
||||||
onClickShowReportMenu: {
|
onClickShowReportMenu: {
|
||||||
viewModel.postId = item.postId
|
viewModel.postId = item.postId
|
||||||
viewModel.isShowReportMenu = true
|
viewModel.isShowReportMenu = true
|
||||||
|
},
|
||||||
|
onClickPurchaseContent: {
|
||||||
|
viewModel.postId = item.postId
|
||||||
|
viewModel.postPrice = item.price
|
||||||
|
viewModel.postIndex = index
|
||||||
|
viewModel.isShowPostPurchaseView = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
@ -50,7 +57,6 @@ struct CreatorCommunityAllView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(5.3)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(
|
.sheet(
|
||||||
|
@ -121,6 +127,19 @@ struct CreatorCommunityAllView: View {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowPostPurchaseView {
|
||||||
|
CommunityPostPurchaseDialog(
|
||||||
|
isShowing: $viewModel.isShowPostPurchaseView,
|
||||||
|
can: viewModel.postPrice
|
||||||
|
) {
|
||||||
|
viewModel.purchaseCommunityPost()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if playerManager.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||||
|
@ -131,7 +150,24 @@ struct CreatorCommunityAllView: View {
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.padding(.top, 66.7)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popup(isPresented: $playerManager.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||||
|
GeometryReader { geo in
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text(playerManager.errorMessage)
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.background(Color.button)
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.cornerRadius(20)
|
.cornerRadius(20)
|
||||||
|
@ -144,6 +180,9 @@ struct CreatorCommunityAllView: View {
|
||||||
viewModel.creatorId = creatorId
|
viewModel.creatorId = creatorId
|
||||||
viewModel.getCommunityPostList()
|
viewModel.getCommunityPostList()
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
CreatorCommunityMediaPlayerManager.shared.stopContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@ class CreatorCommunityAllViewModel: ObservableObject {
|
||||||
@Published private(set) var communityPostList = [GetCommunityPostListResponse]()
|
@Published private(set) var communityPostList = [GetCommunityPostListResponse]()
|
||||||
|
|
||||||
@Published var postId = 0
|
@Published var postId = 0
|
||||||
|
@Published var postPrice = 0
|
||||||
|
@Published var postIndex = -1
|
||||||
|
|
||||||
@Published var isShowCommentListView = false {
|
@Published var isShowCommentListView = false {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -46,6 +48,16 @@ class CreatorCommunityAllViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var isShowPostPurchaseView = false {
|
||||||
|
didSet {
|
||||||
|
if !isShowPostPurchaseView {
|
||||||
|
postId = 0
|
||||||
|
postPrice = 0
|
||||||
|
postIndex = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var creatorId = 0
|
var creatorId = 0
|
||||||
|
|
||||||
var page = 1
|
var page = 1
|
||||||
|
@ -254,4 +266,51 @@ class CreatorCommunityAllViewModel: ObservableObject {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func purchaseCommunityPost() {
|
||||||
|
let postId = postId
|
||||||
|
let postIndex = postIndex
|
||||||
|
|
||||||
|
if !isLoading {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository
|
||||||
|
.purchaseCommunityPost(postId: postId)
|
||||||
|
.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<GetCommunityPostListResponse>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
if postIndex >= 0 {
|
||||||
|
communityPostList[postIndex] = data
|
||||||
|
}
|
||||||
|
} 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,130 @@
|
||||||
|
//
|
||||||
|
// CreatorCommunityMediaPlayerManager.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 8/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVKit
|
||||||
|
import MediaPlayer
|
||||||
|
|
||||||
|
final class CreatorCommunityMediaPlayerManager: NSObject, ObservableObject {
|
||||||
|
static let shared = CreatorCommunityMediaPlayerManager()
|
||||||
|
|
||||||
|
@Published private (set) var currentPlayingContentId: Int = 0
|
||||||
|
@Published private (set) var isPlaying = false
|
||||||
|
|
||||||
|
@Published var isLoading = false
|
||||||
|
@Published var errorMessage = ""
|
||||||
|
@Published var isShowPopup = false
|
||||||
|
|
||||||
|
private var player: AVAudioPlayer!
|
||||||
|
|
||||||
|
private func playContent(item: CreatorCommunityContentItem) {
|
||||||
|
if item.contentId <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPlayingContentId == item.contentId && !isPlaying) {
|
||||||
|
resumeContent()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stopContent()
|
||||||
|
currentPlayingContentId = item.contentId
|
||||||
|
|
||||||
|
guard let url = URL(string: item.url) else {
|
||||||
|
showError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
URLSession.shared.dataTask(with: url) { [unowned self] data, response, error in
|
||||||
|
guard let audioData = data else {
|
||||||
|
self.isLoading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
|
try audioSession.setCategory(.playback, mode: .moviePlayback)
|
||||||
|
try audioSession.setActive(true)
|
||||||
|
|
||||||
|
self.player = try AVAudioPlayer(data: audioData)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.player?.volume = 1
|
||||||
|
self.player?.delegate = self
|
||||||
|
self.player?.prepareToPlay()
|
||||||
|
|
||||||
|
self.player?.play()
|
||||||
|
self.isPlaying = self.player.isPlaying
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.showError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
}.resume()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func resumeContent() {
|
||||||
|
if let player = player {
|
||||||
|
player.play()
|
||||||
|
isPlaying = player.isPlaying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showError() {
|
||||||
|
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CreatorCommunityMediaPlayerManager {
|
||||||
|
func toggleContent(item: CreatorCommunityContentItem) {
|
||||||
|
if currentPlayingContentId == item.contentId {
|
||||||
|
if let player = player, player.isPlaying {
|
||||||
|
pauseContent()
|
||||||
|
} else {
|
||||||
|
resumeContent()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
playContent(item: item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pauseContent() {
|
||||||
|
if let player = player {
|
||||||
|
player.pause()
|
||||||
|
isPlaying = player.isPlaying
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopContent() {
|
||||||
|
if let player = player {
|
||||||
|
player.stop()
|
||||||
|
player.currentTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
isPlaying = false
|
||||||
|
currentPlayingContentId = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CreatorCommunityMediaPlayerManager: AVAudioPlayerDelegate {
|
||||||
|
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||||
|
stopContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct CreatorCommunityContentItem {
|
||||||
|
let contentId: Int
|
||||||
|
let url: String
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
//
|
||||||
|
// PurchasePostRequest.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 5/24/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PurchasePostRequest: Encodable {
|
||||||
|
let postId: Int
|
||||||
|
let container: String = "ios"
|
||||||
|
let timezone: String = TimeZone.current.identifier
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ enum CreatorCommunityApi {
|
||||||
case getCommentReplyList(commentId: Int, page: Int, size: Int)
|
case getCommentReplyList(commentId: Int, page: Int, size: Int)
|
||||||
case modifyComment(request: ModifyCommunityPostCommentRequest)
|
case modifyComment(request: ModifyCommunityPostCommentRequest)
|
||||||
case getLatestPostListFromCreatorsYouFollow
|
case getLatestPostListFromCreatorsYouFollow
|
||||||
|
case purchaseCommunityPost(postId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CreatorCommunityApi: TargetType {
|
extension CreatorCommunityApi: TargetType {
|
||||||
|
@ -48,12 +49,15 @@ extension CreatorCommunityApi: TargetType {
|
||||||
|
|
||||||
case .getLatestPostListFromCreatorsYouFollow:
|
case .getLatestPostListFromCreatorsYouFollow:
|
||||||
return "/creator-community/latest"
|
return "/creator-community/latest"
|
||||||
|
|
||||||
|
case .purchaseCommunityPost:
|
||||||
|
return "/creator-community/purchase"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var method: Moya.Method {
|
var method: Moya.Method {
|
||||||
switch self {
|
switch self {
|
||||||
case .createCommunityPost, .communityPostLike, .createCommunityPostComment:
|
case .createCommunityPost, .communityPostLike, .createCommunityPostComment, .purchaseCommunityPost:
|
||||||
return .post
|
return .post
|
||||||
|
|
||||||
case .getCommunityPostList, .getCommunityPostCommentList, .getCommentReplyList, .getCommunityPostDetail, .getLatestPostListFromCreatorsYouFollow:
|
case .getCommunityPostList, .getCommunityPostCommentList, .getCommentReplyList, .getCommunityPostDetail, .getLatestPostListFromCreatorsYouFollow:
|
||||||
|
@ -115,6 +119,9 @@ extension CreatorCommunityApi: TargetType {
|
||||||
case .getLatestPostListFromCreatorsYouFollow:
|
case .getLatestPostListFromCreatorsYouFollow:
|
||||||
let parameters = ["timezone": TimeZone.current.identifier] as [String: Any]
|
let parameters = ["timezone": TimeZone.current.identifier] as [String: Any]
|
||||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
||||||
|
case .purchaseCommunityPost(let postId):
|
||||||
|
return .requestJSONEncodable(PurchasePostRequest(postId: postId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,19 +22,19 @@ struct CreatorCommunityItemView: View {
|
||||||
|
|
||||||
Text(item.creatorNickname)
|
Text(item.creatorNickname)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(item.date)
|
Text(item.date)
|
||||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text(item.content)
|
Text(item.content)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.lineLimit(3)
|
.lineLimit(3)
|
||||||
|
|
||||||
|
@ -45,9 +45,10 @@ struct CreatorCommunityItemView: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 53.3, height: 53.3)
|
.frame(width: 53.3, height: 53.3)
|
||||||
.cornerRadius(4.7)
|
.cornerRadius(4.7)
|
||||||
|
.blur(radius: item.existOrdered || item.price <= 0 ? 0 : 15)
|
||||||
} else {
|
} else {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "222222").opacity(0))
|
.foregroundColor(Color.gray22.opacity(0))
|
||||||
.frame(width: 53.3, height: 53.3)
|
.frame(width: 53.3, height: 53.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +61,7 @@ struct CreatorCommunityItemView: View {
|
||||||
|
|
||||||
Text("\(item.likeCount)")
|
Text("\(item.likeCount)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 11))
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 6) {
|
HStack(spacing: 6) {
|
||||||
|
@ -70,13 +71,13 @@ struct CreatorCommunityItemView: View {
|
||||||
|
|
||||||
Text("\(item.commentCount)")
|
Text("\(item.commentCount)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 11))
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(11)
|
.cornerRadius(11)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,11 +91,14 @@ struct CreatorCommunityItemView_Previews: PreviewProvider {
|
||||||
creatorNickname: "민하나",
|
creatorNickname: "민하나",
|
||||||
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
imageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
audioUrl: nil,
|
||||||
content: "안녕하세요",
|
content: "안녕하세요",
|
||||||
|
price: 10,
|
||||||
date: "3일전",
|
date: "3일전",
|
||||||
isCommentAvailable: false,
|
isCommentAvailable: false,
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
isLike: false,
|
isLike: false,
|
||||||
|
existOrdered: false,
|
||||||
likeCount: 10,
|
likeCount: 10,
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
firstComment: nil
|
firstComment: nil
|
||||||
|
|
|
@ -52,4 +52,8 @@ class CreatorCommunityRepository {
|
||||||
func getLatestPostListFromCreatorsYouFollow() -> AnyPublisher<Response, MoyaError> {
|
func getLatestPostListFromCreatorsYouFollow() -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.getLatestPostListFromCreatorsYouFollow)
|
return api.requestPublisher(.getLatestPostListFromCreatorsYouFollow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func purchaseCommunityPost(postId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.purchaseCommunityPost(postId: postId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,14 @@ struct GetCommunityPostListResponse: Decodable {
|
||||||
let creatorNickname: String
|
let creatorNickname: String
|
||||||
let creatorProfileUrl: String
|
let creatorProfileUrl: String
|
||||||
let imageUrl: String?
|
let imageUrl: String?
|
||||||
|
let audioUrl: String?
|
||||||
let content: String
|
let content: String
|
||||||
|
let price: Int
|
||||||
let date: String
|
let date: String
|
||||||
let isCommentAvailable: Bool
|
let isCommentAvailable: Bool
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isLike: Bool
|
let isLike: Bool
|
||||||
|
let existOrdered: Bool
|
||||||
let likeCount: Int
|
let likeCount: Int
|
||||||
let commentCount: Int
|
let commentCount: Int
|
||||||
let firstComment: GetCommunityPostCommentListItem?
|
let firstComment: GetCommunityPostCommentListItem?
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Foundation
|
||||||
|
|
||||||
struct CreateCommunityPostRequest: Encodable {
|
struct CreateCommunityPostRequest: Encodable {
|
||||||
let content: String
|
let content: String
|
||||||
|
let price: Int
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isCommentAvailable: Bool
|
let isCommentAvailable: Bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
//
|
||||||
|
// CreatorCommunityRecordingVoiceView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 8/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CreatorCommunityRecordingVoiceView: View {
|
||||||
|
@StateObject var soundManager = CreatorCommunitySoundManager()
|
||||||
|
|
||||||
|
@Binding var isShowing: Bool
|
||||||
|
@Binding var isShowPopup: Bool
|
||||||
|
@Binding var errorMessage: String
|
||||||
|
|
||||||
|
@Binding var fileName: String
|
||||||
|
@Binding var soundData: Data?
|
||||||
|
|
||||||
|
@State private var tempFileName = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.black.opacity(0.7)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
GeometryReader { proxy in
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("음성녹음")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image("ic_close_white")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.onTapGesture { isShowing = false }
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
.padding(.top, 26.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(soundManager.timeString)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 33.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(.top, 80)
|
||||||
|
|
||||||
|
switch soundManager.recordMode {
|
||||||
|
case .RECORD:
|
||||||
|
if !soundManager.isLoading {
|
||||||
|
Image(soundManager.isRecording ? "ic_record_stop" : "ic_record")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 70, height: 70)
|
||||||
|
.padding(.vertical, 52.3)
|
||||||
|
.onTapGesture {
|
||||||
|
if !soundManager.isLoading {
|
||||||
|
if !soundManager.isRecording {
|
||||||
|
tempFileName = "now_voice_\(Int(Date().timeIntervalSince1970 * 1000)).m4a"
|
||||||
|
soundManager.startRecording(tempFileName)
|
||||||
|
} else {
|
||||||
|
soundManager.stopRecording()
|
||||||
|
soundManager.recordMode = .PLAY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .PLAY:
|
||||||
|
if !soundManager.isLoading {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("삭제")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 15.3))
|
||||||
|
.foregroundColor(Color.graybb.opacity(0))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(
|
||||||
|
!soundManager.isPlaying ?
|
||||||
|
"ic_record_play" :
|
||||||
|
"ic_record_pause"
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
if !soundManager.isLoading {
|
||||||
|
if !soundManager.isPlaying {
|
||||||
|
soundManager.playAudio()
|
||||||
|
} else {
|
||||||
|
soundManager.stopAudio()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("삭제")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 15.3))
|
||||||
|
.foregroundColor(Color.graybb)
|
||||||
|
.onTapGesture {
|
||||||
|
soundManager.stopAudio()
|
||||||
|
soundManager.deleteAudioFile()
|
||||||
|
soundManager.recordMode = .RECORD
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 52.3)
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
Text("다시 녹음")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.frame(width: (proxy.size.width - 40) / 3, height: 50)
|
||||||
|
.background(Color.button.opacity(0.2))
|
||||||
|
.cornerRadius(10)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 10)
|
||||||
|
.stroke(Color.button, lineWidth: 1.3)
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
soundManager.stopAudio()
|
||||||
|
soundManager.deleteAudioFile()
|
||||||
|
soundManager.recordMode = .RECORD
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("녹음완료")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(width: (proxy.size.width - 40) * 2 / 3, height: 50)
|
||||||
|
.background(Color.button)
|
||||||
|
.cornerRadius(10)
|
||||||
|
.onTapGesture {
|
||||||
|
do {
|
||||||
|
let soundData = try Data(contentsOf: soundManager.getAudioFileURL())
|
||||||
|
self.soundData = soundData
|
||||||
|
self.fileName = tempFileName
|
||||||
|
self.isShowing = false
|
||||||
|
} catch {
|
||||||
|
errorMessage = "녹음파일을 생성하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.bottom, 40)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxy.safeAreaInsets.bottom > 0 {
|
||||||
|
Rectangle()
|
||||||
|
.foregroundColor(Color.gray22)
|
||||||
|
.frame(width: proxy.size.width, height: 15.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
if soundManager.isLoading {
|
||||||
|
LoadingView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(Color(hex: "222222"))
|
||||||
|
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
||||||
|
}
|
||||||
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
.onAppear {
|
||||||
|
soundManager.prepareRecording()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popup(isPresented: $soundManager.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
|
||||||
|
GeometryReader { geo in
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Text(soundManager.errorMessage)
|
||||||
|
.padding(.vertical, 13.3)
|
||||||
|
.padding(.horizontal, 6.7)
|
||||||
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.background(Color.button)
|
||||||
|
.foregroundColor(Color.white)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.cornerRadius(20)
|
||||||
|
.padding(.top, 66.7)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
if soundManager.onClose {
|
||||||
|
isShowing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
CreatorCommunityRecordingVoiceView(
|
||||||
|
isShowing: .constant(false),
|
||||||
|
isShowPopup: .constant(false),
|
||||||
|
errorMessage: .constant(""),
|
||||||
|
fileName: .constant(""),
|
||||||
|
soundData: .constant(nil)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
//
|
||||||
|
// CreatorCommunitySoundManager.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 8/7/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class CreatorCommunitySoundManager: NSObject, ObservableObject {
|
||||||
|
enum RecordMode {
|
||||||
|
case RECORD, PLAY
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var recordMode = RecordMode.RECORD
|
||||||
|
|
||||||
|
@Published var errorMessage = ""
|
||||||
|
@Published var isShowPopup = false
|
||||||
|
@Published var isLoading = false
|
||||||
|
@Published var onClose = false
|
||||||
|
|
||||||
|
@Published var isPlaying = false
|
||||||
|
@Published var isRecording = false
|
||||||
|
|
||||||
|
@Published var timeString = "00:00.00"
|
||||||
|
|
||||||
|
private var timerSubscription: Cancellable?
|
||||||
|
private var startTime: Date?
|
||||||
|
|
||||||
|
var player: AVAudioPlayer!
|
||||||
|
var audioRecorder: AVAudioRecorder!
|
||||||
|
|
||||||
|
var fileName = "now_voice.m4a"
|
||||||
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
|
|
||||||
|
func prepareRecording() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
do {
|
||||||
|
try audioSession.setCategory(.playAndRecord, mode: .videoRecording)
|
||||||
|
try audioSession.setActive(true)
|
||||||
|
audioSession.requestRecordPermission() { [weak self] allowed in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
if !allowed {
|
||||||
|
self?.errorMessage = "권한을 허용하지 않으시면 음성녹음을 하실 수 없습니다."
|
||||||
|
self?.isShowPopup = true
|
||||||
|
self?.onClose = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
isShowPopup = true
|
||||||
|
onClose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func startRecording(_ fileName: String) {
|
||||||
|
self.fileName = fileName
|
||||||
|
|
||||||
|
player?.stop()
|
||||||
|
player = nil
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
let settings = [
|
||||||
|
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
|
||||||
|
AVSampleRateKey: 48000,
|
||||||
|
AVEncoderBitRateKey: 256000,
|
||||||
|
AVNumberOfChannelsKey: 2,
|
||||||
|
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
|
||||||
|
]
|
||||||
|
|
||||||
|
do {
|
||||||
|
try audioSession.setCategory(.playAndRecord, mode: .videoRecording)
|
||||||
|
try audioSession.setActive(true)
|
||||||
|
|
||||||
|
audioRecorder = try AVAudioRecorder(url: getAudioFileURL(), settings: settings)
|
||||||
|
audioRecorder.record()
|
||||||
|
isRecording = true
|
||||||
|
|
||||||
|
startTimer()
|
||||||
|
} catch {
|
||||||
|
errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
isShowPopup = true
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopRecording() {
|
||||||
|
stopTimer()
|
||||||
|
|
||||||
|
audioRecorder?.stop()
|
||||||
|
audioRecorder = nil
|
||||||
|
isRecording = false
|
||||||
|
prepareForPlay()
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareForPlay(_ url: URL? = nil) {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
do {
|
||||||
|
try self.audioSession.setCategory(.playback, mode: .moviePlayback)
|
||||||
|
try self.audioSession.setActive(true)
|
||||||
|
|
||||||
|
if let url = url {
|
||||||
|
self.player = try AVAudioPlayer(data: Data(contentsOf: url))
|
||||||
|
} else {
|
||||||
|
self.player = try AVAudioPlayer(contentsOf: self.getAudioFileURL())
|
||||||
|
}
|
||||||
|
|
||||||
|
self.player?.volume = 1
|
||||||
|
self.player?.delegate = self
|
||||||
|
self.player?.prepareToPlay()
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func playAudio() {
|
||||||
|
player?.play()
|
||||||
|
|
||||||
|
isPlaying = player.isPlaying
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopAudio() {
|
||||||
|
stopTimer()
|
||||||
|
player?.stop()
|
||||||
|
player.currentTime = 0
|
||||||
|
isPlaying = player.isPlaying
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAudioFile() {
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: getAudioFileURL())
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAudioFileURL() -> URL {
|
||||||
|
return getDocumentsDirectory().appendingPathComponent(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getDocumentsDirectory() -> URL {
|
||||||
|
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||||
|
return paths[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTimer() {
|
||||||
|
timeString = "00:00.00"
|
||||||
|
startTime = Date()
|
||||||
|
timerSubscription = Timer.publish(every: 0.01, on: .main, in: .common)
|
||||||
|
.autoconnect()
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.updateTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopTimer() {
|
||||||
|
timeString = "00:00.00"
|
||||||
|
timerSubscription?.cancel()
|
||||||
|
|
||||||
|
startTime = nil
|
||||||
|
timerSubscription = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateTime() {
|
||||||
|
guard let startTime = startTime else { return }
|
||||||
|
|
||||||
|
let elapsedTime = Date().timeIntervalSince(startTime)
|
||||||
|
|
||||||
|
let minutes = Int(elapsedTime) / 60
|
||||||
|
let seconds = Int(elapsedTime) % 60
|
||||||
|
let centiseconds = Int((elapsedTime - Double(minutes * 60) - Double(seconds)) * 100)
|
||||||
|
|
||||||
|
timeString = String(format: "%02d:%02d.%02d", minutes, seconds, centiseconds)
|
||||||
|
|
||||||
|
if minutes >= 3 {
|
||||||
|
stopRecording()
|
||||||
|
recordMode = .PLAY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
player?.stop()
|
||||||
|
audioRecorder?.stop()
|
||||||
|
|
||||||
|
startTime = nil
|
||||||
|
timerSubscription?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CreatorCommunitySoundManager: AVAudioPlayerDelegate {
|
||||||
|
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
|
||||||
|
stopAudio()
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,9 @@ struct CreatorCommunityWriteView: View {
|
||||||
@StateObject var keyboardHandler = KeyboardHandler()
|
@StateObject var keyboardHandler = KeyboardHandler()
|
||||||
@StateObject private var viewModel = CreatorCommunityWriteViewModel()
|
@StateObject private var viewModel = CreatorCommunityWriteViewModel()
|
||||||
|
|
||||||
|
@State private var isShowRecordingVoiceView = false
|
||||||
@State private var isShowPhotoPicker = false
|
@State private var isShowPhotoPicker = false
|
||||||
|
@State private var fileName: String = "녹음"
|
||||||
let onSuccess: () -> Void
|
let onSuccess: () -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -27,7 +29,7 @@ struct CreatorCommunityWriteView: View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
Text("이미지")
|
Text("이미지")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -45,14 +47,14 @@ struct CreatorCommunityWriteView: View {
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.frame(width: 107, height: 107)
|
.frame(width: 107, height: 107)
|
||||||
.background(Color(hex: "13181B"))
|
.background(Color.bg)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
.clipped()
|
.clipped()
|
||||||
}
|
}
|
||||||
|
|
||||||
Image("ic_camera")
|
Image("ic_camera")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(Color(hex: "3BB9F1"))
|
.background(Color.button)
|
||||||
.cornerRadius(30)
|
.cornerRadius(30)
|
||||||
.offset(x: 50, y: 36)
|
.offset(x: 50, y: 36)
|
||||||
}
|
}
|
||||||
|
@ -63,28 +65,60 @@ struct CreatorCommunityWriteView: View {
|
||||||
HStack(alignment: .top, spacing: 0) {
|
HStack(alignment: .top, spacing: 0) {
|
||||||
Text("※ ")
|
Text("※ ")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
|
|
||||||
Text("등록할 이미지가 없으면 이미지 없이 게시글만 등록 하셔도 됩니다.")
|
Text("등록할 이미지가 없으면 이미지 없이 게시글만 등록 하셔도 됩니다.")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.top, 24)
|
.padding(.top, 24)
|
||||||
|
|
||||||
|
if let _ = viewModel.postImage {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("오디오 녹음")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(fileName)
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(Color.main)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.background(Color.bg)
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 5.3)
|
||||||
|
.stroke(Color.button, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.onTapGesture { isShowRecordingVoiceView = true }
|
||||||
|
|
||||||
|
|
||||||
|
Text("※ 오디오 녹음은 최대 3분입니다")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.gray77)
|
||||||
|
}
|
||||||
|
.padding(.top, 24)
|
||||||
|
}
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text("내용")
|
Text("내용")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("\(viewModel.content.count)자")
|
Text("\(viewModel.content.count)자")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "ff5c49")) +
|
.foregroundColor(Color.mainRed) +
|
||||||
Text(" / 최대 500자")
|
Text(" / 최대 500자")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
|
@ -101,7 +135,7 @@ struct CreatorCommunityWriteView: View {
|
||||||
VStack(spacing: 13.3) {
|
VStack(spacing: 13.3) {
|
||||||
Text("댓글 가능 여부")
|
Text("댓글 가능 여부")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -126,33 +160,92 @@ struct CreatorCommunityWriteView: View {
|
||||||
}
|
}
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
VStack(spacing: 13.3) {
|
if UserDefaults.bool(forKey: .auth) {
|
||||||
Text("연령 제한")
|
VStack(spacing: 13.3) {
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
Text("연령 제한")
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.foregroundColor(Color.grayee)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
HStack(spacing: 13.3) {
|
|
||||||
SelectButtonView(
|
|
||||||
title: "전체 연령",
|
|
||||||
isChecked: !viewModel.isAdult
|
|
||||||
) {
|
|
||||||
if viewModel.isAdult {
|
|
||||||
viewModel.isAdult = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectButtonView(
|
HStack(spacing: 13.3) {
|
||||||
title: "19세 이상",
|
SelectButtonView(
|
||||||
isChecked: viewModel.isAdult
|
title: "전체 연령",
|
||||||
) {
|
isChecked: !viewModel.isAdult
|
||||||
if !viewModel.isAdult {
|
) {
|
||||||
viewModel.isAdult = true
|
if viewModel.isAdult {
|
||||||
|
viewModel.isAdult = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectButtonView(
|
||||||
|
title: "19세 이상",
|
||||||
|
isChecked: viewModel.isAdult
|
||||||
|
) {
|
||||||
|
if !viewModel.isAdult {
|
||||||
|
viewModel.isAdult = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.top, 26.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let _ = viewModel.postImage {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
Text("가격 설정")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
SelectButtonView(
|
||||||
|
title: "무료",
|
||||||
|
isChecked: viewModel.isPriceFree
|
||||||
|
) {
|
||||||
|
if !viewModel.isPriceFree {
|
||||||
|
viewModel.isPriceFree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectButtonView(
|
||||||
|
title: "유료",
|
||||||
|
isChecked: !viewModel.isPriceFree
|
||||||
|
) {
|
||||||
|
if viewModel.isPriceFree {
|
||||||
|
viewModel.isPriceFree = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !viewModel.isPriceFree {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
TextField("", text: $viewModel.priceString)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.accentColor(Color.button)
|
||||||
|
.keyboardType(.numberPad)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text("캔")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
.padding(.vertical, 16.7)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 6.7)
|
||||||
|
.stroke(Color.gray77, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.background(Color.gray23)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 26.7)
|
||||||
}
|
}
|
||||||
.padding(.top, 26.7)
|
|
||||||
}
|
}
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
|
|
||||||
|
@ -160,14 +253,14 @@ struct CreatorCommunityWriteView: View {
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
Text("닫기")
|
Text("닫기")
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color(hex: "3BB9F1"))
|
.foregroundColor(Color.button)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
.background(Color(hex: "13181B"))
|
.background(Color.bg)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.stroke(Color(hex: "3BB9F1"), lineWidth: 1)
|
.stroke(Color.button, lineWidth: 1)
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -179,11 +272,12 @@ struct CreatorCommunityWriteView: View {
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
.background(Color(hex: "3BB9F1"))
|
.background(Color.button)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
viewModel.createCommunityPost {
|
viewModel.createCommunityPost {
|
||||||
|
deleteAudioFile()
|
||||||
AppState.shared.back()
|
AppState.shared.back()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
@ -194,17 +288,17 @@ struct CreatorCommunityWriteView: View {
|
||||||
}
|
}
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "222222"))
|
.foregroundColor(Color.gray22)
|
||||||
.frame(height: keyboardHandler.keyboardHeight)
|
.frame(height: keyboardHandler.keyboardHeight)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
if proxy.safeAreaInsets.bottom > 0 {
|
if proxy.safeAreaInsets.bottom > 0 {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "222222"))
|
.foregroundColor(Color.gray22)
|
||||||
.frame(height: 15.3)
|
.frame(height: 15.3)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
|
@ -221,6 +315,16 @@ struct CreatorCommunityWriteView: View {
|
||||||
sourceType: .photoLibrary
|
sourceType: .photoLibrary
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowRecordingVoiceView {
|
||||||
|
CreatorCommunityRecordingVoiceView(
|
||||||
|
isShowing: $isShowRecordingVoiceView,
|
||||||
|
isShowPopup: $viewModel.isShowPopup,
|
||||||
|
errorMessage: $viewModel.errorMessage,
|
||||||
|
fileName: $fileName,
|
||||||
|
soundData: $viewModel.soundData
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.onTapGesture { hideKeyboard() }
|
.onTapGesture { hideKeyboard() }
|
||||||
.edgesIgnoringSafeArea(.bottom)
|
.edgesIgnoringSafeArea(.bottom)
|
||||||
|
@ -232,7 +336,7 @@ struct CreatorCommunityWriteView: View {
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.cornerRadius(20)
|
.cornerRadius(20)
|
||||||
|
@ -244,6 +348,21 @@ struct CreatorCommunityWriteView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func deleteAudioFile() {
|
||||||
|
do {
|
||||||
|
try FileManager.default.removeItem(at: getAudioFileURL())
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getAudioFileURL() -> URL {
|
||||||
|
return getDocumentsDirectory().appendingPathComponent(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getDocumentsDirectory() -> URL {
|
||||||
|
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||||
|
return paths[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CreatorCommunityWriteView_Previews: PreviewProvider {
|
struct CreatorCommunityWriteView_Previews: PreviewProvider {
|
||||||
|
|
|
@ -20,16 +20,39 @@ final class CreatorCommunityWriteViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var content = ""
|
@Published var content = ""
|
||||||
@Published var isAdult = false
|
@Published var isAdult = false
|
||||||
|
@Published var isPriceFree = true {
|
||||||
|
didSet {
|
||||||
|
if isPriceFree {
|
||||||
|
priceString = "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@Published var isAvailableComment = true
|
@Published var isAvailableComment = true
|
||||||
@Published var postImage: UIImage? = nil
|
@Published var postImage: UIImage? = nil
|
||||||
|
|
||||||
|
@Published var priceString = "0" {
|
||||||
|
didSet {
|
||||||
|
if priceString.count > 5 {
|
||||||
|
priceString = String(priceString.prefix(5))
|
||||||
|
} else {
|
||||||
|
if let price = Int(priceString) {
|
||||||
|
self.price = price
|
||||||
|
} else {
|
||||||
|
self.price = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published var price = 0
|
||||||
|
@Published var soundData: Data? = nil
|
||||||
|
|
||||||
var placeholder = "내용을 입력하세요"
|
var placeholder = "내용을 입력하세요"
|
||||||
|
|
||||||
func createCommunityPost(onSuccess: @escaping () -> Void) {
|
func createCommunityPost(onSuccess: @escaping () -> Void) {
|
||||||
if !isLoading && validateData() {
|
if !isLoading && validateData() {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
let request = CreateCommunityPostRequest(content: content, isAdult: isAdult, isCommentAvailable: isAvailableComment)
|
let request = CreateCommunityPostRequest(content: content, price: price, isAdult: isAdult, isCommentAvailable: isAvailableComment)
|
||||||
var multipartData = [MultipartFormData]()
|
var multipartData = [MultipartFormData]()
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
|
@ -43,7 +66,19 @@ final class CreatorCommunityWriteViewModel: ObservableObject {
|
||||||
provider: .data(imageData),
|
provider: .data(imageData),
|
||||||
name: "postImage",
|
name: "postImage",
|
||||||
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg",
|
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).jpg",
|
||||||
mimeType: "image/*")
|
mimeType: "image/*"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let soundData = soundData {
|
||||||
|
multipartData.append(
|
||||||
|
MultipartFormData(
|
||||||
|
provider: .data(soundData),
|
||||||
|
name: "audioFile",
|
||||||
|
fileName: "\(UUID().uuidString)_\(Date().timeIntervalSince1970 * 1000).m4a",
|
||||||
|
mimeType: "audio/m4a"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +138,12 @@ final class CreatorCommunityWriteViewModel: ObservableObject {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isPriceFree && price < 5 {
|
||||||
|
errorMessage = "최소금액은 5캔 입니다."
|
||||||
|
isShowPopup = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ struct UserProfileFanTalkAllView: View {
|
||||||
@State private var cheersContent: String = ""
|
@State private var cheersContent: String = ""
|
||||||
@State private var cheersId: Int = 0
|
@State private var cheersId: Int = 0
|
||||||
|
|
||||||
|
@State private var memberId: Int = 0
|
||||||
|
@State private var isShowMemberProfilePopup: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
BaseView(isLoading: $viewModel.isLoading) {
|
BaseView(isLoading: $viewModel.isLoading) {
|
||||||
|
@ -26,17 +29,17 @@ struct UserProfileFanTalkAllView: View {
|
||||||
HStack(spacing: 6.7) {
|
HStack(spacing: 6.7) {
|
||||||
Text("응원")
|
Text("응원")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Text("\(viewModel.cheersTotalCount)")
|
Text("\(viewModel.cheersTotalCount)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
.foregroundColor(Color.gray90.opacity(0.5))
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
@ -44,8 +47,8 @@ struct UserProfileFanTalkAllView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
@ -61,18 +64,18 @@ struct UserProfileFanTalkAllView: View {
|
||||||
cheersContent = ""
|
cheersContent = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
.foregroundColor(Color.gray90.opacity(0.5))
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
|
@ -96,6 +99,10 @@ struct UserProfileFanTalkAllView: View {
|
||||||
onClickDelete: { cheersId in
|
onClickDelete: { cheersId in
|
||||||
self.cheersId = cheersId
|
self.cheersId = cheersId
|
||||||
viewModel.isShowCheersDeleteView = true
|
viewModel.isShowCheersDeleteView = true
|
||||||
|
},
|
||||||
|
onClickProfile: {
|
||||||
|
self.memberId = $0
|
||||||
|
self.isShowMemberProfilePopup = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
@ -110,7 +117,7 @@ struct UserProfileFanTalkAllView: View {
|
||||||
} else {
|
} else {
|
||||||
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
|
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
|
||||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.padding(.vertical, 60)
|
.padding(.vertical, 60)
|
||||||
|
@ -136,7 +143,7 @@ struct UserProfileFanTalkAllView: View {
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.cornerRadius(20)
|
.cornerRadius(20)
|
||||||
|
@ -171,6 +178,10 @@ struct UserProfileFanTalkAllView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMemberProfilePopup {
|
||||||
|
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
let modifyCheer: (Int, String) -> Void
|
let modifyCheer: (Int, String) -> Void
|
||||||
let reportPopup: (Int) -> Void
|
let reportPopup: (Int) -> Void
|
||||||
let onClickDelete: (Int) -> Void
|
let onClickDelete: (Int) -> Void
|
||||||
|
let onClickProfile: (Int) -> Void
|
||||||
|
|
||||||
@State var replyContent: String = ""
|
@State var replyContent: String = ""
|
||||||
@State var isShowInputReply = false
|
@State var isShowInputReply = false
|
||||||
|
@ -34,6 +35,11 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 33.3, height: 33.3)
|
.frame(width: 33.3, height: 33.3)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
|
.onTapGesture {
|
||||||
|
if UserDefaults.int(forKey: .userId) != cheersItem.memberId {
|
||||||
|
onClickProfile(cheersItem.memberId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text("\(cheersItem.nickname)")
|
Text("\(cheersItem.nickname)")
|
||||||
|
@ -51,10 +57,10 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
|
@ -65,7 +71,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
|
|
||||||
Text("수정")
|
Text("수정")
|
||||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "ffffff"))
|
.foregroundColor(Color.white)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color.button)
|
.background(Color.button)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
|
@ -78,7 +84,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color.button)
|
.foregroundColor(Color.button)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isModeModify = false
|
isModeModify = false
|
||||||
|
@ -100,23 +106,23 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
|
|
||||||
Text("등록")
|
Text("등록")
|
||||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "ffffff"))
|
.foregroundColor(Color.white)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "3bb9f1"))
|
.background(Color.button)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if cheersItem.replyList.count > 0 {
|
if cheersItem.replyList.count > 0 {
|
||||||
|
@ -143,23 +149,24 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
VStack(alignment: .leading, spacing: 8.3) {
|
VStack(alignment: .leading, spacing: 8.3) {
|
||||||
Text(reply.content)
|
Text(reply.content)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "ffffff"))
|
.foregroundColor(Color.white)
|
||||||
.frame(minWidth: 100)
|
.frame(minWidth: 100)
|
||||||
.padding(.horizontal, 6.7)
|
.padding(.horizontal, 6.7)
|
||||||
.padding(.vertical, 6.7)
|
.padding(.vertical, 6.7)
|
||||||
.background(Color.button.opacity(0.3))
|
.background(Color.button.opacity(0.3))
|
||||||
.cornerRadius(16.7)
|
.cornerRadius(16.7)
|
||||||
.padding(.top, 18.3)
|
.padding(.top, 18.3)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
HStack(spacing: 6.7) {
|
HStack(spacing: 6.7) {
|
||||||
Text(reply.date)
|
Text(reply.date)
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||||
.foregroundColor(Color(hex: "525252"))
|
.foregroundColor(Color.gray52)
|
||||||
|
|
||||||
if userId == UserDefaults.int(forKey: .userId) {
|
if userId == UserDefaults.int(forKey: .userId) {
|
||||||
Text("답글 수정")
|
Text("답글 수정")
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
self.replyContent = reply.content
|
self.replyContent = reply.content
|
||||||
isShowInputReply = true
|
isShowInputReply = true
|
||||||
|
@ -181,7 +188,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
.foregroundColor(Color.gray90.opacity(0.5))
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
}
|
}
|
||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
|
@ -191,7 +198,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
if cheersItem.memberId != UserDefaults.int(forKey: .userId) {
|
if cheersItem.memberId != UserDefaults.int(forKey: .userId) {
|
||||||
Text("신고하기")
|
Text("신고하기")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14))
|
.font(.custom(Font.medium.rawValue, size: 14))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
reportPopup(cheersItem.cheersId)
|
reportPopup(cheersItem.cheersId)
|
||||||
isShowPopupMenu = false
|
isShowPopupMenu = false
|
||||||
|
@ -201,7 +208,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
if cheersItem.memberId == UserDefaults.int(forKey: .userId) {
|
if cheersItem.memberId == UserDefaults.int(forKey: .userId) {
|
||||||
Text("수정")
|
Text("수정")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14))
|
.font(.custom(Font.medium.rawValue, size: 14))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isModeModify = true
|
isModeModify = true
|
||||||
isShowPopupMenu = false
|
isShowPopupMenu = false
|
||||||
|
@ -214,7 +221,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
{
|
{
|
||||||
Text("삭제")
|
Text("삭제")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14))
|
.font(.custom(Font.medium.rawValue, size: 14))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
onClickDelete(cheersItem.cheersId)
|
onClickDelete(cheersItem.cheersId)
|
||||||
isShowPopupMenu = false
|
isShowPopupMenu = false
|
||||||
|
@ -222,7 +229,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct UserProfileFanTalkView: View {
|
||||||
let errorPopup: (String) -> Void
|
let errorPopup: (String) -> Void
|
||||||
let reportPopup: (Int) -> Void
|
let reportPopup: (Int) -> Void
|
||||||
let deletePopup: (Int) -> Void
|
let deletePopup: (Int) -> Void
|
||||||
|
let profilePopup: (Int) -> Void
|
||||||
|
|
||||||
@Binding var isLoading: Bool
|
@Binding var isLoading: Bool
|
||||||
@State private var cheersContent: String = ""
|
@State private var cheersContent: String = ""
|
||||||
|
@ -26,13 +27,13 @@ struct UserProfileFanTalkView: View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text("팬 Talk")
|
Text("팬 Talk")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("전체보기")
|
Text("전체보기")
|
||||||
.font(.custom(Font.light.rawValue, size: 11.3))
|
.font(.custom(Font.light.rawValue, size: 11.3))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
AppState.shared.setAppStep(step: .userProfileFanTalkAll(userId: userId))
|
AppState.shared.setAppStep(step: .userProfileFanTalkAll(userId: userId))
|
||||||
}
|
}
|
||||||
|
@ -43,17 +44,17 @@ struct UserProfileFanTalkView: View {
|
||||||
HStack(spacing: 6.7) {
|
HStack(spacing: 6.7) {
|
||||||
Text("응원")
|
Text("응원")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Text("\(cheers.totalCount)")
|
Text("\(cheers.totalCount)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
.padding(.top, 20)
|
.padding(.top, 20)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
.foregroundColor(Color.gray90.opacity(0.5))
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
@ -61,8 +62,8 @@ struct UserProfileFanTalkView: View {
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
|
@ -78,18 +79,18 @@ struct UserProfileFanTalkView: View {
|
||||||
cheersContent = ""
|
cheersContent = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "232323"))
|
.background(Color.gray23)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
.foregroundColor(Color.gray90.opacity(0.5))
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 20) {
|
||||||
|
@ -110,6 +111,9 @@ struct UserProfileFanTalkView: View {
|
||||||
},
|
},
|
||||||
onClickDelete: { cheersId in
|
onClickDelete: { cheersId in
|
||||||
deletePopup(cheersId)
|
deletePopup(cheersId)
|
||||||
|
},
|
||||||
|
onClickProfile: {
|
||||||
|
profilePopup($0)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
@ -119,7 +123,7 @@ struct UserProfileFanTalkView: View {
|
||||||
} else {
|
} else {
|
||||||
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
|
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
|
||||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color.graybb)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.padding(.vertical, 60)
|
.padding(.vertical, 60)
|
||||||
|
|
|
@ -81,6 +81,9 @@ struct GetAudioContentListItem: Decodable {
|
||||||
let isPin: Bool
|
let isPin: Bool
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isScheduledToOpen: Bool
|
let isScheduledToOpen: Bool
|
||||||
|
let isRented: Bool
|
||||||
|
let isOwned: Bool
|
||||||
|
let isSoldOut: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GetCreatorActivitySummary: Decodable {
|
struct GetCreatorActivitySummary: Decodable {
|
||||||
|
|
|
@ -76,7 +76,10 @@ struct UserProfileContentView_Previews: PreviewProvider {
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
isPin: true,
|
isPin: true,
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
isScheduledToOpen: false
|
isScheduledToOpen: false,
|
||||||
|
isRented: false,
|
||||||
|
isOwned: false,
|
||||||
|
isSoldOut: false
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,9 @@ struct UserProfileView: View {
|
||||||
let userId: Int
|
let userId: Int
|
||||||
@StateObject var viewModel = UserProfileViewModel()
|
@StateObject var viewModel = UserProfileViewModel()
|
||||||
|
|
||||||
|
@State private var memberId: Int = 0
|
||||||
|
@State private var isShowMemberProfilePopup: Bool = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
BaseView(isLoading: $viewModel.isLoading) {
|
BaseView(isLoading: $viewModel.isLoading) {
|
||||||
|
@ -196,6 +199,10 @@ struct UserProfileView: View {
|
||||||
viewModel.cheersId = cheerId
|
viewModel.cheersId = cheerId
|
||||||
viewModel.isShowCheersDeleteView = true
|
viewModel.isShowCheersDeleteView = true
|
||||||
},
|
},
|
||||||
|
profilePopup: {
|
||||||
|
self.memberId = $0
|
||||||
|
self.isShowMemberProfilePopup = true
|
||||||
|
},
|
||||||
isLoading: $viewModel.isLoading
|
isLoading: $viewModel.isLoading
|
||||||
)
|
)
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
@ -311,6 +318,10 @@ struct UserProfileView: View {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isShowMemberProfilePopup {
|
||||||
|
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(
|
.sheet(
|
||||||
|
|
|
@ -14,36 +14,37 @@ struct FollowCreatorView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
BaseView(isLoading: $viewModel.isLoading) {
|
BaseView(isLoading: $viewModel.isLoading) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
DetailNavigationBar(title: "팔로잉 채널리스트")
|
DetailNavigationBar(title: "팔로잉 리스트")
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text("총 ")
|
Text("총 ")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Text("\(viewModel.totalCount)")
|
Text("\(viewModel.totalCount)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "dd4500"))
|
.foregroundColor(Color.mainRed3)
|
||||||
|
|
||||||
Text(" 명")
|
Text(" 명")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.padding(.top, 6.7)
|
.padding(.top, 6.7)
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
if viewModel.totalCount > 0 {
|
||||||
VStack(spacing: 13.3) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
ForEach(0..<viewModel.creatorList.count, id: \.self) { index in
|
VStack(spacing: 13.3) {
|
||||||
let creator = viewModel.creatorList[index]
|
ForEach(0..<viewModel.creatorList.count, id: \.self) { index in
|
||||||
|
let creator = viewModel.creatorList[index]
|
||||||
FollowCreatorItemView(
|
|
||||||
creator: creator,
|
FollowCreatorItemView(
|
||||||
onClickFollow: { viewModel.creatorFollow(userId: $0) },
|
creator: creator,
|
||||||
onClickUnFollow: { viewModel.creatorUnFollow(userId: $0) }
|
onClickFollow: { viewModel.creatorFollow(userId: $0) },
|
||||||
)
|
onClickUnFollow: { viewModel.creatorUnFollow(userId: $0) }
|
||||||
|
)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
AppState.shared
|
AppState.shared
|
||||||
|
@ -54,9 +55,15 @@ struct FollowCreatorView: View {
|
||||||
viewModel.getFollowedCreatorAllList()
|
viewModel.getFollowedCreatorAllList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
}
|
}
|
||||||
.padding(.top, 13.3)
|
} else {
|
||||||
|
Text("팔로우 중인 채널이 없습니다.")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
.frame(maxHeight: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
@ -69,7 +76,7 @@ struct FollowCreatorView: View {
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.background(Color(hex: "3bb9f1"))
|
.background(Color.button)
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.cornerRadius(20)
|
.cornerRadius(20)
|
||||||
|
|
|
@ -73,8 +73,8 @@ final class LiveRepository {
|
||||||
return api.requestPublisher(.getRoomInfo(roomId: roomId))
|
return api.requestPublisher(.getRoomInfo(roomId: roomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
func donation(roomId: Int, can: Int, message: String = "") -> AnyPublisher<Response, MoyaError> {
|
func donation(roomId: Int, can: Int, message: String = "", isSecret: Bool = false) -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.donation(request: LiveRoomDonationRequest(roomId: roomId, can: can, message: message)))
|
return api.requestPublisher(.donation(request: LiveRoomDonationRequest(roomId: roomId, can: can, message: message, isSecret: isSecret)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func refundDonation(roomId: Int) -> AnyPublisher<Response, MoyaError> {
|
func refundDonation(roomId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import Foundation
|
||||||
|
|
||||||
struct LiveRoomChatRawMessage: Codable {
|
struct LiveRoomChatRawMessage: Codable {
|
||||||
enum LiveRoomChatRawMessageType: String, Codable {
|
enum LiveRoomChatRawMessageType: String, Codable {
|
||||||
case DONATION, EDIT_ROOM_INFO, SET_MANAGER, TOGGLE_ROULETTE, ROULETTE_DONATION
|
case DONATION, SECRET_DONATION, EDIT_ROOM_INFO, SET_MANAGER, TOGGLE_ROULETTE, ROULETTE_DONATION
|
||||||
}
|
}
|
||||||
|
|
||||||
let type: LiveRoomChatRawMessageType
|
let type: LiveRoomChatRawMessageType
|
||||||
|
|
|
@ -43,7 +43,7 @@ struct LiveRoomDonationChatItemView: View {
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
.foregroundColor(Color(hex: "fdca2f"))
|
.foregroundColor(Color(hex: "fdca2f"))
|
||||||
|
|
||||||
Text("을 후원하셨습니다.")
|
Text(chatMessage.chat.contains("비밀") ? "을 비밀후원하셨습니다.💰🪙" : "을 후원하셨습니다.💰🪙")
|
||||||
.font(.system(size: 15))
|
.font(.system(size: 15))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,7 @@ struct LiveRoomDonationChatItemView: View {
|
||||||
.padding(13)
|
.padding(13)
|
||||||
.frame(width: screenSize().width - 86, alignment: .leading)
|
.frame(width: screenSize().width - 86, alignment: .leading)
|
||||||
.background(
|
.background(
|
||||||
|
chatMessage.chat.contains("비밀") ? Color(hex: "333333").opacity(0.8) :
|
||||||
chatMessage.can >= 10000 ? Color(hex: "c25264").opacity(0.8) :
|
chatMessage.can >= 10000 ? Color(hex: "c25264").opacity(0.8) :
|
||||||
chatMessage.can >= 5000 ? Color(hex: "d85e37").opacity(0.8) :
|
chatMessage.can >= 5000 ? Color(hex: "d85e37").opacity(0.8) :
|
||||||
chatMessage.can >= 1000 ? Color(hex: "d38c38").opacity(0.8) :
|
chatMessage.can >= 1000 ? Color(hex: "d38c38").opacity(0.8) :
|
||||||
|
|
|
@ -9,8 +9,10 @@ import SwiftUI
|
||||||
|
|
||||||
struct LiveRoomDonationMessageDialog: View {
|
struct LiveRoomDonationMessageDialog: View {
|
||||||
|
|
||||||
|
@StateObject var viewModel: LiveRoomViewModel
|
||||||
@Binding var isShowing: Bool
|
@Binding var isShowing: Bool
|
||||||
@StateObject var viewModel = LiveRoomViewModel()
|
|
||||||
|
@State var creatorId = 0
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -45,7 +47,7 @@ struct LiveRoomDonationMessageDialog: View {
|
||||||
ForEach(0..<viewModel.donationMessageList.count, id: \.self) { index in
|
ForEach(0..<viewModel.donationMessageList.count, id: \.self) { index in
|
||||||
let donationMessage = viewModel.donationMessageList[index]
|
let donationMessage = viewModel.donationMessageList[index]
|
||||||
|
|
||||||
LiveRoomDonationMessageItemView(message: donationMessage) {
|
LiveRoomDonationMessageItemView(message: donationMessage, creatorId: creatorId) {
|
||||||
viewModel.deleteDonationMessage(uuid: $0)
|
viewModel.deleteDonationMessage(uuid: $0)
|
||||||
}
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
@ -79,7 +81,7 @@ struct LiveRoomDonationMessageDialog: View {
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.frame(width: screenSize().width - 66.7, alignment: .center)
|
.frame(width: screenSize().width - 66.7, alignment: .center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.cornerRadius(20)
|
.cornerRadius(20)
|
||||||
|
@ -89,6 +91,7 @@ struct LiveRoomDonationMessageDialog: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.getDonationMessageList()
|
viewModel.getDonationMessageList()
|
||||||
|
creatorId = viewModel.liveRoomInfo?.creatorId ?? 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import SwiftUI
|
||||||
struct LiveRoomDonationMessageItemView: View {
|
struct LiveRoomDonationMessageItemView: View {
|
||||||
|
|
||||||
let message: LiveRoomDonationMessage
|
let message: LiveRoomDonationMessage
|
||||||
|
let creatorId: Int
|
||||||
let deleteDonationMessage: (String) -> Void
|
let deleteDonationMessage: (String) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -32,10 +33,12 @@ struct LiveRoomDonationMessageItemView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image("ic_close_white")
|
if creatorId == UserDefaults.int(forKey: .userId) {
|
||||||
.resizable()
|
Image("ic_close_white")
|
||||||
.frame(width: 13.3, height: 13.3)
|
.resizable()
|
||||||
.onTapGesture { deleteDonationMessage(message.uuid) }
|
.frame(width: 13.3, height: 13.3)
|
||||||
|
.onTapGesture { deleteDonationMessage(message.uuid) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(message.canMessage.trimmingCharacters(in: .whitespaces).isEmpty ? Color(hex: "c25264").opacity(0.8) : Color.gray33)
|
.background(message.canMessage.trimmingCharacters(in: .whitespaces).isEmpty ? Color(hex: "c25264").opacity(0.8) : Color.gray33)
|
||||||
|
@ -51,6 +54,7 @@ struct LiveRoomDonationMessageItemView: View {
|
||||||
canMessage: "10캔을 후원하셨습니다",
|
canMessage: "10캔을 후원하셨습니다",
|
||||||
donationMessage: "ㅅㅅㅅ"
|
donationMessage: "ㅅㅅㅅ"
|
||||||
),
|
),
|
||||||
|
creatorId: 0,
|
||||||
deleteDonationMessage: { _ in }
|
deleteDonationMessage: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -63,6 +67,7 @@ struct LiveRoomDonationMessageItemView: View {
|
||||||
canMessage: "",
|
canMessage: "",
|
||||||
donationMessage: "[테스트] 당첨!"
|
donationMessage: "[테스트] 당첨!"
|
||||||
),
|
),
|
||||||
|
creatorId: 0,
|
||||||
deleteDonationMessage: { _ in }
|
deleteDonationMessage: { _ in }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
|
|
||||||
@State private var title = ""
|
@State private var title = ""
|
||||||
@State private var notice = ""
|
@State private var notice = ""
|
||||||
|
@State private var isAdult = false
|
||||||
|
|
||||||
let placeholder = "라이브 공지를 입력하세요"
|
let placeholder = "라이브 공지를 입력하세요"
|
||||||
|
|
||||||
|
@ -23,24 +24,26 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
let isLoading: Bool
|
let isLoading: Bool
|
||||||
let coverImageUrl: String?
|
let coverImageUrl: String?
|
||||||
let coverImage: UIImage?
|
let coverImage: UIImage?
|
||||||
var confirmAction: (String, String) -> Void
|
var confirmAction: (String, String, Bool) -> Void
|
||||||
|
|
||||||
init(
|
init(
|
||||||
isShowing: Binding<Bool>,
|
isShowing: Binding<Bool>,
|
||||||
isShowPhotoPicker: Binding<Bool>,
|
isShowPhotoPicker: Binding<Bool>,
|
||||||
viewModel: LiveRoomViewModel,
|
viewModel: LiveRoomViewModel,
|
||||||
|
isAdult: Bool,
|
||||||
isLoading: Bool,
|
isLoading: Bool,
|
||||||
currentTitle: String,
|
currentTitle: String,
|
||||||
currentNotice: String,
|
currentNotice: String,
|
||||||
coverImageUrl: String,
|
coverImageUrl: String,
|
||||||
coverImage: UIImage?,
|
coverImage: UIImage?,
|
||||||
confirmAction: @escaping (String, String) -> Void
|
confirmAction: @escaping (String, String, Bool) -> Void
|
||||||
) {
|
) {
|
||||||
self._isShowing = isShowing
|
self._isShowing = isShowing
|
||||||
self._isShowPhotoPicker = isShowPhotoPicker
|
self._isShowPhotoPicker = isShowPhotoPicker
|
||||||
|
|
||||||
self._viewModel = StateObject(wrappedValue: viewModel)
|
self._viewModel = StateObject(wrappedValue: viewModel)
|
||||||
self.isLoading = isLoading
|
self.isLoading = isLoading
|
||||||
|
self.isAdult = isAdult
|
||||||
|
|
||||||
self.title = currentTitle
|
self.title = currentTitle
|
||||||
self.notice = currentNotice
|
self.notice = currentNotice
|
||||||
|
@ -116,6 +119,30 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
ContentInputView()
|
ContentInputView()
|
||||||
.padding(.top, 33.3)
|
.padding(.top, 33.3)
|
||||||
|
|
||||||
|
if UserDefaults.bool(forKey: .auth) {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("연령제한")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("19세 이상")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(isAdult ? "btn_toggle_on_big" : "btn_toggle_off_big")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 33.3, height: 20)
|
||||||
|
.onTapGesture {
|
||||||
|
isAdult.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 33.3)
|
||||||
|
}
|
||||||
|
|
||||||
LiveRoomMenuSelectView(
|
LiveRoomMenuSelectView(
|
||||||
menu: $viewModel.menu,
|
menu: $viewModel.menu,
|
||||||
isActivate: $viewModel.isActivateMenu,
|
isActivate: $viewModel.isActivateMenu,
|
||||||
|
@ -152,7 +179,8 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
confirmAction(
|
confirmAction(
|
||||||
title,
|
title,
|
||||||
notice.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? notice : ""
|
notice.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? notice : "",
|
||||||
|
isAdult
|
||||||
)
|
)
|
||||||
isShowing = false
|
isShowing = false
|
||||||
}
|
}
|
||||||
|
@ -169,7 +197,7 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
|
.background(Color.gray22.edgesIgnoringSafeArea(.all))
|
||||||
|
|
||||||
if viewModel.isShowPopup {
|
if viewModel.isShowPopup {
|
||||||
LiveRoomDialogView(
|
LiveRoomDialogView(
|
||||||
|
@ -198,7 +226,7 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text("제목")
|
Text("제목")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
TextField("라이브 제목을 입력하세요", text: $title)
|
TextField("라이브 제목을 입력하세요", text: $title)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
|
|
|
@ -148,7 +148,7 @@ struct LiveRoomProfilesDialogView: View {
|
||||||
role: listener.role,
|
role: listener.role,
|
||||||
onClickChangeListener: { _ in },
|
onClickChangeListener: { _ in },
|
||||||
onClickInviteSpeaker: {
|
onClickInviteSpeaker: {
|
||||||
if viewModel.liveRoomInfo!.speakerList.count <= 4 {
|
if viewModel.liveRoomInfo!.speakerList.count <= 5 {
|
||||||
viewModel.inviteSpeaker(peerId: $0)
|
viewModel.inviteSpeaker(peerId: $0)
|
||||||
viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
|
viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
|
||||||
viewModel.isShowPopup = true
|
viewModel.isShowPopup = true
|
||||||
|
|
|
@ -16,4 +16,5 @@ struct EditLiveRoomInfoRequest: Encodable {
|
||||||
var menuPanId: Int = 0
|
var menuPanId: Int = 0
|
||||||
var menuPan: String = ""
|
var menuPan: String = ""
|
||||||
var isActiveMenuPan: Bool? = nil
|
var isActiveMenuPan: Bool? = nil
|
||||||
|
var isAdult: Bool? = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,5 +11,6 @@ struct LiveRoomDonationRequest: Encodable {
|
||||||
let roomId: Int
|
let roomId: Int
|
||||||
let can: Int
|
let can: Int
|
||||||
let message: String
|
let message: String
|
||||||
|
var isSecret: Bool = false
|
||||||
let container: String = "ios"
|
let container: String = "ios"
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
@Published var isShowReportPopup = false
|
@Published var isShowReportPopup = false
|
||||||
@Published var isShowErrorPopup = false
|
@Published var isShowErrorPopup = false
|
||||||
@Published var isShowUserProfilePopup = false
|
@Published var isShowUserProfilePopup = false
|
||||||
|
@Published var changeIsAdult = false {
|
||||||
|
didSet {
|
||||||
|
if changeIsAdult && !UserDefaults.bool(forKey: .auth) {
|
||||||
|
agora.speakerMute(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Published var popupContent = ""
|
@Published var popupContent = ""
|
||||||
@Published var popupCancelTitle: String? = nil
|
@Published var popupCancelTitle: String? = nil
|
||||||
|
@ -189,6 +196,34 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
var timer: DispatchSourceTimer?
|
var timer: DispatchSourceTimer?
|
||||||
|
|
||||||
|
private var blockedMemberIdList = Set<Int>()
|
||||||
|
|
||||||
|
func getBlockedMemberIdList() {
|
||||||
|
userRepository.getBlockedMemberIdList()
|
||||||
|
.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<[Int]>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.blockedMemberIdList.removeAll()
|
||||||
|
self.blockedMemberIdList.formUnion(data)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
func setOriginOffset(_ offset: CGFloat) {
|
func setOriginOffset(_ offset: CGFloat) {
|
||||||
guard !isCheckedOriginOffset else { return }
|
guard !isCheckedOriginOffset else { return }
|
||||||
self.originOffset = offset
|
self.originOffset = offset
|
||||||
|
@ -314,6 +349,10 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
getTotalDonationCan()
|
getTotalDonationCan()
|
||||||
|
|
||||||
|
if data.isAdult && !UserDefaults.bool(forKey: .auth) {
|
||||||
|
changeIsAdult = true
|
||||||
|
}
|
||||||
|
|
||||||
if (userId > 0 && data.creatorId == UserDefaults.int(forKey: .userId)) {
|
if (userId > 0 && data.creatorId == UserDefaults.int(forKey: .userId)) {
|
||||||
let nickname = getUserNicknameAndProfileUrl(accountId: userId).nickname
|
let nickname = getUserNicknameAndProfileUrl(accountId: userId).nickname
|
||||||
onSuccess(nickname)
|
onSuccess(nickname)
|
||||||
|
@ -380,11 +419,11 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func donation(can: Int, message: String = "") {
|
func donation(can: Int, message: String = "", isSecret: Bool = false) {
|
||||||
if can > 0 {
|
if can > 0 {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
repository.donation(roomId: AppState.shared.roomId, can: can, message: message)
|
repository.donation(roomId: AppState.shared.roomId, can: can, message: message, isSecret: isSecret)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
@ -402,9 +441,16 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
|
|
||||||
if decoded.success {
|
if decoded.success {
|
||||||
let rawMessage = "\(can)캔을 후원하셨습니다."
|
var rawMessage = ""
|
||||||
|
|
||||||
|
if isSecret {
|
||||||
|
rawMessage = "\(can)캔을 비밀후원하셨습니다.💰🪙"
|
||||||
|
} else {
|
||||||
|
rawMessage = "\(can)캔을 후원하셨습니다.💰🪙"
|
||||||
|
}
|
||||||
|
|
||||||
let donationRawMessage = LiveRoomChatRawMessage(
|
let donationRawMessage = LiveRoomChatRawMessage(
|
||||||
type: .DONATION,
|
type: isSecret ? .SECRET_DONATION : .DONATION,
|
||||||
message: rawMessage,
|
message: rawMessage,
|
||||||
can: can,
|
can: can,
|
||||||
signature: decoded.data,
|
signature: decoded.data,
|
||||||
|
@ -414,36 +460,68 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can)
|
UserDefaults.set(UserDefaults.int(forKey: .can) - can, forKey: .can)
|
||||||
|
|
||||||
agora.sendRawMessageToGroup(
|
if isSecret {
|
||||||
rawMessage: donationRawMessage,
|
agora.sendRawMessageToPeer(
|
||||||
completion: { [unowned self] errorCode in
|
peerId: String(liveRoomInfo!.creatorId), rawMessage: donationRawMessage,
|
||||||
if errorCode == .errorOk {
|
completion: { [unowned self] errorCode in
|
||||||
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
|
if errorCode == .ok {
|
||||||
self.messages.append(
|
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
|
||||||
LiveRoomDonationChat(
|
self.messages.append(
|
||||||
profileUrl: profileUrl,
|
LiveRoomDonationChat(
|
||||||
nickname: nickname,
|
profileUrl: profileUrl,
|
||||||
chat: rawMessage,
|
nickname: nickname,
|
||||||
can: can,
|
chat: rawMessage,
|
||||||
donationMessage: message
|
can: can,
|
||||||
|
donationMessage: message
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
addSignature(signature: decoded.data)
|
||||||
totalDonationCan += can
|
|
||||||
addSignature(signature: decoded.data)
|
self.messageChangeFlag.toggle()
|
||||||
|
if self.messages.count > 100 {
|
||||||
self.messageChangeFlag.toggle()
|
self.messages.remove(at: 0)
|
||||||
if self.messages.count > 100 {
|
}
|
||||||
self.messages.remove(at: 0)
|
} else {
|
||||||
|
refundDonation()
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
|
fail: { [unowned self] in
|
||||||
refundDonation()
|
refundDonation()
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
fail: { [unowned self] in
|
} else {
|
||||||
refundDonation()
|
agora.sendRawMessageToGroup(
|
||||||
}
|
rawMessage: donationRawMessage,
|
||||||
)
|
completion: { [unowned self] errorCode in
|
||||||
|
if errorCode == .errorOk {
|
||||||
|
let (nickname, profileUrl) = self.getUserNicknameAndProfileUrl(accountId: UserDefaults.int(forKey: .userId))
|
||||||
|
self.messages.append(
|
||||||
|
LiveRoomDonationChat(
|
||||||
|
profileUrl: profileUrl,
|
||||||
|
nickname: nickname,
|
||||||
|
chat: rawMessage,
|
||||||
|
can: can,
|
||||||
|
donationMessage: message
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
totalDonationCan += can
|
||||||
|
addSignature(signature: decoded.data)
|
||||||
|
|
||||||
|
self.messageChangeFlag.toggle()
|
||||||
|
if self.messages.count > 100 {
|
||||||
|
self.messages.remove(at: 0)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refundDonation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: { [unowned self] in
|
||||||
|
refundDonation()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let message = decoded.message {
|
if let message = decoded.message {
|
||||||
self.popupContent = message
|
self.popupContent = message
|
||||||
|
@ -640,7 +718,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
agora.sendMessageToPeer(peerId: peerId, rawMessage: LiveRoomRequestType.REQUEST_SPEAKER_ALLOW.rawValue.data(using: .utf8)!, completion: nil)
|
agora.sendMessageToPeer(peerId: peerId, rawMessage: LiveRoomRequestType.REQUEST_SPEAKER_ALLOW.rawValue.data(using: .utf8)!, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func editLiveRoomInfo(title: String, notice: String) {
|
func editLiveRoomInfo(title: String, notice: String, isAdult: Bool) {
|
||||||
let request = EditLiveRoomInfoRequest(
|
let request = EditLiveRoomInfoRequest(
|
||||||
title: liveRoomInfo!.title != title ? title : nil,
|
title: liveRoomInfo!.title != title ? title : nil,
|
||||||
notice: liveRoomInfo!.notice != notice ? notice : nil,
|
notice: liveRoomInfo!.notice != notice ? notice : nil,
|
||||||
|
@ -649,10 +727,11 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
timezone: nil,
|
timezone: nil,
|
||||||
menuPanId: isActivateMenu ? menuId : 0,
|
menuPanId: isActivateMenu ? menuId : 0,
|
||||||
menuPan: isActivateMenu ? menu : "",
|
menuPan: isActivateMenu ? menu : "",
|
||||||
isActiveMenuPan: isActivateMenu
|
isActiveMenuPan: isActivateMenu,
|
||||||
|
isAdult: liveRoomInfo!.isAdult != isAdult ? isAdult : nil
|
||||||
)
|
)
|
||||||
|
|
||||||
if (request.title == nil && request.notice == nil && coverImage == nil && menu == liveRoomInfo?.menuPan) {
|
if (request.title == nil && request.notice == nil && coverImage == nil && menu == liveRoomInfo?.menuPan && request.isAdult == nil) {
|
||||||
self.errorMessage = "변경사항이 없습니다."
|
self.errorMessage = "변경사항이 없습니다."
|
||||||
self.isShowErrorPopup = true
|
self.isShowErrorPopup = true
|
||||||
return
|
return
|
||||||
|
@ -663,7 +742,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
encoder.outputFormatting = .withoutEscapingSlashes
|
encoder.outputFormatting = .withoutEscapingSlashes
|
||||||
|
|
||||||
if (request.title != nil || request.notice != nil || menu != liveRoomInfo?.menuPan) {
|
if (request.title != nil || request.notice != nil || request.isAdult != nil || menu != liveRoomInfo?.menuPan) {
|
||||||
let jsonData = try? encoder.encode(request)
|
let jsonData = try? encoder.encode(request)
|
||||||
if let jsonData = jsonData {
|
if let jsonData = jsonData {
|
||||||
multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request"))
|
multipartData.append(MultipartFormData(provider: .data(jsonData), name: "request"))
|
||||||
|
@ -814,22 +893,66 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
.store(in: &subscription)
|
.store(in: &subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
func kickOut() {
|
func shareRoom() {
|
||||||
repository.kickOut(roomId: AppState.shared.roomId, userId: kickOutId)
|
guard let link = URL(string: "https://sodalive.net/?room_id=\(AppState.shared.roomId)") else { return }
|
||||||
.sink { result in
|
let dynamicLinksDomainURIPrefix = "https://sodalive.page.link"
|
||||||
switch result {
|
guard let linkBuilder = DynamicLinkComponents(link: link, domainURIPrefix: dynamicLinksDomainURIPrefix) else {
|
||||||
case .finished:
|
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
|
||||||
DEBUG_LOG("finish")
|
self.isShowErrorPopup = true
|
||||||
case .failure(let error):
|
return
|
||||||
ERROR_LOG(error.localizedDescription)
|
}
|
||||||
}
|
|
||||||
} receiveValue: { _ in
|
|
||||||
|
|
||||||
}
|
|
||||||
.store(in: &subscription)
|
|
||||||
|
|
||||||
let nickname = getUserNicknameAndProfileUrl(accountId: kickOutId).nickname
|
linkBuilder.iOSParameters = DynamicLinkIOSParameters(bundleID: "kr.co.vividnext.sodalive")
|
||||||
|
linkBuilder.iOSParameters?.appStoreID = "6461721697"
|
||||||
|
|
||||||
|
linkBuilder.androidParameters = DynamicLinkAndroidParameters(packageName: "kr.co.vividnext.sodalive")
|
||||||
|
|
||||||
|
guard let longDynamicLink = linkBuilder.url else {
|
||||||
|
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
DEBUG_LOG("The long URL is: \(longDynamicLink)")
|
||||||
|
|
||||||
|
DynamicLinkComponents.shortenURL(longDynamicLink, options: nil) { [unowned self] url, warnings, error in
|
||||||
|
let shortUrl = url?.absoluteString
|
||||||
|
|
||||||
|
if let liveRoomInfo = self.liveRoomInfo {
|
||||||
|
let urlString = shortUrl != nil ? shortUrl! : longDynamicLink.absoluteString
|
||||||
|
if liveRoomInfo.isPrivateRoom {
|
||||||
|
shareMessage = "\(UserDefaults.string(forKey: .nickname))님이 귀하를 소다라이브 비공개라이브에 초대하였습니다.\n" +
|
||||||
|
"※ 라이브 참여: \(urlString)\n" +
|
||||||
|
"(입장 비밀번호: \(liveRoomInfo.password!))"
|
||||||
|
} else {
|
||||||
|
shareMessage = "\(UserDefaults.string(forKey: .nickname))님이 귀하를 소다라이브 공개라이브에 초대하였습니다.\n" +
|
||||||
|
"※ 라이브 참여: \(urlString)"
|
||||||
|
}
|
||||||
|
|
||||||
|
isShowShareView = true
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "공유링크를 생성하지 못했습니다.\n다시 시도해 주세요."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func kickOut() {
|
||||||
if UserDefaults.int(forKey: .userId) == liveRoomInfo?.creatorId {
|
if UserDefaults.int(forKey: .userId) == liveRoomInfo?.creatorId {
|
||||||
|
repository.kickOut(roomId: AppState.shared.roomId, userId: kickOutId)
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { _ in
|
||||||
|
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
|
||||||
|
let nickname = getUserNicknameAndProfileUrl(accountId: kickOutId).nickname
|
||||||
agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in
|
agora.sendMessageToPeer(peerId: String(kickOutId), rawMessage: LiveRoomRequestType.KICK_OUT.rawValue.data(using: .utf8)!, completion: { [unowned self] errorCode in
|
||||||
if errorCode == .ok {
|
if errorCode == .ok {
|
||||||
self.popupContent = "\(nickname)님을 내보냈습니다."
|
self.popupContent = "\(nickname)님을 내보냈습니다."
|
||||||
|
@ -1248,6 +1371,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func userBlock(onSuccess: @escaping (Int) -> Void) {
|
func userBlock(onSuccess: @escaping (Int) -> Void) {
|
||||||
|
blockedMemberIdList.insert(reportUserId)
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
userRepository.memberBlock(userId: reportUserId)
|
userRepository.memberBlock(userId: reportUserId)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
|
@ -1291,6 +1416,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func userUnBlock() {
|
func userUnBlock() {
|
||||||
|
blockedMemberIdList.remove(reportUserId)
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
userRepository.memberUnBlock(userId: reportUserId)
|
userRepository.memberUnBlock(userId: reportUserId)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
|
@ -1738,7 +1865,7 @@ extension LiveRoomViewModel: AgoraRtmDelegate {
|
||||||
self.popupConfirmTitle = "스피커로 초대"
|
self.popupConfirmTitle = "스피커로 초대"
|
||||||
self.popupConfirmAction = {
|
self.popupConfirmAction = {
|
||||||
self.isShowPopup = false
|
self.isShowPopup = false
|
||||||
if self.liveRoomInfo!.speakerList.count <= 4 {
|
if self.liveRoomInfo!.speakerList.count <= 5 {
|
||||||
self.requestSpeakerAllow(peerId)
|
self.requestSpeakerAllow(peerId)
|
||||||
} else {
|
} else {
|
||||||
self.errorMessage = "스피커 정원이 초과되었습니다."
|
self.errorMessage = "스피커 정원이 초과되었습니다."
|
||||||
|
@ -1814,6 +1941,31 @@ extension LiveRoomViewModel: AgoraRtmDelegate {
|
||||||
self.startNoChatting()
|
self.startNoChatting()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(LiveRoomChatRawMessage.self, from: rawMessage.rawData)
|
||||||
|
let (nickname, profileUrl) = getUserNicknameAndProfileUrl(accountId: Int(peerId)!)
|
||||||
|
|
||||||
|
if decoded.type == .SECRET_DONATION {
|
||||||
|
self.messages.append(
|
||||||
|
LiveRoomDonationChat(
|
||||||
|
profileUrl: profileUrl,
|
||||||
|
nickname: nickname,
|
||||||
|
chat: decoded.message,
|
||||||
|
can: decoded.can,
|
||||||
|
donationMessage: decoded.donationMessage ?? ""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if let signature = decoded.signature {
|
||||||
|
self.addSignature(signature: signature)
|
||||||
|
} else if let imageUrl = decoded.signatureImageUrl {
|
||||||
|
self.addSignatureImage(imageUrl: imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1864,11 +2016,12 @@ extension LiveRoomViewModel: AgoraRtmChannelDelegate {
|
||||||
} catch {
|
} catch {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let memberId = Int(member.userId) ?? 0
|
||||||
let chat = message.text
|
let chat = message.text
|
||||||
let rank = getUserRank(userId: Int(member.userId) ?? 0)
|
let rank = getUserRank(userId: memberId)
|
||||||
|
|
||||||
if !chat.trimmingCharacters(in: .whitespaces).isEmpty {
|
if !chat.trimmingCharacters(in: .whitespaces).isEmpty && !blockedMemberIdList.contains(memberId) {
|
||||||
messages.append(LiveRoomNormalChat(userId: Int(member.userId)!, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chat))
|
messages.append(LiveRoomNormalChat(userId: memberId, profileUrl: profileUrl, nickname: nickname, rank: rank, chat: chat))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if totalPercentage != Float(100) {
|
if totalPercentage > Float(100.1) || totalPercentage <= Float(99.99) {
|
||||||
isLoading = false
|
isLoading = false
|
||||||
errorMessage = "확률이 100%가 아닙니다"
|
errorMessage = "확률이 100%가 아닙니다"
|
||||||
isShowErrorPopup = true
|
isShowErrorPopup = true
|
||||||
|
|
|
@ -30,6 +30,7 @@ struct LiveRoomInfoGuestView: View {
|
||||||
|
|
||||||
let onClickQuit: () -> Void
|
let onClickQuit: () -> Void
|
||||||
let onClickToggleBg: () -> Void
|
let onClickToggleBg: () -> Void
|
||||||
|
let onClickShare: () -> Void
|
||||||
let onClickFollow: (Bool) -> Void
|
let onClickFollow: (Bool) -> Void
|
||||||
let onClickProfile: (Int) -> Void
|
let onClickProfile: (Int) -> Void
|
||||||
let onClickNotice: () -> Void
|
let onClickNotice: () -> Void
|
||||||
|
@ -85,6 +86,13 @@ struct LiveRoomInfoGuestView: View {
|
||||||
strokeWidth: 1,
|
strokeWidth: 1,
|
||||||
strokeCornerRadius: 5.3
|
strokeCornerRadius: 5.3
|
||||||
) { onClickToggleBg() }
|
) { onClickToggleBg() }
|
||||||
|
|
||||||
|
LiveRoomOverlayStrokeImageButton(
|
||||||
|
imageName: "ic_share",
|
||||||
|
strokeColor: Color.graybb,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeCornerRadius: 5.3
|
||||||
|
) { onClickShare() }
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
|
@ -211,6 +219,24 @@ struct LiveRoomInfoGuestView_Previews: PreviewProvider {
|
||||||
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
|
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
|
||||||
role: .SPEAKER
|
role: .SPEAKER
|
||||||
),
|
),
|
||||||
|
LiveRoomMember(
|
||||||
|
id: 4,
|
||||||
|
nickname: "도화",
|
||||||
|
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
role: .SPEAKER
|
||||||
|
),
|
||||||
|
LiveRoomMember(
|
||||||
|
id: 5,
|
||||||
|
nickname: "도화",
|
||||||
|
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
role: .SPEAKER
|
||||||
|
),
|
||||||
|
LiveRoomMember(
|
||||||
|
id: 6,
|
||||||
|
nickname: "도화",
|
||||||
|
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
role: .SPEAKER
|
||||||
|
),
|
||||||
],
|
],
|
||||||
muteSpeakerList: [],
|
muteSpeakerList: [],
|
||||||
activeSpeakerList: [],
|
activeSpeakerList: [],
|
||||||
|
@ -218,6 +244,7 @@ struct LiveRoomInfoGuestView_Previews: PreviewProvider {
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
onClickQuit: {},
|
onClickQuit: {},
|
||||||
onClickToggleBg: {},
|
onClickToggleBg: {},
|
||||||
|
onClickShare: {},
|
||||||
onClickFollow: { _ in },
|
onClickFollow: { _ in },
|
||||||
onClickProfile: { _ in },
|
onClickProfile: { _ in },
|
||||||
onClickNotice: {},
|
onClickNotice: {},
|
||||||
|
|
|
@ -31,6 +31,7 @@ struct LiveRoomInfoHostView: View {
|
||||||
|
|
||||||
let onClickQuit: () -> Void
|
let onClickQuit: () -> Void
|
||||||
let onClickToggleBg: () -> Void
|
let onClickToggleBg: () -> Void
|
||||||
|
let onClickShare: () -> Void
|
||||||
let onClickEdit: () -> Void
|
let onClickEdit: () -> Void
|
||||||
let onClickProfile: (Int) -> Void
|
let onClickProfile: (Int) -> Void
|
||||||
let onClickNotice: () -> Void
|
let onClickNotice: () -> Void
|
||||||
|
@ -77,6 +78,13 @@ struct LiveRoomInfoHostView: View {
|
||||||
strokeCornerRadius: 5.3
|
strokeCornerRadius: 5.3
|
||||||
) { onClickToggleBg() }
|
) { onClickToggleBg() }
|
||||||
|
|
||||||
|
LiveRoomOverlayStrokeImageButton(
|
||||||
|
imageName: "ic_share",
|
||||||
|
strokeColor: Color.graybb,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeCornerRadius: 5.3
|
||||||
|
) { onClickShare() }
|
||||||
|
|
||||||
LiveRoomOverlayStrokeImageButton(
|
LiveRoomOverlayStrokeImageButton(
|
||||||
imageName: "ic_edit",
|
imageName: "ic_edit",
|
||||||
strokeColor: Color.graybb,
|
strokeColor: Color.graybb,
|
||||||
|
@ -227,12 +235,31 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider {
|
||||||
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
|
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
|
||||||
role: .SPEAKER
|
role: .SPEAKER
|
||||||
),
|
),
|
||||||
|
LiveRoomMember(
|
||||||
|
id: 4,
|
||||||
|
nickname: "도화",
|
||||||
|
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
role: .SPEAKER
|
||||||
|
),
|
||||||
|
LiveRoomMember(
|
||||||
|
id: 5,
|
||||||
|
nickname: "도화",
|
||||||
|
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
role: .SPEAKER
|
||||||
|
),
|
||||||
|
LiveRoomMember(
|
||||||
|
id: 6,
|
||||||
|
nickname: "도화",
|
||||||
|
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
role: .SPEAKER
|
||||||
|
),
|
||||||
],
|
],
|
||||||
muteSpeakerList: [],
|
muteSpeakerList: [],
|
||||||
activeSpeakerList: [],
|
activeSpeakerList: [],
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
onClickQuit: {},
|
onClickQuit: {},
|
||||||
onClickToggleBg: {},
|
onClickToggleBg: {},
|
||||||
|
onClickShare: {},
|
||||||
onClickEdit: {},
|
onClickEdit: {},
|
||||||
onClickProfile: { _ in },
|
onClickProfile: { _ in },
|
||||||
onClickNotice: {},
|
onClickNotice: {},
|
||||||
|
|
|
@ -45,6 +45,9 @@ struct LiveRoomViewV2: View {
|
||||||
onClickToggleBg: {
|
onClickToggleBg: {
|
||||||
viewModel.isBgOn.toggle()
|
viewModel.isBgOn.toggle()
|
||||||
},
|
},
|
||||||
|
onClickShare: {
|
||||||
|
viewModel.shareRoom()
|
||||||
|
},
|
||||||
onClickEdit: {
|
onClickEdit: {
|
||||||
viewModel.isShowEditRoomInfoDialog = true
|
viewModel.isShowEditRoomInfoDialog = true
|
||||||
},
|
},
|
||||||
|
@ -92,6 +95,9 @@ struct LiveRoomViewV2: View {
|
||||||
onClickToggleBg: {
|
onClickToggleBg: {
|
||||||
viewModel.isBgOn.toggle()
|
viewModel.isBgOn.toggle()
|
||||||
},
|
},
|
||||||
|
onClickShare: {
|
||||||
|
viewModel.shareRoom()
|
||||||
|
},
|
||||||
onClickFollow: {
|
onClickFollow: {
|
||||||
if $0 {
|
if $0 {
|
||||||
viewModel.creatorUnFollow()
|
viewModel.creatorUnFollow()
|
||||||
|
@ -145,16 +151,18 @@ struct LiveRoomViewV2: View {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
scrollObservableView
|
scrollObservableView
|
||||||
|
|
||||||
LiveRoomChatView(messages: viewModel.messages) {
|
if !viewModel.changeIsAdult || UserDefaults.bool(forKey: .auth) {
|
||||||
if $0 != UserDefaults.int(forKey: .userId) {
|
LiveRoomChatView(messages: viewModel.messages) {
|
||||||
viewModel.getUserProfile(userId: $0)
|
if $0 != UserDefaults.int(forKey: .userId) {
|
||||||
|
viewModel.getUserProfile(userId: $0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.frame(width: screenSize().width)
|
||||||
.frame(width: screenSize().width)
|
.rotationEffect(Angle(degrees: 180))
|
||||||
.rotationEffect(Angle(degrees: 180))
|
.valueChanged(value: viewModel.messageChangeFlag) { _ in
|
||||||
.valueChanged(value: viewModel.messageChangeFlag) { _ in
|
if viewModel.offset - viewModel.originOffset > (56.7 * 2) {
|
||||||
if viewModel.offset - viewModel.originOffset > (56.7 * 2) {
|
viewModel.isShowingNewChat = true
|
||||||
viewModel.isShowingNewChat = true
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,13 +205,12 @@ struct LiveRoomViewV2: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) &&
|
LiveRoomRightBottomButton(
|
||||||
UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
|
imageName: "ic_donation_message_list",
|
||||||
LiveRoomRightBottomButton(
|
onClick: { viewModel.isShowDonationMessagePopup = true }
|
||||||
imageName: "ic_donation_message_list",
|
)
|
||||||
onClick: { viewModel.isShowDonationMessagePopup = true }
|
|
||||||
)
|
if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) {
|
||||||
} else {
|
|
||||||
LiveRoomRightBottomButton(
|
LiveRoomRightBottomButton(
|
||||||
imageName: "ic_donation",
|
imageName: "ic_donation",
|
||||||
onClick: { viewModel.isShowDonationPopup = true }
|
onClick: { viewModel.isShowDonationPopup = true }
|
||||||
|
@ -353,6 +360,7 @@ struct LiveRoomViewV2: View {
|
||||||
viewModel.getMemberCan()
|
viewModel.getMemberCan()
|
||||||
viewModel.initAgoraEngine()
|
viewModel.initAgoraEngine()
|
||||||
viewModel.getRoomInfo()
|
viewModel.getRoomInfo()
|
||||||
|
viewModel.getBlockedMemberIdList()
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(
|
NotificationCenter.default.addObserver(
|
||||||
forName: UIApplication.willTerminateNotification,
|
forName: UIApplication.willTerminateNotification,
|
||||||
|
@ -387,11 +395,22 @@ struct LiveRoomViewV2: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if viewModel.isShowDonationPopup {
|
if viewModel.isShowDonationPopup {
|
||||||
LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: false) { can, message in
|
LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: false) { can, message, isSecret in
|
||||||
viewModel.donation(can: can, message: message)
|
viewModel.donation(can: can, message: message, isSecret: isSecret)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.changeIsAdult && !UserDefaults.bool(forKey: .auth) {
|
||||||
|
SodaDialog(
|
||||||
|
title: "알림",
|
||||||
|
desc: "지금 참여하던 라이브는 '19세 이상' 연령제한이 설정되어 정보통신망 이용촉진 및 정보 보호 등에 관한 법률 및 청소년 보호법의 규정에 의해 만 19세 미만의 청소년은 이용할 수 없습니다.\n마이페이지에서 본인인증 후 다시 이용하시기 바랍니다.",
|
||||||
|
confirmButtonTitle: "확인",
|
||||||
|
confirmButtonAction: {
|
||||||
|
viewModel.quitRoom()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if viewModel.isShowQuitPopup {
|
if viewModel.isShowQuitPopup {
|
||||||
SodaDialog(
|
SodaDialog(
|
||||||
title: "라이브 나가기",
|
title: "라이브 나가기",
|
||||||
|
@ -515,7 +534,7 @@ struct LiveRoomViewV2: View {
|
||||||
)
|
)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "222222"))
|
.foregroundColor(Color.gray22)
|
||||||
.frame(width: screenSize().width, height: 15.3)
|
.frame(width: screenSize().width, height: 15.3)
|
||||||
}
|
}
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
@ -640,15 +659,17 @@ struct LiveRoomViewV2: View {
|
||||||
isShowing: $viewModel.isShowEditRoomInfoDialog,
|
isShowing: $viewModel.isShowEditRoomInfoDialog,
|
||||||
isShowPhotoPicker: $viewModel.isShowPhotoPicker,
|
isShowPhotoPicker: $viewModel.isShowPhotoPicker,
|
||||||
viewModel: viewModel,
|
viewModel: viewModel,
|
||||||
|
isAdult: liveRoomInfo.isAdult,
|
||||||
isLoading: viewModel.isLoading,
|
isLoading: viewModel.isLoading,
|
||||||
currentTitle: liveRoomInfo.title,
|
currentTitle: liveRoomInfo.title,
|
||||||
currentNotice: liveRoomInfo.notice,
|
currentNotice: liveRoomInfo.notice,
|
||||||
coverImageUrl: liveRoomInfo.coverImageUrl,
|
coverImageUrl: liveRoomInfo.coverImageUrl,
|
||||||
coverImage: viewModel.coverImage
|
coverImage: viewModel.coverImage
|
||||||
) { newTitle, newNotice in
|
) { newTitle, newNotice, isAdult in
|
||||||
self.viewModel.editLiveRoomInfo(
|
self.viewModel.editLiveRoomInfo(
|
||||||
title: newTitle,
|
title: newTitle,
|
||||||
notice: newNotice
|
notice: newNotice,
|
||||||
|
isAdult: isAdult
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -662,7 +683,7 @@ struct LiveRoomViewV2: View {
|
||||||
LiveRoomDonationRankingDialog(isShowing: $viewModel.isShowDonationRankingPopup)
|
LiveRoomDonationRankingDialog(isShowing: $viewModel.isShowDonationRankingPopup)
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $viewModel.isShowDonationMessagePopup) {
|
.sheet(isPresented: $viewModel.isShowDonationMessagePopup) {
|
||||||
LiveRoomDonationMessageDialog(isShowing: $viewModel.isShowDonationMessagePopup)
|
LiveRoomDonationMessageDialog(viewModel: viewModel, isShowing: $viewModel.isShowDonationMessagePopup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -674,7 +695,7 @@ struct LiveRoomViewV2: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func inviteSpeaker(peerId: Int) {
|
private func inviteSpeaker(peerId: Int) {
|
||||||
if viewModel.liveRoomInfo!.speakerList.count <= 4 {
|
if viewModel.liveRoomInfo!.speakerList.count <= 5 {
|
||||||
viewModel.inviteSpeaker(peerId: peerId)
|
viewModel.inviteSpeaker(peerId: peerId)
|
||||||
self.viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
|
self.viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
|
||||||
self.viewModel.isShowPopup = true
|
self.viewModel.isShowPopup = true
|
||||||
|
|