Compare commits
32 Commits
27b87c9cbe
...
3c6ac1c060
Author | SHA1 | Date |
---|---|---|
![]() |
3c6ac1c060 | |
![]() |
e11e40ff03 | |
![]() |
80e360aa17 | |
![]() |
578b9b9089 | |
![]() |
4d73136740 | |
![]() |
5c15e7d52f | |
![]() |
7b24b50582 | |
![]() |
1f35c4585d | |
![]() |
e95d152e45 | |
![]() |
68cc99c263 | |
![]() |
6070bdc6e0 | |
![]() |
225747dbe6 | |
![]() |
70460f5c8a | |
![]() |
b6411c3ccb | |
![]() |
aa4171b095 | |
![]() |
95ba74b862 | |
![]() |
86d4bc1c1e | |
![]() |
367be74279 | |
![]() |
bb64adc684 | |
![]() |
3a102674e9 | |
![]() |
91504bbda9 | |
![]() |
68ac0a4e76 | |
![]() |
bc11519120 | |
![]() |
0584b17b76 | |
![]() |
fec66fea56 | |
![]() |
2359257005 | |
![]() |
d16c56fef8 | |
![]() |
a07e87b9c8 | |
![]() |
a5eaf7c6ac | |
![]() |
57e2de3a48 | |
![]() |
dc73d6e89c | |
![]() |
97f703cb22 |
|
@ -36,13 +36,22 @@
|
||||||
"version" : "5.7.1"
|
"version" : "5.7.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "app-check",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/app-check.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "3e464dad87dad2d29bb29a97836789bf0f8f67d2",
|
||||||
|
"version" : "10.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "firebase-ios-sdk",
|
"identity" : "firebase-ios-sdk",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
|
"location" : "https://github.com/firebase/firebase-ios-sdk.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "df2171b0c6afb9e9d4f7e07669d558c510b9f6be",
|
"revision" : "be49849dcba96f2b5ee550d4eceb2c0fa27dade4",
|
||||||
"version" : "10.13.0"
|
"version" : "10.22.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -50,8 +59,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
"location" : "https://github.com/google/GoogleAppMeasurement.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "03b9beee1a61f62d32c521e172e192a1663a5e8b",
|
"revision" : "482cfa4e5880f0a29f66ecfd60c5a62af28bd1f0",
|
||||||
"version" : "10.13.0"
|
"version" : "10.22.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -59,8 +68,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/google/GoogleDataTransport.git",
|
"location" : "https://github.com/google/GoogleDataTransport.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "aae45a320fd0d11811820335b1eabc8753902a40",
|
"revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
|
||||||
"version" : "9.2.5"
|
"version" : "9.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -68,8 +77,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/google/GoogleUtilities.git",
|
"location" : "https://github.com/google/GoogleUtilities.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "c38ce365d77b04a9a300c31061c5227589e5597b",
|
"revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55",
|
||||||
"version" : "7.11.5"
|
"version" : "7.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -77,8 +86,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/google/grpc-binary.git",
|
"location" : "https://github.com/google/grpc-binary.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "f1b366129d1125be7db83247e003fc333104b569",
|
"revision" : "a673bc2937fbe886dd1f99c401b01b6d977a9c98",
|
||||||
"version" : "1.50.2"
|
"version" : "1.49.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -90,6 +99,15 @@
|
||||||
"version" : "3.1.1"
|
"version" : "3.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "interop-ios-for-google-sdks",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/google/interop-ios-for-google-sdks.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
|
||||||
|
"version" : "100.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "kingfisher",
|
"identity" : "kingfisher",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "splash_bubble.png",
|
"filename" : "btn_follow_big.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/btn_follow_big.imageset/btn_follow_big.png
vendored
Normal file
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "btn_notification.png",
|
"filename" : "btn_following_big.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/btn_following_big.imageset/btn_following_big.png
vendored
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 4.6 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "btn_notification_selected.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 4.8 KiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "splash_text.png",
|
"filename" : "ic_can_white.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
After Width: | Height: | Size: 563 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.2 KiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "img_guide_6.png",
|
"filename" : "img_bg_review_live.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/img_bg_review_live.imageset/img_bg_review_live.png
vendored
Normal file
After Width: | Height: | Size: 13 KiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "img_guide_5.png",
|
"filename" : "img_bg_short_play.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/img_bg_short_play.imageset/img_bg_short_play.png
vendored
Normal file
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 772 KiB After Width: | Height: | Size: 2.2 MiB |
Before Width: | Height: | Size: 554 KiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 320 KiB After Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 617 KiB After Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 678 KiB After Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 531 KiB |
Before Width: | Height: | Size: 252 KiB |
Before Width: | Height: | Size: 349 KiB |
|
@ -9,7 +9,7 @@
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "splash_bg_2024.jpg",
|
"filename" : "splash_bg_2024_03.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/splash/splash_bg_2024_03.imageset/splash_bg_2024_03.png
vendored
Normal file
After Width: | Height: | Size: 3.1 MiB |
Before Width: | Height: | Size: 11 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "splash_logo.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 55 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "splash_logo_2024.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 40 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "splash_logo_dragon_2024.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 15 KiB |
21
SodaLive/Resources/Assets.xcassets/splash/splash_text_2024_03.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "splash_text_2024_03.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/splash/splash_text_2024_03.imageset/splash_text_2024_03.png
vendored
Normal file
After Width: | Height: | Size: 29 KiB |
21
SodaLive/Resources/Assets.xcassets/splash/splash_text_logo_2024_03.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "splash_text_logo_2024_03.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 4.5 KiB |
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename" : "vividnext_logo.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>CA92.1</string>
|
||||||
|
</array>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>C617.1</string>
|
||||||
|
</array>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>35F9.1</string>
|
||||||
|
</array>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>E174.1</string>
|
||||||
|
</array>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -89,7 +89,7 @@ struct ContentAllByThemeView: View {
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
LazyVGrid(columns: columns, spacing: 13.3) {
|
LazyVGrid(columns: columns, spacing: 32) {
|
||||||
ForEach(0..<viewModel.contentList.count, id: \.self) { index in
|
ForEach(0..<viewModel.contentList.count, id: \.self) { index in
|
||||||
ContentNewAllItemView(item: viewModel.contentList[index])
|
ContentNewAllItemView(item: viewModel.contentList[index])
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
|
@ -35,11 +35,11 @@ struct ContentNewAllItemView: View {
|
||||||
Image("ic_card_can_gray")
|
Image("ic_card_can_gray")
|
||||||
|
|
||||||
Text("\(item.price)")
|
Text("\(item.price)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 8.3))
|
.font(.custom(Font.medium.rawValue, size: 8.5))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
} else {
|
} else {
|
||||||
Text("무료")
|
Text("무료")
|
||||||
.font(.custom(Font.medium.rawValue, size: 8.3))
|
.font(.custom(Font.medium.rawValue, size: 8.5))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,8 @@ struct ContentNewAllItemView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(spacing: 2) {
|
HStack(spacing: 2) {
|
||||||
Image("ic_card_time_small_gray")
|
|
||||||
|
|
||||||
Text(item.duration)
|
Text(item.duration)
|
||||||
.font(.custom(Font.medium.rawValue, size: 8.3))
|
.font(.custom(Font.medium.rawValue, size: 8.5))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
}
|
}
|
||||||
.padding(3)
|
.padding(3)
|
||||||
|
@ -93,7 +91,7 @@ struct ContentNewAllItemView: View {
|
||||||
}
|
}
|
||||||
.frame(width: width)
|
.frame(width: width)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
width = (screenSize().width - 54) / 3
|
width = (screenSize().width - 42) / 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ struct ContentNewAllView: View {
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
LazyVGrid(columns: columns, spacing: 13.3) {
|
LazyVGrid(columns: columns, spacing: 32) {
|
||||||
ForEach(0..<viewModel.newContentList.count, id: \.self) { index in
|
ForEach(0..<viewModel.newContentList.count, id: \.self) { index in
|
||||||
ContentNewAllItemView(item: viewModel.newContentList[index])
|
ContentNewAllItemView(item: viewModel.newContentList[index])
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
|
@ -91,7 +91,7 @@ struct ContentCurationView: View {
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
LazyVGrid(columns: columns, spacing: 13.3) {
|
LazyVGrid(columns: columns, spacing: 32) {
|
||||||
ForEach(0..<viewModel.contentList.count, id: \.self) { index in
|
ForEach(0..<viewModel.contentList.count, id: \.self) { index in
|
||||||
ContentNewAllItemView(item: viewModel.contentList[index])
|
ContentNewAllItemView(item: viewModel.contentList[index])
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
|
@ -33,8 +33,8 @@ struct AudioContentCommentItemView: View {
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(commentItem.nickname)
|
Text(commentItem.nickname)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.gray90)
|
||||||
|
|
||||||
Text(commentItem.date)
|
Text(commentItem.date)
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.3))
|
.font(.custom(Font.medium.rawValue, size: 10.3))
|
||||||
|
@ -107,14 +107,15 @@ struct AudioContentCommentItemView: View {
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "3bb9f1"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading, spacing: 13.3) {
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
Text(commentItem.comment)
|
Text(commentItem.comment)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.grayee)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.lineSpacing(8)
|
||||||
.padding(.top, commentItem.donationCan > 0 ? 0 : 13.3)
|
.padding(.top, commentItem.donationCan > 0 ? 0 : 13.3)
|
||||||
|
|
||||||
if !isReplyComment {
|
if !isReplyComment {
|
||||||
|
@ -127,7 +128,7 @@ struct AudioContentCommentItemView: View {
|
||||||
) {
|
) {
|
||||||
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
|
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ struct ContentDetailCommentView: View {
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
.foregroundColor(Color(hex: "bbbbbb"))
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
.lineSpacing(8)
|
||||||
.padding(.leading, 3)
|
.padding(.leading, 3)
|
||||||
} else {
|
} else {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ struct ContentDetailCreatorProfileView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if creator.creatorId != UserDefaults.int(forKey: .userId) {
|
if creator.creatorId != UserDefaults.int(forKey: .userId) {
|
||||||
Image(creator.isFollowing ? "btn_notification_selected" : "btn_notification")
|
Image(creator.isFollowing ? "btn_following_big" : "btn_follow_big")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if creator.isFollowing {
|
if creator.isFollowing {
|
||||||
onClickUnFollow(creator.creatorId)
|
onClickUnFollow(creator.creatorId)
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
//
|
||||||
|
// ContentDetailInfoLimitedEditionView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 3/27/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct ContentDetailInfoLimitedEditionView: View {
|
||||||
|
|
||||||
|
let totalContentCount: Int
|
||||||
|
let remainingContentCount: Int
|
||||||
|
let orderSequence: Int?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("한정판")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if let orderSequence = orderSequence {
|
||||||
|
Text("\(orderSequence)")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.padding(.leading, 5.3)
|
||||||
|
|
||||||
|
Text("/")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayd2)
|
||||||
|
.padding(.leading, 2.3)
|
||||||
|
|
||||||
|
Text("\(totalContentCount)")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayd2)
|
||||||
|
.padding(.leading, 2.3)
|
||||||
|
} else if (remainingContentCount <= 0) {
|
||||||
|
Text("Sold Out")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color.grayd2)
|
||||||
|
.padding(.horizontal, 5.3)
|
||||||
|
.padding(.vertical, 3.3)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 2.6)
|
||||||
|
.stroke(lineWidth: 1)
|
||||||
|
.foregroundColor(Color.grayd2)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text("잔여수량")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.grayd2)
|
||||||
|
|
||||||
|
Text("\(remainingContentCount)")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
.padding(.leading, 5.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 10.3)
|
||||||
|
.background(Color(hex: "14262d"))
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
ContentDetailInfoLimitedEditionView(
|
||||||
|
totalContentCount: 10,
|
||||||
|
remainingContentCount: 0,
|
||||||
|
orderSequence: nil
|
||||||
|
)
|
||||||
|
}
|
|
@ -142,6 +142,14 @@ struct ContentDetailInfoView: View {
|
||||||
}
|
}
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
|
if let totalContentCount = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount {
|
||||||
|
ContentDetailInfoLimitedEditionView(
|
||||||
|
totalContentCount: totalContentCount,
|
||||||
|
remainingContentCount: remainingContentCount,
|
||||||
|
orderSequence: audioContent.orderSequence
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
VStack(spacing: 8) {
|
VStack(spacing: 8) {
|
||||||
if audioContent.tag.count > 0 {
|
if audioContent.tag.count > 0 {
|
||||||
|
|
|
@ -35,7 +35,17 @@ struct ContentDetailPlayView: View {
|
||||||
)
|
)
|
||||||
.cornerRadius(10.7, corners: [.topLeft, .topRight])
|
.cornerRadius(10.7, corners: [.topLeft, .topRight])
|
||||||
|
|
||||||
if audioContent.releaseDate == nil && !isAlertPreview || (isAlertPreview && audioContent.isActivePreview) {
|
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0, audioContent.creator.creatorId != UserDefaults.int(forKey: .userId), !audioContent.existOrdered {
|
||||||
|
Text("Sold Out")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 36.7))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(
|
||||||
|
width: screenSize().width - 26.7,
|
||||||
|
height: screenSize().width - 26.7,
|
||||||
|
alignment: .center
|
||||||
|
)
|
||||||
|
.background(Color.black.opacity(0.6))
|
||||||
|
} else if audioContent.releaseDate == nil && !isAlertPreview || (audioContent.isActivePreview && !audioContent.contentUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) {
|
||||||
Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
|
Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if isPlaying() {
|
if isPlaying() {
|
||||||
|
@ -56,7 +66,7 @@ struct ContentDetailPlayView: View {
|
||||||
isShowPreviewAlert = true
|
isShowPreviewAlert = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if audioContent.releaseDate == nil {
|
||||||
Text("해당 콘텐츠는 크리에이터의 요청으로\n미리듣기를 제공하지 않습니다.")
|
Text("해당 콘텐츠는 크리에이터의 요청으로\n미리듣기를 제공하지 않습니다.")
|
||||||
.font(.custom(Font.medium.rawValue, size: 16.7))
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color.grayee)
|
.foregroundColor(Color.grayee)
|
||||||
|
|
|
@ -33,7 +33,7 @@ struct ContentDetailPurchaseButton: View {
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: 48.7)
|
.frame(height: 48.7)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(5.3)
|
.cornerRadius(5.3)
|
||||||
.padding(.top, 18.3)
|
.padding(.top, 18.3)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,10 +107,29 @@ struct ContentDetailView: View {
|
||||||
!audioContent.existOrdered &&
|
!audioContent.existOrdered &&
|
||||||
audioContent.orderType == nil &&
|
audioContent.orderType == nil &&
|
||||||
audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
|
audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
|
||||||
|
if let _ = audioContent.totalContentCount, let remainingContentCount = audioContent.remainingContentCount, remainingContentCount <= 0 {
|
||||||
|
Text("해당 콘텐츠가 매진되었습니다.")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: 48.7)
|
||||||
|
.background(Color(hex: "525252"))
|
||||||
|
.cornerRadius(5.3)
|
||||||
|
.padding(.top, 18.3)
|
||||||
|
.padding(.horizontal, 13.3)
|
||||||
|
} else {
|
||||||
ContentDetailPurchaseButton(price: audioContent.price, isOnlyRental: audioContent.isOnlyRental)
|
ContentDetailPurchaseButton(price: audioContent.price, isOnlyRental: audioContent.isOnlyRental)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.onTapGesture { isShowOrderView = true }
|
.onTapGesture {
|
||||||
|
if let _ = audioContent.totalContentCount, let _ = audioContent.remainingContentCount {
|
||||||
|
viewModel.orderType = .KEEP
|
||||||
|
isShowOrderConfirmView = true
|
||||||
|
} else {
|
||||||
|
isShowOrderView = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if audioContent.isCommentAvailable {
|
if audioContent.isCommentAvailable {
|
||||||
|
|
|
@ -18,6 +18,9 @@ struct GetAudioContentDetailResponse: Decodable {
|
||||||
let price: Int
|
let price: Int
|
||||||
let duration: String
|
let duration: String
|
||||||
let releaseDate: String?
|
let releaseDate: String?
|
||||||
|
let totalContentCount: Int?
|
||||||
|
let remainingContentCount: Int?
|
||||||
|
let orderSequence: Int?
|
||||||
let isActivePreview: Bool
|
let isActivePreview: Bool
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isMosaic: Bool
|
let isMosaic: Bool
|
||||||
|
|
|
@ -38,47 +38,45 @@ struct ContentMainView: View {
|
||||||
.padding(.bottom, 26.7)
|
.padding(.bottom, 26.7)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 8) {
|
||||||
VStack(spacing: 2.7) {
|
ZStack {
|
||||||
|
Image("img_bg_short_play")
|
||||||
|
.resizable()
|
||||||
|
.frame(height: 53.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.cornerRadius(2.6)
|
||||||
|
|
||||||
HStack(spacing: 2.7) {
|
HStack(spacing: 2.7) {
|
||||||
Image("ic_short_play")
|
Image("ic_short_play")
|
||||||
|
|
||||||
Text("숏플")
|
Text("숏플")
|
||||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "dd158d"))
|
.foregroundColor(Color(hex: "dd158d"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("짧지만 꼴릿한 이야기")
|
|
||||||
.font(.custom(Font.light.rawValue, size: 10))
|
|
||||||
.foregroundColor(Color(hex: "dd158d"))
|
|
||||||
}
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.background(Color(hex: "ffecf7"))
|
|
||||||
.cornerRadius(2.6)
|
.cornerRadius(2.6)
|
||||||
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
AppState.shared.setAppStep(
|
AppState.shared.setAppStep(
|
||||||
step: .contentAllByTheme(themeId: 11)
|
step: .contentAllByTheme(themeId: 11)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(spacing: 2.7) {
|
ZStack {
|
||||||
|
Image("img_bg_review_live")
|
||||||
|
.resizable()
|
||||||
|
.frame(height: 53.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.cornerRadius(2.6)
|
||||||
|
|
||||||
HStack(spacing: 2.7) {
|
HStack(spacing: 2.7) {
|
||||||
Image("ic_thumb_play_blue")
|
Image("ic_thumb_play_blue")
|
||||||
|
|
||||||
Text("라이브 다시듣기")
|
Text("라이브 다시듣기")
|
||||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "0057ff"))
|
.foregroundColor(Color(hex: "0057ff"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Text("놓쳤던 라이브 다시듣기")
|
|
||||||
.font(.custom(Font.light.rawValue, size: 10))
|
|
||||||
.foregroundColor(Color(hex: "0057ff"))
|
|
||||||
}
|
|
||||||
.padding(.vertical, 10)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.background(Color(hex: "ecf9ff"))
|
|
||||||
.cornerRadius(2.6)
|
.cornerRadius(2.6)
|
||||||
|
}
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
AppState.shared.setAppStep(
|
AppState.shared.setAppStep(
|
||||||
step: .contentAllByTheme(themeId: 7)
|
step: .contentAllByTheme(themeId: 7)
|
||||||
|
|
|
@ -33,8 +33,8 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text(commentItem.nickname)
|
Text(commentItem.nickname)
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.gray90)
|
||||||
|
|
||||||
Text(commentItem.date)
|
Text(commentItem.date)
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.3))
|
.font(.custom(Font.medium.rawValue, size: 10.3))
|
||||||
|
@ -86,9 +86,10 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading, spacing: 13.3) {
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
Text(commentItem.comment)
|
Text(commentItem.comment)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.grayee)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.lineSpacing(8)
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
|
|
||||||
if !isReplyComment {
|
if !isReplyComment {
|
||||||
|
@ -101,7 +102,7 @@ struct CreatorCommunityCommentItemView: View {
|
||||||
) {
|
) {
|
||||||
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
|
Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,12 +37,12 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text("\(cheersItem.nickname)")
|
Text("\(cheersItem.nickname)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.gray90)
|
||||||
|
|
||||||
Text("\(cheersItem.date)")
|
Text("\(cheersItem.date)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 10.7))
|
.font(.custom(Font.medium.rawValue, size: 10.7))
|
||||||
.foregroundColor(Color(hex: "525252"))
|
.foregroundColor(Color.gray55)
|
||||||
.padding(.top, 8.3)
|
.padding(.top, 8.3)
|
||||||
|
|
||||||
if isModeModify {
|
if isModeModify {
|
||||||
|
@ -60,14 +60,14 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: 10)
|
||||||
.strokeBorder(lineWidth: 1)
|
.strokeBorder(lineWidth: 1)
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.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(hex: "ffffff"))
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
modifyCheer(cheersItem.cheersId, cheers)
|
modifyCheer(cheersItem.cheersId, cheers)
|
||||||
|
@ -76,7 +76,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: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color(hex: "222222"))
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
|
@ -87,9 +87,10 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
} else {
|
} else {
|
||||||
Text("\(cheersItem.content)")
|
Text("\(cheersItem.content)")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.grayee)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
.lineSpacing(8)
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +132,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
if userId == UserDefaults.int(forKey: .userId) {
|
if userId == UserDefaults.int(forKey: .userId) {
|
||||||
Text("답글쓰기")
|
Text("답글쓰기")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.padding(.top, 18.3)
|
.padding(.top, 18.3)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isShowInputReply = true
|
isShowInputReply = true
|
||||||
|
@ -146,7 +147,7 @@ struct UserProfileFanTalkCheersItemView: View {
|
||||||
.frame(minWidth: 100)
|
.frame(minWidth: 100)
|
||||||
.padding(.horizontal, 6.7)
|
.padding(.horizontal, 6.7)
|
||||||
.padding(.vertical, 6.7)
|
.padding(.vertical, 6.7)
|
||||||
.background(Color(hex: "9970ff").opacity(0.3))
|
.background(Color.button.opacity(0.3))
|
||||||
.cornerRadius(16.7)
|
.cornerRadius(16.7)
|
||||||
.padding(.top, 18.3)
|
.padding(.top, 18.3)
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ struct FollowerListItemView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if let isFollow = item.isFollow {
|
if let isFollow = item.isFollow {
|
||||||
Image(isFollow ? "btn_notification_selected" : "btn_notification")
|
Image(isFollow ? "btn_following_big" : "btn_follow_big")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isFollow ?
|
isFollow ?
|
||||||
creatorUnFollow(item.userId) :
|
creatorUnFollow(item.userId) :
|
||||||
|
|
|
@ -57,7 +57,7 @@ struct UserProfileCreatorView: View {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
VStack(alignment: .leading, spacing: 9.3) {
|
VStack(alignment: .leading, spacing: 9.3) {
|
||||||
Image(creator.isNotification ? "btn_notification_selected" : "btn_notification")
|
Image(creator.isNotification ? "btn_following_big" : "btn_follow_big")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 83.3, height: 26.7)
|
.frame(width: 83.3, height: 26.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct FollowCreatorItemView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image(isFollow ? "btn_notification_selected" : "btn_notification")
|
Image(isFollow ? "btn_following_big" : "btn_follow_big")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if isFollow {
|
if isFollow {
|
||||||
onClickUnFollow(creator.creatorId)
|
onClickUnFollow(creator.creatorId)
|
||||||
|
|
|
@ -36,6 +36,7 @@ enum LiveApi {
|
||||||
case getDonationMessageList(roomId: Int)
|
case getDonationMessageList(roomId: Int)
|
||||||
case deleteDonationMessage(roomId: Int, messageUUID: String)
|
case deleteDonationMessage(roomId: Int, messageUUID: String)
|
||||||
case getUserProfile(roomId: Int, userId: Int)
|
case getUserProfile(roomId: Int, userId: Int)
|
||||||
|
case getAllMenuPreset(creatorId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LiveApi: TargetType {
|
extension LiveApi: TargetType {
|
||||||
|
@ -125,12 +126,15 @@ extension LiveApi: TargetType {
|
||||||
|
|
||||||
case .getUserProfile(let roomId, let userId):
|
case .getUserProfile(let roomId, let userId):
|
||||||
return "/live/room/\(roomId)/profile/\(userId)"
|
return "/live/room/\(roomId)/profile/\(userId)"
|
||||||
|
|
||||||
|
case .getAllMenuPreset:
|
||||||
|
return "/live/room/menu/all"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var method: Moya.Method {
|
var method: Moya.Method {
|
||||||
switch self {
|
switch self {
|
||||||
case .roomList, .recentVisitRoomUsers, .getReservations, .getReservation, .getRoomDetail, .getTags, .getRecentRoomInfo, .getRoomInfo, .donationStatus, .donationTotal, .getDonationMessageList, .getUserProfile:
|
case .roomList, .recentVisitRoomUsers, .getReservations, .getReservation, .getRoomDetail, .getTags, .getRecentRoomInfo, .getRoomInfo, .donationStatus, .donationTotal, .getDonationMessageList, .getUserProfile, .getAllMenuPreset:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .makeReservation, .enterRoom, .createRoom, .quitRoom, .donation, .refundDonation, .kickOut:
|
case .makeReservation, .enterRoom, .createRoom, .quitRoom, .donation, .refundDonation, .kickOut:
|
||||||
|
@ -226,6 +230,9 @@ extension LiveApi: TargetType {
|
||||||
|
|
||||||
case .deleteDonationMessage(let roomId, let messageUUID):
|
case .deleteDonationMessage(let roomId, let messageUUID):
|
||||||
return .requestJSONEncodable(DeleteLiveRoomDonationMessage(roomId: roomId, messageUUID: messageUUID))
|
return .requestJSONEncodable(DeleteLiveRoomDonationMessage(roomId: roomId, messageUUID: messageUUID))
|
||||||
|
|
||||||
|
case .getAllMenuPreset(let creatorId):
|
||||||
|
return .requestParameters(parameters: ["creatorId" : creatorId], encoding: URLEncoding.queryString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,4 +116,8 @@ final class LiveRepository {
|
||||||
func getUserProfile(roomId: Int, userId: Int) -> AnyPublisher<Response, MoyaError> {
|
func getUserProfile(roomId: Int, userId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
api.requestPublisher(.getUserProfile(roomId: roomId, userId: userId))
|
api.requestPublisher(.getUserProfile(roomId: roomId, userId: userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAllMenuPreset(creatorId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
api.requestPublisher(.getAllMenuPreset(creatorId: creatorId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,33 +11,39 @@ import Kingfisher
|
||||||
struct LiveNowAllItemView: View {
|
struct LiveNowAllItemView: View {
|
||||||
|
|
||||||
let item: GetRoomListResponse
|
let item: GetRoomListResponse
|
||||||
|
let itemWidth: CGFloat
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 13.3) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
HStack(spacing: 20) {
|
ZStack {
|
||||||
ZStack(alignment: .topLeading) {
|
|
||||||
KFImage(URL(string: item.coverImageUrl))
|
KFImage(URL(string: item.coverImageUrl))
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: 80, height: 116.7, alignment: .top)
|
.frame(width: itemWidth, height: itemWidth * 144 / 102, alignment: .center)
|
||||||
.cornerRadius(4.7)
|
.cornerRadius(4.7)
|
||||||
.clipped()
|
.clipped()
|
||||||
|
|
||||||
|
LinearGradient(
|
||||||
|
colors: [Color.black.opacity(0.1), Color.black.opacity(0.8)],
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
VStack(alignment: .trailing, spacing: 0) {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
HStack(spacing: 1) {
|
||||||
|
if item.price > 0 {
|
||||||
|
Image("ic_can_white")
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
Text(item.price > 0 ? "\(item.price)" : "무료")
|
||||||
HStack(alignment: .top, spacing: 0) {
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
.foregroundColor(Color.white)
|
||||||
Text(item.creatorNickname)
|
|
||||||
.font(.custom(Font.medium.rawValue, size: 11.3))
|
|
||||||
.foregroundColor(Color(hex: "bbbbbb"))
|
|
||||||
|
|
||||||
Text(item.title)
|
|
||||||
.font(.custom(Font.medium.rawValue, size: 15.3))
|
|
||||||
.foregroundColor(Color(hex: "e2e2e2"))
|
|
||||||
.lineLimit(2)
|
|
||||||
.padding(.top, 4.3)
|
|
||||||
.padding(.trailing, 20)
|
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 7.3)
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.background(item.price > 0 ? Color.mainRed3 : Color.gray11)
|
||||||
|
.cornerRadius(13.3)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -45,48 +51,92 @@ struct LiveNowAllItemView: View {
|
||||||
Image("ic_lock")
|
Image("ic_lock")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(2.7)
|
||||||
|
.background(Color.gray33.opacity(0.7))
|
||||||
|
.clipShape(Circle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, 13.3)
|
.padding(.horizontal, 3.3)
|
||||||
|
.padding(.top, 3.3)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if item.numberOfPeople - item.numberOfParticipate < 3 {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text(item.numberOfPeople > item.numberOfParticipate ? "참여가능" : "Sold out")
|
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
|
||||||
.foregroundColor(
|
|
||||||
Color(
|
|
||||||
hex: item.numberOfPeople > item.numberOfParticipate ?
|
|
||||||
"3bb9f1" :
|
|
||||||
"ffd300"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if item.price > 0 {
|
HStack(spacing: 2) {
|
||||||
Text("\(item.price)")
|
Text("잔여")
|
||||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Image("ic_can")
|
Text("\(item.numberOfPeople - item.numberOfParticipate)")
|
||||||
.resizable()
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.frame(width: 20, height: 20)
|
.foregroundColor(Color.button)
|
||||||
.padding(.leading, 6.7)
|
|
||||||
} else {
|
|
||||||
Text("무료")
|
|
||||||
.font(.custom(Font.bold.rawValue, size: 15.3))
|
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
.padding(.vertical, 3)
|
||||||
|
.background(Color.gray33.opacity(0.7))
|
||||||
|
.cornerRadius(13.3)
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 3.3)
|
||||||
.padding(.bottom, 3.3)
|
.padding(.bottom, 3.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle()
|
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.5))
|
|
||||||
.frame(width: screenSize().width - 26.7, height: 1)
|
|
||||||
}
|
}
|
||||||
.frame(width: screenSize().width - 26.7, height: 130, alignment: .center)
|
.frame(width: itemWidth, height: itemWidth * 144 / 102)
|
||||||
|
|
||||||
|
Text("\(item.title)")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
.lineLimit(2)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
|
||||||
|
if item.tags.count > 0 {
|
||||||
|
Text("\(item.tags.map { "#\($0)" }.joined(separator: " "))")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 11))
|
||||||
|
.foregroundColor(Color.button)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 5.3) {
|
||||||
|
KFImage(URL(string: item.creatorProfileImage))
|
||||||
|
.resizable()
|
||||||
|
.scaledToFill()
|
||||||
|
.frame(width: 21.3, height: 21.3, alignment: .center)
|
||||||
|
.clipShape(Circle())
|
||||||
|
|
||||||
|
Text("\(item.creatorNickname)")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 10))
|
||||||
|
.foregroundColor(Color.gray77)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(width: itemWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LiveNowAllItemView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
LiveNowAllItemView(
|
||||||
|
item: GetRoomListResponse(
|
||||||
|
roomId: 99,
|
||||||
|
title: "testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest",
|
||||||
|
content: "testtest",
|
||||||
|
beginDateTime: "2022.05.23 Mon 03:00 PM",
|
||||||
|
numberOfParticipate: 3,
|
||||||
|
numberOfPeople: 5,
|
||||||
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
isAdult: true,
|
||||||
|
price: 20,
|
||||||
|
tags: ["팬미팅", "힐링"],
|
||||||
|
channelName: nil,
|
||||||
|
creatorProfileImage: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
creatorNickname: "user8",
|
||||||
|
creatorId: 19,
|
||||||
|
isReservation: false,
|
||||||
|
isPrivateRoom: true
|
||||||
|
),
|
||||||
|
itemWidth: 102
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,14 @@ struct LiveNowAllView: View {
|
||||||
@StateObject var viewModel = LiveViewModel()
|
@StateObject var viewModel = LiveViewModel()
|
||||||
@StateObject var liveAllViewModel = LiveAllViewModel()
|
@StateObject var liveAllViewModel = LiveAllViewModel()
|
||||||
|
|
||||||
|
let columns = [
|
||||||
|
GridItem(.flexible(), alignment: .top),
|
||||||
|
GridItem(.flexible(), alignment: .top),
|
||||||
|
GridItem(.flexible(), alignment: .top)
|
||||||
|
]
|
||||||
|
|
||||||
|
let spacing: CGFloat = 13.3
|
||||||
|
|
||||||
let onClickParticipant: (Int) -> Void
|
let onClickParticipant: (Int) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -29,11 +37,11 @@ struct LiveNowAllView: View {
|
||||||
viewModel.getLiveNowList()
|
viewModel.getLiveNowList()
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
VStack(spacing: 13.3) {
|
LazyVGrid(columns: columns, spacing: spacing) {
|
||||||
ForEach(0..<viewModel.liveNowItems.count, id: \.self) { index in
|
ForEach(0..<viewModel.liveNowItems.count, id: \.self) { index in
|
||||||
let item = viewModel.liveNowItems[index]
|
let item = viewModel.liveNowItems[index]
|
||||||
|
|
||||||
LiveNowAllItemView(item: item)
|
LiveNowAllItemView(item: item, itemWidth: (screenSize().width - (spacing * (CGFloat(columns.count + 1)))) / 3)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
self.liveAllViewModel.selectedRoomId = item.roomId
|
self.liveAllViewModel.selectedRoomId = item.roomId
|
||||||
|
|
|
@ -12,8 +12,8 @@ struct LiveNowItemView: View {
|
||||||
|
|
||||||
let item: GetRoomListResponse
|
let item: GetRoomListResponse
|
||||||
|
|
||||||
let width: CGFloat = 133.3
|
let width: CGFloat = 128
|
||||||
let height: CGFloat = 176.7
|
let height: CGFloat = 179
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
@ -32,14 +32,20 @@ struct LiveNowItemView: View {
|
||||||
)
|
)
|
||||||
|
|
||||||
VStack(alignment: .trailing, spacing: 0) {
|
VStack(alignment: .trailing, spacing: 0) {
|
||||||
HStack(spacing: 3.3) {
|
HStack(spacing: 0) {
|
||||||
Text(item.price > 0 ? "유료" : "무료")
|
HStack(spacing: 1) {
|
||||||
|
if item.price > 0 {
|
||||||
|
Image("ic_can_white")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(item.price > 0 ? "\(item.price)" : "무료")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
|
}
|
||||||
.padding(.horizontal, 7.3)
|
.padding(.horizontal, 7.3)
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
.background(Color(hex: item.price > 0 ? "881609" : "643bc8"))
|
.background(item.price > 0 ? Color.mainRed3 : Color.gray11)
|
||||||
.cornerRadius(10)
|
.cornerRadius(13.3)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -47,6 +53,9 @@ struct LiveNowItemView: View {
|
||||||
Image("ic_lock")
|
Image("ic_lock")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(2.7)
|
||||||
|
.background(Color.gray33.opacity(0.7))
|
||||||
|
.clipShape(Circle())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 3.3)
|
.padding(.horizontal, 3.3)
|
||||||
|
@ -97,7 +106,7 @@ struct LiveNowItemView_Previews: PreviewProvider {
|
||||||
numberOfPeople: 5,
|
numberOfPeople: 5,
|
||||||
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
isAdult: true,
|
isAdult: true,
|
||||||
price: 0,
|
price: 20,
|
||||||
tags: ["팬미팅", "힐링"],
|
tags: ["팬미팅", "힐링"],
|
||||||
channelName: nil,
|
channelName: nil,
|
||||||
creatorProfileImage: "https://test-cf.sodalive.net/profile/default-profile.png",
|
creatorProfileImage: "https://test-cf.sodalive.net/profile/default-profile.png",
|
||||||
|
|
|
@ -15,6 +15,7 @@ struct LiveRoomChatRawMessage: Codable {
|
||||||
let type: LiveRoomChatRawMessageType
|
let type: LiveRoomChatRawMessageType
|
||||||
let message: String
|
let message: String
|
||||||
let can: Int
|
let can: Int
|
||||||
|
var signatureImageUrl: String? = nil
|
||||||
let donationMessage: String?
|
let donationMessage: String?
|
||||||
var isActiveRoulette: Bool? = nil
|
var isActiveRoulette: Bool? = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,7 @@ struct CreateLiveRoomRequest: Encodable {
|
||||||
var password: String? = nil
|
var password: String? = nil
|
||||||
let timezone: String = TimeZone.current.identifier
|
let timezone: String = TimeZone.current.identifier
|
||||||
var beginDateTimeString: String? = nil
|
var beginDateTimeString: String? = nil
|
||||||
|
var menuPanId: Int = 0
|
||||||
|
var menuPan: String = ""
|
||||||
|
var isActiveMenuPan: Bool = false
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ struct LiveRoomCreateView: View {
|
||||||
|
|
||||||
Text("라이브 만들기")
|
Text("라이브 만들기")
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
@ -49,13 +49,13 @@ struct LiveRoomCreateView: View {
|
||||||
if viewModel.isShowGetRecentInfoButton {
|
if viewModel.isShowGetRecentInfoButton {
|
||||||
Text("최근 데이터 가져오기")
|
Text("최근 데이터 가져오기")
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
.padding(.horizontal, 10.7)
|
.padding(.horizontal, 10.7)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.stroke()
|
.stroke()
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
viewModel.getRecentInfo()
|
viewModel.getRecentInfo()
|
||||||
|
@ -71,7 +71,7 @@ struct LiveRoomCreateView: 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)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
.frame(width: screenSize().width, alignment: .leading)
|
.frame(width: screenSize().width, alignment: .leading)
|
||||||
|
@ -95,13 +95,13 @@ struct LiveRoomCreateView: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 80, height: 116.8)
|
.frame(width: 80, height: 116.8)
|
||||||
.background(Color(hex: "3e3358"))
|
.background(Color.bg)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
Image("ic_camera")
|
Image("ic_camera")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(30)
|
.cornerRadius(30)
|
||||||
.offset(x: 40, y: 40)
|
.offset(x: 40, y: 40)
|
||||||
}
|
}
|
||||||
|
@ -115,25 +115,32 @@ struct LiveRoomCreateView: View {
|
||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.padding(.top, 33.3)
|
.padding(.top, 33.3)
|
||||||
|
|
||||||
TagSelectView()
|
|
||||||
.frame(width: screenSize().width - 26.7)
|
|
||||||
.padding(.top, 33.3)
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
ContentInputView()
|
ContentInputView()
|
||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.padding(.top, 33.3)
|
.padding(.top, 33.3)
|
||||||
|
|
||||||
if viewModel.roomType != .SECRET {
|
|
||||||
TimeSettingView()
|
|
||||||
.padding(.top, 33.3)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 33.3) {
|
||||||
|
LiveRoomMenuSelectView(
|
||||||
|
menu: $viewModel.menu,
|
||||||
|
isActivate: $viewModel.isActivateMenu,
|
||||||
|
menuCount: viewModel.menuList.count,
|
||||||
|
selectedMenu: viewModel.selectedMenu ?? .MENU_1,
|
||||||
|
selectMenu: {
|
||||||
|
viewModel.selectMenuPreset(selectedMenuPreset: $0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.frame(width: screenSize().width - 26.7)
|
||||||
|
|
||||||
|
TagSelectView()
|
||||||
|
.frame(width: screenSize().width - 26.7)
|
||||||
|
|
||||||
|
TimeSettingView()
|
||||||
|
|
||||||
NumberOfPeopleLimitView()
|
NumberOfPeopleLimitView()
|
||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.padding(.top, 33.3)
|
|
||||||
}
|
}
|
||||||
|
.padding(.top, 33.3)
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
RoomTypeSettingView()
|
RoomTypeSettingView()
|
||||||
|
@ -146,12 +153,10 @@ struct LiveRoomCreateView: View {
|
||||||
.padding(.top, 33.3)
|
.padding(.top, 33.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
if UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
|
|
||||||
PriceSettingView()
|
PriceSettingView()
|
||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.padding(.top, 33.3)
|
.padding(.top, 33.3)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
HStack(alignment: .top, spacing: 0) {
|
HStack(alignment: .top, spacing: 0) {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
|
@ -167,23 +172,23 @@ struct LiveRoomCreateView: View {
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color.white)
|
.foregroundColor(Color.white)
|
||||||
.frame(width: screenSize().width - 26.7, height: 50)
|
.frame(width: screenSize().width - 26.7, height: 50)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: screenSize().width)
|
.frame(width: screenSize().width)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
||||||
.padding(.top, 30)
|
.padding(.top, 30)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "222222"))
|
.foregroundColor(Color.gray22)
|
||||||
.frame(width: screenSize().width, height: keyboardHandler.keyboardHeight)
|
.frame(width: screenSize().width, height: keyboardHandler.keyboardHeight)
|
||||||
|
|
||||||
if proxy.safeAreaInsets.bottom > 0 {
|
if proxy.safeAreaInsets.bottom > 0 {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.foregroundColor(Color(hex: "222222"))
|
.foregroundColor(Color.gray22)
|
||||||
.frame(width: screenSize().width, height: 15.3)
|
.frame(width: screenSize().width, height: 15.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +239,7 @@ struct LiveRoomCreateView: View {
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.frame(width: geo.size.width - 66.7, alignment: .center)
|
.frame(width: geo.size.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)
|
||||||
|
@ -245,6 +250,7 @@ struct LiveRoomCreateView: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.timeSettingMode = timeSettingMode
|
viewModel.timeSettingMode = timeSettingMode
|
||||||
|
viewModel.getAllMenuPreset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +259,7 @@ struct LiveRoomCreateView: 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)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.frame(width: screenSize().width, alignment: .leading)
|
.frame(width: screenSize().width, alignment: .leading)
|
||||||
|
|
||||||
|
@ -261,15 +267,15 @@ struct LiveRoomCreateView: 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(.top, 12)
|
.padding(.top, 12)
|
||||||
.padding(.horizontal, 6.7)
|
.padding(.horizontal, 6.7)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.7))
|
.foregroundColor(Color.gray90.opacity(0.7))
|
||||||
.padding(.top, 8.3)
|
.padding(.top, 8.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,7 +285,7 @@ struct LiveRoomCreateView: View {
|
||||||
VStack(alignment: .leading, spacing: 13.3) {
|
VStack(alignment: .leading, 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)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -287,15 +293,15 @@ struct LiveRoomCreateView: View {
|
||||||
}) {
|
}) {
|
||||||
Text("관심사 선택")
|
Text("관심사 선택")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.padding(.vertical, 13.7)
|
.padding(.vertical, 13.7)
|
||||||
.frame(width: screenSize().width - 26.7)
|
.frame(width: screenSize().width - 26.7)
|
||||||
.background(Color(hex: "9970ff").opacity(0.2))
|
.background(Color.button.opacity(0.2))
|
||||||
.cornerRadius(24.3)
|
.cornerRadius(24.3)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 24.3)
|
RoundedRectangle(cornerRadius: 24.3)
|
||||||
.stroke()
|
.stroke()
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +321,7 @@ struct LiveRoomCreateView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(24.3)
|
.cornerRadius(24.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,25 +336,25 @@ struct LiveRoomCreateView: View {
|
||||||
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(" / 1000자")
|
Text(" / 1000자")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
|
|
||||||
TextViewWrapper(
|
TextViewWrapper(
|
||||||
text: $viewModel.content,
|
text: $viewModel.content,
|
||||||
placeholder: viewModel.placeholder,
|
placeholder: viewModel.placeholder,
|
||||||
textColorHex: "eeeeee",
|
textColorHex: "eeeeee",
|
||||||
backgroundColorHex: "222222"
|
backgroundColorHex: "303030"
|
||||||
)
|
)
|
||||||
.frame(width: screenSize().width - 26.7, height: 133.3)
|
.frame(width: screenSize().width - 26.7, height: 200)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.padding(.top, 13.3)
|
.padding(.top, 13.3)
|
||||||
}
|
}
|
||||||
|
@ -359,7 +365,7 @@ struct LiveRoomCreateView: 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(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -400,15 +406,11 @@ struct LiveRoomCreateView: View {
|
||||||
.foregroundColor(
|
.foregroundColor(
|
||||||
viewModel.timeSettingMode == timeSettingMode ?
|
viewModel.timeSettingMode == timeSettingMode ?
|
||||||
.white :
|
.white :
|
||||||
Color(hex: "9970ff")
|
Color.button
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.frame(width: buttonWidth, height: 48.7)
|
.frame(width: buttonWidth, height: 48.7)
|
||||||
.background(
|
.background(viewModel.timeSettingMode == timeSettingMode ? Color.button : Color.bg)
|
||||||
viewModel.timeSettingMode == timeSettingMode ?
|
|
||||||
Color(hex: "9970ff") :
|
|
||||||
Color(hex: "1f1734")
|
|
||||||
)
|
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -424,7 +426,7 @@ struct LiveRoomCreateView: View {
|
||||||
VStack(alignment: .leading, spacing: 6.7) {
|
VStack(alignment: .leading, spacing: 6.7) {
|
||||||
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)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -432,11 +434,11 @@ struct LiveRoomCreateView: View {
|
||||||
}) {
|
}) {
|
||||||
Text(viewModel.reservationDateString)
|
Text(viewModel.reservationDateString)
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.frame(width: buttonWidth, height: 48.7)
|
.frame(width: buttonWidth, height: 48.7)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 6.7)
|
RoundedRectangle(cornerRadius: 6.7)
|
||||||
.stroke(Color(hex: "9970ff"), lineWidth: 1.3)
|
.stroke(Color.button, lineWidth: 1.3)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -444,7 +446,7 @@ struct LiveRoomCreateView: View {
|
||||||
VStack(alignment: .leading, spacing: 6.7) {
|
VStack(alignment: .leading, spacing: 6.7) {
|
||||||
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)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -452,18 +454,18 @@ struct LiveRoomCreateView: View {
|
||||||
}) {
|
}) {
|
||||||
Text(viewModel.reservationTimeString)
|
Text(viewModel.reservationTimeString)
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.frame(width: buttonWidth, height: 48.7)
|
.frame(width: buttonWidth, height: 48.7)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 6.7)
|
RoundedRectangle(cornerRadius: 6.7)
|
||||||
.stroke(Color(hex: "9970ff"), lineWidth: 1.3)
|
.stroke(Color.button, lineWidth: 1.3)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: screenSize().width)
|
.frame(width: screenSize().width)
|
||||||
.padding(.vertical, 13.3)
|
.padding(.vertical, 13.3)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
@ -471,7 +473,7 @@ struct LiveRoomCreateView: 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(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
|
|
||||||
TextField("최대 인원 999명", text: $viewModel.numberOfPeople)
|
TextField("최대 인원 999명", text: $viewModel.numberOfPeople)
|
||||||
|
@ -479,12 +481,12 @@ struct LiveRoomCreateView: View {
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
.padding(.vertical, 15.7)
|
.padding(.vertical, 15.7)
|
||||||
.frame(width: screenSize().width - 26.7, alignment: .center)
|
.frame(width: screenSize().width - 26.7, alignment: .center)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -508,12 +510,12 @@ struct LiveRoomCreateView: View {
|
||||||
Button(action: { self.isShowSelectDateView = false }) {
|
Button(action: { self.isShowSelectDateView = false }) {
|
||||||
Text("확인")
|
Text("확인")
|
||||||
.font(.system(size: 16))
|
.font(.system(size: 16))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
.frame(width: proxy.size.width - 53.4)
|
.frame(width: proxy.size.width - 53.4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
}
|
}
|
||||||
.frame(width: proxy.size.width)
|
.frame(width: proxy.size.width)
|
||||||
|
@ -539,12 +541,12 @@ struct LiveRoomCreateView: View {
|
||||||
Button(action: { self.isShowSelectTimeView = false }) {
|
Button(action: { self.isShowSelectTimeView = false }) {
|
||||||
Text("확인")
|
Text("확인")
|
||||||
.font(.system(size: 16))
|
.font(.system(size: 16))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.padding(.vertical, 10)
|
.padding(.vertical, 10)
|
||||||
.frame(width: proxy.size.width)
|
.frame(width: proxy.size.width)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
}
|
}
|
||||||
.frame(width: proxy.size.width)
|
.frame(width: proxy.size.width)
|
||||||
|
@ -556,7 +558,7 @@ struct LiveRoomCreateView: 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(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -591,18 +593,10 @@ struct LiveRoomCreateView: View {
|
||||||
|
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||||
.foregroundColor(
|
.foregroundColor(viewModel.roomType == type ? .white : Color.button)
|
||||||
viewModel.roomType == type ?
|
|
||||||
.white :
|
|
||||||
Color(hex: "9970ff")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.frame(width: buttonWidth, height: 48.7)
|
.frame(width: buttonWidth, height: 48.7)
|
||||||
.background(
|
.background(viewModel.roomType == type ? Color.button : Color.bg)
|
||||||
viewModel.roomType == type ?
|
|
||||||
Color(hex: "9970ff") :
|
|
||||||
Color(hex: "1f1734")
|
|
||||||
)
|
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -617,7 +611,7 @@ struct LiveRoomCreateView: 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(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
|
|
||||||
TextField("방 입장 비밀번호 6자리를 입력해 주세요.", text: $viewModel.password)
|
TextField("방 입장 비밀번호 6자리를 입력해 주세요.", text: $viewModel.password)
|
||||||
|
@ -625,12 +619,12 @@ struct LiveRoomCreateView: View {
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
.padding(.vertical, 15.7)
|
.padding(.vertical, 15.7)
|
||||||
.frame(width: screenSize().width - 26.7, alignment: .center)
|
.frame(width: screenSize().width - 26.7, alignment: .center)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color.gray22)
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -640,7 +634,7 @@ struct LiveRoomCreateView: 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(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -668,18 +662,10 @@ struct LiveRoomCreateView: View {
|
||||||
|
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(.custom(Font.bold.rawValue, size: 14.7))
|
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||||
.foregroundColor(
|
.foregroundColor(viewModel.isAdult == isAdult ? .white : Color.button)
|
||||||
viewModel.isAdult == isAdult ?
|
|
||||||
.white :
|
|
||||||
Color(hex: "9970ff")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.frame(width: buttonWidth, height: 48.7)
|
.frame(width: buttonWidth, height: 48.7)
|
||||||
.background(
|
.background(viewModel.isAdult == isAdult ? Color.button : Color.bg)
|
||||||
viewModel.isAdult == isAdult ?
|
|
||||||
Color(hex: "9970ff") :
|
|
||||||
Color(hex: "1f1734")
|
|
||||||
)
|
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
@ -694,7 +680,7 @@ struct LiveRoomCreateView: 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(width: screenSize().width - 26.7, alignment: .leading)
|
.frame(width: screenSize().width - 26.7, alignment: .leading)
|
||||||
|
|
||||||
HStack(spacing: 13.3) {
|
HStack(spacing: 13.3) {
|
||||||
|
@ -719,35 +705,23 @@ struct LiveRoomCreateView: View {
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.font(.custom(Font.bold.rawValue, size: 13.3))
|
.font(.custom(Font.bold.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.accentColor(Color(hex: "3bb9f1"))
|
.accentColor(Color.button)
|
||||||
.keyboardType(.numberPad)
|
.keyboardType(.numberPad)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text("캔")
|
Text("캔")
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(
|
.foregroundColor(Color.button)
|
||||||
Color(hex: "9970ff")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.frame(width: screenSize().width - 26.7, height: 48.7)
|
.frame(width: screenSize().width - 26.7, height: 48.7)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 6.7)
|
RoundedRectangle(cornerRadius: 6.7)
|
||||||
.stroke(
|
.stroke(!viewModel.prices.contains(viewModel.price) ? Color.button : Color.gray77, lineWidth: 1)
|
||||||
Color(hex: !viewModel.prices.contains(viewModel.price) ?
|
|
||||||
"9970ff" :
|
|
||||||
"777777"
|
|
||||||
),
|
|
||||||
lineWidth: 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
!viewModel.prices.contains(viewModel.price) ?
|
|
||||||
Color(hex:"9970ff").opacity(0.3):
|
|
||||||
Color(hex: "232323")
|
|
||||||
)
|
)
|
||||||
|
.background(!viewModel.prices.contains(viewModel.price) ? Color.button.opacity(0.3): Color.gray23)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,23 +735,17 @@ struct LiveRoomCreateView: View {
|
||||||
Font.medium.rawValue,
|
Font.medium.rawValue,
|
||||||
size: 14.7
|
size: 14.7
|
||||||
))
|
))
|
||||||
.foregroundColor(
|
.foregroundColor(viewModel.price == price ? Color.button : Color.gray77)
|
||||||
Color(hex: viewModel.price == price ? "9970ff" : "777777")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.frame(width: buttonWidth, height: 48.7)
|
.frame(width: buttonWidth, height: 48.7)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 6.7)
|
RoundedRectangle(cornerRadius: 6.7)
|
||||||
.stroke(
|
.stroke(
|
||||||
Color(hex: viewModel.price == price ? "9970ff" : "777777"),
|
viewModel.price == price ? Color.button : Color.gray77,
|
||||||
lineWidth: 1
|
lineWidth: 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.background(
|
.background(viewModel.price == price ? Color.button.opacity(0.3) : Color.gray23)
|
||||||
viewModel.price == price ?
|
|
||||||
Color(hex:"9970ff").opacity(0.3):
|
|
||||||
Color(hex: "232323")
|
|
||||||
)
|
|
||||||
.cornerRadius(6.7)
|
.cornerRadius(6.7)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
|
|
@ -9,13 +9,19 @@ import UIKit
|
||||||
import Moya
|
import Moya
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
enum SelectedMenu: Int {
|
||||||
|
case MENU_1 = 0
|
||||||
|
case MENU_2 = 1
|
||||||
|
case MENU_3 = 2
|
||||||
|
}
|
||||||
|
|
||||||
final class LiveRoomCreateViewModel: ObservableObject {
|
final class LiveRoomCreateViewModel: ObservableObject {
|
||||||
enum TimeSettingMode: String {
|
enum TimeSettingMode: String {
|
||||||
case NOW, RESERVATION
|
case NOW, RESERVATION
|
||||||
}
|
}
|
||||||
|
|
||||||
enum LiveRoomType: String, Codable {
|
enum LiveRoomType: String, Codable {
|
||||||
case OPEN, PRIVATE, SECRET
|
case OPEN, PRIVATE
|
||||||
}
|
}
|
||||||
|
|
||||||
let prices = [0, 100, 300, 500, 1000, 2000]
|
let prices = [0, 100, 300, 500, 1000, 2000]
|
||||||
|
@ -40,15 +46,12 @@ final class LiveRoomCreateViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var roomType: LiveRoomType = .OPEN {
|
@Published var roomType: LiveRoomType = .OPEN {
|
||||||
didSet {
|
didSet {
|
||||||
if roomType == .SECRET {
|
|
||||||
timeSettingMode = .NOW
|
|
||||||
}
|
|
||||||
|
|
||||||
if roomType != .PRIVATE {
|
if roomType != .PRIVATE {
|
||||||
password = ""
|
password = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var password = "" {
|
@Published var password = "" {
|
||||||
didSet {
|
didSet {
|
||||||
if password.count > 6 {
|
if password.count > 6 {
|
||||||
|
@ -77,6 +80,14 @@ final class LiveRoomCreateViewModel: ObservableObject {
|
||||||
@Published var isShowPopup = false
|
@Published var isShowPopup = false
|
||||||
@Published var isShowGetRecentInfoButton = true
|
@Published var isShowGetRecentInfoButton = true
|
||||||
|
|
||||||
|
|
||||||
|
private var menuId = 0
|
||||||
|
@Published var menu = ""
|
||||||
|
@Published var menuList = [GetMenuPresetResponse]()
|
||||||
|
|
||||||
|
@Published var isActivateMenu = false
|
||||||
|
@Published var selectedMenu: SelectedMenu? = nil
|
||||||
|
|
||||||
private let repository = LiveRepository()
|
private let repository = LiveRepository()
|
||||||
private var subscription = Set<AnyCancellable>()
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@ -164,10 +175,13 @@ final class LiveRoomCreateViewModel: ObservableObject {
|
||||||
isAdult: isAdult,
|
isAdult: isAdult,
|
||||||
price: price,
|
price: price,
|
||||||
type: roomType,
|
type: roomType,
|
||||||
password: (roomType == .PRIVATE && !password.trimmingCharacters(in: .whitespaces).isEmpty) ? password : nil
|
password: (roomType == .PRIVATE && !password.trimmingCharacters(in: .whitespaces).isEmpty) ? password : nil,
|
||||||
|
menuPanId: isActivateMenu ? menuId : 0,
|
||||||
|
menuPan: isActivateMenu ? menu : "",
|
||||||
|
isActiveMenuPan: isActivateMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
if timeSettingMode == .RESERVATION && roomType != .SECRET {
|
if timeSettingMode == .RESERVATION {
|
||||||
request.beginDateTimeString = "\(reservationDate.convertDateFormat(dateFormat: "yyyy-MM-dd")) \(reservationTime.convertDateFormat(dateFormat: "HH:mm"))"
|
request.beginDateTimeString = "\(reservationDate.convertDateFormat(dateFormat: "yyyy-MM-dd")) \(reservationTime.convertDateFormat(dateFormat: "HH:mm"))"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,6 +247,73 @@ final class LiveRoomCreateViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func selectMenuPreset(selectedMenuPreset: SelectedMenu) {
|
||||||
|
if menuList.isEmpty && (selectedMenuPreset == .MENU_2 || selectedMenuPreset == .MENU_3) {
|
||||||
|
errorMessage = "메뉴 1을 먼저 설정하세요"
|
||||||
|
isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuList.count == 1 && selectedMenuPreset == .MENU_3 {
|
||||||
|
errorMessage = "메뉴 1과 메뉴 2를 먼저 설정하세요"
|
||||||
|
isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selectedMenu != selectedMenuPreset {
|
||||||
|
self.selectedMenu = selectedMenuPreset
|
||||||
|
|
||||||
|
if menuList.count > selectedMenuPreset.rawValue {
|
||||||
|
let menu = menuList[selectedMenuPreset.rawValue]
|
||||||
|
self.menu = menu.menu
|
||||||
|
self.menuId = menu.id
|
||||||
|
} else {
|
||||||
|
self.menu = ""
|
||||||
|
self.menuId = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllMenuPreset() {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.getAllMenuPreset(creatorId: UserDefaults.int(forKey: .userId))
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { [unowned self] response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponse<[GetMenuPresetResponse]>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.menuList.removeAll()
|
||||||
|
self.menuList.append(contentsOf: data)
|
||||||
|
self.selectMenuPreset(selectedMenuPreset: .MENU_1)
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
private func validate() -> Bool {
|
private func validate() -> Bool {
|
||||||
if coverImage == nil && coverImagePath == nil {
|
if coverImage == nil && coverImagePath == nil {
|
||||||
self.errorMessage = "커버이미지를 선택해주세요."
|
self.errorMessage = "커버이미지를 선택해주세요."
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
|
|
||||||
let placeholder = "라이브 공지를 입력하세요"
|
let placeholder = "라이브 공지를 입력하세요"
|
||||||
|
|
||||||
let viewModel: LiveRoomViewModel
|
@StateObject var viewModel: LiveRoomViewModel
|
||||||
|
|
||||||
let isLoading: Bool
|
let isLoading: Bool
|
||||||
let coverImageUrl: String?
|
let coverImageUrl: String?
|
||||||
|
@ -39,7 +39,7 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
self._isShowing = isShowing
|
self._isShowing = isShowing
|
||||||
self._isShowPhotoPicker = isShowPhotoPicker
|
self._isShowPhotoPicker = isShowPhotoPicker
|
||||||
|
|
||||||
self.viewModel = viewModel
|
self._viewModel = StateObject(wrappedValue: viewModel)
|
||||||
self.isLoading = isLoading
|
self.isLoading = isLoading
|
||||||
|
|
||||||
self.title = currentTitle
|
self.title = currentTitle
|
||||||
|
@ -53,13 +53,12 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
GeometryReader { proxy in
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
VStack(alignment: .leading, spacing: 0) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text("라이브 수정")
|
Text("라이브 수정")
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color.grayee)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -92,13 +91,13 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: 80, height: 116.8)
|
.frame(width: 80, height: 116.8)
|
||||||
.background(Color(hex: "3e3358"))
|
.background(Color.bg)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
Image("ic_camera")
|
Image("ic_camera")
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(30)
|
.cornerRadius(30)
|
||||||
.offset(x: 40, y: 40)
|
.offset(x: 40, y: 40)
|
||||||
}
|
}
|
||||||
|
@ -117,18 +116,27 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
ContentInputView()
|
ContentInputView()
|
||||||
.padding(.top, 33.3)
|
.padding(.top, 33.3)
|
||||||
|
|
||||||
|
LiveRoomMenuSelectView(
|
||||||
|
menu: $viewModel.menu,
|
||||||
|
isActivate: $viewModel.isActivateMenu,
|
||||||
|
menuCount: viewModel.menuList.count,
|
||||||
|
selectedMenu: viewModel.selectedMenu,
|
||||||
|
selectMenu: { viewModel.selectMenuPreset(selectedMenuPreset: $0) }
|
||||||
|
)
|
||||||
|
.padding(.top, 33.3)
|
||||||
|
|
||||||
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: "9970ff"))
|
.foregroundColor(Color.button)
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, 16)
|
||||||
.frame(width: (screenSize().width - 40) / 2)
|
.frame(width: (screenSize().width - 40) / 2)
|
||||||
.background(Color(hex: "9970ff").opacity(0.2))
|
.background(Color.button.opacity(0.2))
|
||||||
.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)
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
isShowing = false
|
isShowing = false
|
||||||
|
@ -139,7 +147,7 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, 16)
|
||||||
.frame(width: (screenSize().width - 40) / 2)
|
.frame(width: (screenSize().width - 40) / 2)
|
||||||
.background(Color(hex: "9970ff"))
|
.background(Color.button)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
confirmAction(
|
confirmAction(
|
||||||
|
@ -154,11 +162,14 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.padding(13.3)
|
.padding(13.3)
|
||||||
.frame(width: proxy.size.width, height: proxy.size.height)
|
|
||||||
.onTapGesture { hideKeyboard() }
|
.onTapGesture { hideKeyboard() }
|
||||||
|
.onAppear {
|
||||||
|
viewModel.getAllMenuPreset {
|
||||||
|
self.isShowing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
|
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
|
||||||
}
|
|
||||||
|
|
||||||
if viewModel.isShowPopup {
|
if viewModel.isShowPopup {
|
||||||
LiveRoomDialogView(
|
LiveRoomDialogView(
|
||||||
|
@ -193,15 +204,15 @@ struct LiveRoomInfoEditDialog: 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(.top, 12)
|
.padding(.top, 12)
|
||||||
.padding(.horizontal, 6.7)
|
.padding(.horizontal, 6.7)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(height: 1)
|
.frame(height: 1)
|
||||||
.foregroundColor(Color(hex: "909090").opacity(0.7))
|
.foregroundColor(Color.gray90.opacity(0.7))
|
||||||
.padding(.top, 8.3)
|
.padding(.top, 8.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,16 +223,16 @@ struct LiveRoomInfoEditDialog: View {
|
||||||
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("\(notice.count)자")
|
Text("\(notice.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(" / 1000자")
|
Text(" / 1000자")
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
.foregroundColor(Color(hex: "777777"))
|
.foregroundColor(Color.gray77)
|
||||||
}
|
}
|
||||||
|
|
||||||
TextViewWrapper(
|
TextViewWrapper(
|
||||||
|
|
|
@ -13,4 +13,7 @@ struct EditLiveRoomInfoRequest: Encodable {
|
||||||
let numberOfPeople: Int?
|
let numberOfPeople: Int?
|
||||||
let beginDateTimeString: String?
|
let beginDateTimeString: String?
|
||||||
let timezone: String?
|
let timezone: String?
|
||||||
|
var menuPanId: Int = 0
|
||||||
|
var menuPan: String = ""
|
||||||
|
var isActiveMenuPan: Bool? = nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ struct GetRoomInfoResponse: Decodable {
|
||||||
let listenerList: [LiveRoomMember]
|
let listenerList: [LiveRoomMember]
|
||||||
let managerList: [LiveRoomMember]
|
let managerList: [LiveRoomMember]
|
||||||
let donationRankingTop3UserIds: [Int]
|
let donationRankingTop3UserIds: [Int]
|
||||||
|
let menuPan: String
|
||||||
let isActiveRoulette: Bool
|
let isActiveRoulette: Bool
|
||||||
let isPrivateRoom: Bool
|
let isPrivateRoom: Bool
|
||||||
let password: String?
|
let password: String?
|
||||||
|
|
|
@ -67,7 +67,21 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
@Published var selectedProfile: LiveRoomMember?
|
@Published var selectedProfile: LiveRoomMember?
|
||||||
|
|
||||||
@Published var isShowNotice = false
|
@Published var isShowNotice = false {
|
||||||
|
didSet {
|
||||||
|
if isShowNotice {
|
||||||
|
isShowMenuPan = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Published var isShowMenuPan = false {
|
||||||
|
didSet {
|
||||||
|
if isShowMenuPan {
|
||||||
|
isShowNotice = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Published var isShowDonationPopup = false
|
@Published var isShowDonationPopup = false
|
||||||
|
|
||||||
|
@ -143,6 +157,38 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
@Published var rouletteSelectedItem = ""
|
@Published var rouletteSelectedItem = ""
|
||||||
var rouletteCan = 0
|
var rouletteCan = 0
|
||||||
|
|
||||||
|
@Published var signatureImageUrl = "" {
|
||||||
|
didSet {
|
||||||
|
if signatureImageUrl.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 7) {
|
||||||
|
if let imageUrl = self.signatureImageUrls.first {
|
||||||
|
self.signatureImageUrl = imageUrl
|
||||||
|
self.signatureImageUrls.removeFirst()
|
||||||
|
} else {
|
||||||
|
self.signatureImageUrl = ""
|
||||||
|
self.isShowSignatureImage = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var menuId = 0
|
||||||
|
@Published var menu = ""
|
||||||
|
@Published var menuList = [GetMenuPresetResponse]()
|
||||||
|
|
||||||
|
@Published var isActivateMenu = false {
|
||||||
|
didSet {
|
||||||
|
if !isActivateMenu {
|
||||||
|
menu = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Published var selectedMenu: SelectedMenu? = nil
|
||||||
|
|
||||||
|
var signatureImageUrls = [String]()
|
||||||
|
var isShowSignatureImage = false
|
||||||
|
|
||||||
var timer: DispatchSourceTimer?
|
var timer: DispatchSourceTimer?
|
||||||
|
|
||||||
func setOriginOffset(_ offset: CGFloat) {
|
func setOriginOffset(_ offset: CGFloat) {
|
||||||
|
@ -353,7 +399,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let jsonDecoder = JSONDecoder()
|
let jsonDecoder = JSONDecoder()
|
||||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
let decoded = try jsonDecoder.decode(ApiResponse<String>.self, from: responseData)
|
||||||
|
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
|
|
||||||
|
@ -363,6 +409,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
type: .DONATION,
|
type: .DONATION,
|
||||||
message: rawMessage,
|
message: rawMessage,
|
||||||
can: can,
|
can: can,
|
||||||
|
signatureImageUrl: decoded.data,
|
||||||
donationMessage: message
|
donationMessage: message
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -384,6 +431,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
)
|
)
|
||||||
|
|
||||||
totalDonationCan += can
|
totalDonationCan += can
|
||||||
|
showSignatureImage(imageUrl: decoded.data ?? "")
|
||||||
|
|
||||||
self.messageChangeFlag.toggle()
|
self.messageChangeFlag.toggle()
|
||||||
if self.messages.count > 100 {
|
if self.messages.count > 100 {
|
||||||
|
@ -599,10 +647,13 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
notice: liveRoomInfo!.notice != notice ? notice : nil,
|
notice: liveRoomInfo!.notice != notice ? notice : nil,
|
||||||
numberOfPeople: nil,
|
numberOfPeople: nil,
|
||||||
beginDateTimeString: nil,
|
beginDateTimeString: nil,
|
||||||
timezone: nil
|
timezone: nil,
|
||||||
|
menuPanId: isActivateMenu ? menuId : 0,
|
||||||
|
menuPan: isActivateMenu ? menu : "",
|
||||||
|
isActiveMenuPan: isActivateMenu
|
||||||
)
|
)
|
||||||
|
|
||||||
if (request.title == nil && request.notice == nil && coverImage == nil) {
|
if (request.title == nil && request.notice == nil && coverImage == nil && menu == liveRoomInfo?.menuPan) {
|
||||||
self.errorMessage = "변경사항이 없습니다."
|
self.errorMessage = "변경사항이 없습니다."
|
||||||
self.isShowErrorPopup = true
|
self.isShowErrorPopup = true
|
||||||
return
|
return
|
||||||
|
@ -613,7 +664,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) {
|
if (request.title != nil || request.notice != 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"))
|
||||||
|
@ -630,8 +681,6 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
repository.editLiveRoomInfo(roomId: AppState.shared.roomId, parameters: multipartData)
|
repository.editLiveRoomInfo(roomId: AppState.shared.roomId, parameters: multipartData)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
@ -649,6 +698,11 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
|
|
||||||
if decoded.success {
|
if decoded.success {
|
||||||
self.coverImage = nil
|
self.coverImage = nil
|
||||||
|
self.menuList.removeAll()
|
||||||
|
self.isActivateMenu = false
|
||||||
|
self.selectedMenu = nil
|
||||||
|
self.menu = ""
|
||||||
|
self.isShowMenuPan = false
|
||||||
self.getRoomInfo()
|
self.getRoomInfo()
|
||||||
|
|
||||||
let editRoomInfoMessage = LiveRoomChatRawMessage(
|
let editRoomInfoMessage = LiveRoomChatRawMessage(
|
||||||
|
@ -659,12 +713,102 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
)
|
)
|
||||||
|
|
||||||
self.agora.sendRawMessageToGroup(rawMessage: editRoomInfoMessage)
|
self.agora.sendRawMessageToGroup(rawMessage: editRoomInfoMessage)
|
||||||
|
self.errorMessage = "라이브 정보가 수정되었습니다."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
} else {
|
} else {
|
||||||
self.errorMessage = decoded.message ?? "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
|
self.errorMessage = decoded.message ?? "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
|
||||||
self.isShowPopup = true
|
self.isShowErrorPopup = true
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
self.errorMessage = "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
|
self.errorMessage = "라이브 정보를 수정하지 못했습니다.\n다시 시도해 주세요."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectMenuPreset(selectedMenuPreset: SelectedMenu) {
|
||||||
|
if menuList.isEmpty && (selectedMenuPreset == .MENU_2 || selectedMenuPreset == .MENU_3) {
|
||||||
|
errorMessage = "메뉴 1을 먼저 설정하세요"
|
||||||
|
isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if menuList.count == 1 && selectedMenuPreset == .MENU_3 {
|
||||||
|
errorMessage = "메뉴 1과 메뉴 2를 먼저 설정하세요"
|
||||||
|
isShowPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selectedMenu != selectedMenuPreset {
|
||||||
|
self.selectedMenu = selectedMenuPreset
|
||||||
|
|
||||||
|
if menuList.count > selectedMenuPreset.rawValue {
|
||||||
|
let menu = menuList[selectedMenuPreset.rawValue]
|
||||||
|
self.menu = menu.menu
|
||||||
|
self.menuId = menu.id
|
||||||
|
} else {
|
||||||
|
self.menu = ""
|
||||||
|
self.menuId = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAllMenuPreset(onFailure: @escaping () -> Void) {
|
||||||
|
isLoading = true
|
||||||
|
|
||||||
|
repository.getAllMenuPreset(creatorId: UserDefaults.int(forKey: .userId))
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { [unowned self] response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponse<[GetMenuPresetResponse]>.self, from: responseData)
|
||||||
|
|
||||||
|
if let data = decoded.data, decoded.success {
|
||||||
|
self.menuList.removeAll()
|
||||||
|
self.menuList.append(contentsOf: data)
|
||||||
|
self.isActivateMenu = false
|
||||||
|
self.selectedMenu = nil
|
||||||
|
|
||||||
|
for (index, menuPreset) in self.menuList.enumerated() {
|
||||||
|
if menuPreset.isActive {
|
||||||
|
switch index {
|
||||||
|
case 1:
|
||||||
|
self.selectMenuPreset(selectedMenuPreset: .MENU_2)
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
self.selectMenuPreset(selectedMenuPreset: .MENU_3)
|
||||||
|
|
||||||
|
default:
|
||||||
|
self.selectMenuPreset(selectedMenuPreset: .MENU_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isActivateMenu = true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onFailure()
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
onFailure()
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
self.isShowPopup = true
|
self.isShowPopup = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1341,8 +1485,8 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setActiveRoulette(isActiveRoulette: Bool) {
|
func setActiveRoulette(isActiveRoulette: Bool, message: String) {
|
||||||
self.popupContent = isActiveRoulette ? "룰렛을 활성화 했습니다." : "룰렛을 비활성화 했습니다."
|
self.popupContent = message
|
||||||
self.isShowPopup = true
|
self.isShowPopup = true
|
||||||
self.agora.sendRawMessageToGroup(
|
self.agora.sendRawMessageToGroup(
|
||||||
rawMessage: LiveRoomChatRawMessage(
|
rawMessage: LiveRoomChatRawMessage(
|
||||||
|
@ -1544,6 +1688,17 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
.store(in: &subscription)
|
.store(in: &subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func showSignatureImage(imageUrl: String) {
|
||||||
|
if imageUrl.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
|
||||||
|
if !isShowSignatureImage {
|
||||||
|
isShowSignatureImage = true
|
||||||
|
signatureImageUrl = imageUrl
|
||||||
|
} else {
|
||||||
|
signatureImageUrls.append(imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LiveRoomViewModel: AgoraRtcEngineDelegate {
|
extension LiveRoomViewModel: AgoraRtcEngineDelegate {
|
||||||
|
@ -1708,6 +1863,7 @@ extension LiveRoomViewModel: AgoraRtmChannelDelegate {
|
||||||
)
|
)
|
||||||
|
|
||||||
self.totalDonationCan += decoded.can
|
self.totalDonationCan += decoded.can
|
||||||
|
self.showSignatureImage(imageUrl: decoded.signatureImageUrl ?? "")
|
||||||
} else if decoded.type == .ROULETTE_DONATION {
|
} else if decoded.type == .ROULETTE_DONATION {
|
||||||
self.messages.append(
|
self.messages.append(
|
||||||
LiveRoomRouletteDonationChat(
|
LiveRoomRouletteDonationChat(
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
//
|
||||||
|
// GetMenuPresetResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 3/8/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct GetMenuPresetResponse: Decodable {
|
||||||
|
let id: Int
|
||||||
|
let menu: String
|
||||||
|
let isActive: Bool
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
//
|
||||||
|
// LiveRoomMenuSelectView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 3/8/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LiveRoomMenuSelectView: View {
|
||||||
|
|
||||||
|
@Binding var menu: String
|
||||||
|
@Binding var isActivate: Bool
|
||||||
|
|
||||||
|
let menuCount: Int
|
||||||
|
let selectedMenu: SelectedMenu?
|
||||||
|
let selectMenu: (SelectedMenu) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text("메뉴")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("메뉴를 활성화 하시겠습니까?")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||||
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(isActivate ? "btn_toggle_on_big" : "btn_toggle_off_big")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 33.3, height: 20)
|
||||||
|
.onTapGesture {
|
||||||
|
isActivate.toggle()
|
||||||
|
selectMenu(.MENU_1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 8)
|
||||||
|
|
||||||
|
if isActivate {
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
SelectedButtonView(
|
||||||
|
title: "메뉴 1",
|
||||||
|
isActive: true,
|
||||||
|
isSelected: selectedMenu == .MENU_1
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
selectMenu(.MENU_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedButtonView(
|
||||||
|
title: "메뉴 2",
|
||||||
|
isActive: menuCount > 0,
|
||||||
|
isSelected: selectedMenu == .MENU_2
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
selectMenu(.MENU_2)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedButtonView(
|
||||||
|
title: "메뉴 3",
|
||||||
|
isActive: menuCount > 1,
|
||||||
|
isSelected: selectedMenu == .MENU_3
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
selectMenu(.MENU_3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextViewWrapper(
|
||||||
|
text: $menu,
|
||||||
|
placeholder: "메뉴판을 작성해주세요.",
|
||||||
|
textColorHex: "eeeeee",
|
||||||
|
backgroundColorHex: "303030"
|
||||||
|
)
|
||||||
|
.frame(height: 200)
|
||||||
|
.cornerRadius(6.7)
|
||||||
|
}
|
||||||
|
.padding(.top, 13.3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LiveRoomMenuSelectView(
|
||||||
|
menu: .constant("메뉴 1 입니다\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n테스트"),
|
||||||
|
isActivate: .constant(true),
|
||||||
|
menuCount: 2,
|
||||||
|
selectedMenu: .MENU_1,
|
||||||
|
selectMenu: { _ in }
|
||||||
|
)
|
||||||
|
}
|
|
@ -7,7 +7,14 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct CreateOrUpdateRouletteRequest: Encodable {
|
struct CreateRouletteRequest: Encodable {
|
||||||
|
let can: Int
|
||||||
|
let isActive: Bool
|
||||||
|
let items: [RouletteItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpdateRouletteRequest: Encodable {
|
||||||
|
let id: Int
|
||||||
let can: Int
|
let can: Int
|
||||||
let isActive: Bool
|
let isActive: Bool
|
||||||
let items: [RouletteItem]
|
let items: [RouletteItem]
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// GetNewRouletteResponse.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/23/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct GetNewRouletteResponse: Decodable {
|
||||||
|
let id: Int
|
||||||
|
let can: Int
|
||||||
|
let isActive: Bool
|
||||||
|
let items: [RouletteItem]
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ struct RouletteSettingsView: View {
|
||||||
@StateObject var viewModel = RouletteSettingsViewModel()
|
@StateObject var viewModel = RouletteSettingsViewModel()
|
||||||
|
|
||||||
@Binding var isShowing: Bool
|
@Binding var isShowing: Bool
|
||||||
let onComplete: (Bool) -> Void
|
let onComplete: (Bool, String) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
|
@ -25,6 +25,36 @@ struct RouletteSettingsView: View {
|
||||||
|
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
SelectedButtonView(
|
||||||
|
title: "룰렛 1",
|
||||||
|
isActive: true,
|
||||||
|
isSelected: viewModel.selectedRoulette == .ROULETTE_1
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.selectRoulette(selectedRoulette: .ROULETTE_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedButtonView(
|
||||||
|
title: "룰렛 2",
|
||||||
|
isActive: viewModel.rouletteList.count > 0,
|
||||||
|
isSelected: viewModel.selectedRoulette == .ROULETTE_2
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.selectRoulette(selectedRoulette: .ROULETTE_2)
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectedButtonView(
|
||||||
|
title: "룰렛 3",
|
||||||
|
isActive: viewModel.rouletteList.count > 1,
|
||||||
|
isSelected: viewModel.selectedRoulette == .ROULETTE_3
|
||||||
|
)
|
||||||
|
.onTapGesture {
|
||||||
|
viewModel.selectRoulette(selectedRoulette: .ROULETTE_3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Text("룰렛을 활성화 하시겠습니까?")
|
Text("룰렛을 활성화 하시겠습니까?")
|
||||||
.font(.custom(Font.bold.rawValue, size: 16))
|
.font(.custom(Font.bold.rawValue, size: 16))
|
||||||
|
@ -39,6 +69,7 @@ struct RouletteSettingsView: View {
|
||||||
viewModel.isActive.toggle()
|
viewModel.isActive.toggle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.top, 26.7)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 13.3) {
|
VStack(alignment: .leading, spacing: 13.3) {
|
||||||
Text("룰렛 금액 설정")
|
Text("룰렛 금액 설정")
|
||||||
|
@ -135,7 +166,7 @@ struct RouletteSettingsView: View {
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
viewModel.createOrUpdateRoulette {
|
viewModel.createOrUpdateRoulette {
|
||||||
onComplete($0)
|
onComplete($0, $1)
|
||||||
isShowing = false
|
isShowing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +212,7 @@ struct RouletteSettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.getRoulette(creatorId: UserDefaults.int(forKey: .userId))
|
viewModel.getAllRoulette(creatorId: UserDefaults.int(forKey: .userId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,6 +220,6 @@ struct RouletteSettingsView: View {
|
||||||
|
|
||||||
struct RouletteSettingsView_Previews: PreviewProvider {
|
struct RouletteSettingsView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
RouletteSettingsView(isShowing: .constant(true)) { _ in }
|
RouletteSettingsView(isShowing: .constant(true)) { _, _ in }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
enum SelectedRoulette: Int {
|
||||||
|
case ROULETTE_1 = 0
|
||||||
|
case ROULETTE_2 = 1
|
||||||
|
case ROULETTE_3 = 2
|
||||||
|
}
|
||||||
|
|
||||||
final class RouletteSettingsViewModel: ObservableObject {
|
final class RouletteSettingsViewModel: ObservableObject {
|
||||||
private let repository = RouletteRepository()
|
private let repository = RouletteRepository()
|
||||||
private var subscription = Set<AnyCancellable>()
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
@ -34,7 +40,11 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
@Published var previewData: RoulettePreview? = nil
|
@Published var previewData: RoulettePreview? = nil
|
||||||
|
|
||||||
|
@Published var selectedRoulette: SelectedRoulette? = nil
|
||||||
|
|
||||||
var can = 0
|
var can = 0
|
||||||
|
private var rouletteId = 0
|
||||||
|
@Published var rouletteList = [GetNewRouletteResponse]()
|
||||||
|
|
||||||
func plusWeight(index: Int) {
|
func plusWeight(index: Int) {
|
||||||
options[index].weight += 1
|
options[index].weight += 1
|
||||||
|
@ -84,9 +94,9 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
self.options.append(contentsOf: options)
|
self.options.append(contentsOf: options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRoulette(creatorId: Int) {
|
func getAllRoulette(creatorId: Int) {
|
||||||
self.isLoading = true
|
self.isLoading = true
|
||||||
repository.getRoulette(creatorId: creatorId)
|
repository.getAllRoulette(creatorId: creatorId)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
@ -100,32 +110,15 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let jsonDecoder = JSONDecoder()
|
let jsonDecoder = JSONDecoder()
|
||||||
let decoded = try jsonDecoder.decode(ApiResponse<GetRouletteResponse>.self, from: responseData)
|
let decoded = try jsonDecoder.decode(ApiResponse<[GetNewRouletteResponse]>.self, from: responseData)
|
||||||
|
|
||||||
if let data = decoded.data, decoded.success {
|
if let data = decoded.data, decoded.success {
|
||||||
self.isActive = data.isActive
|
rouletteList.removeAll()
|
||||||
|
rouletteList.append(contentsOf: data)
|
||||||
if data.can > 0 {
|
selectRoulette(selectedRoulette: .ROULETTE_1)
|
||||||
self.canText = String(data.can)
|
|
||||||
} else {
|
} else {
|
||||||
self.canText = ""
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
}
|
self.isShowErrorPopup = true
|
||||||
|
|
||||||
if !data.items.isEmpty {
|
|
||||||
let options = data.items.map {
|
|
||||||
RouletteOption(title: $0.title, weight: $0.weight)
|
|
||||||
}
|
|
||||||
removeAllAndAddOptions(options: options)
|
|
||||||
recalculatePercentages()
|
|
||||||
} else {
|
|
||||||
self.addOption()
|
|
||||||
self.addOption()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.isActive = false
|
|
||||||
self.canText = ""
|
|
||||||
self.addOption()
|
|
||||||
self.addOption()
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
@ -155,10 +148,19 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
isShowPreview = true
|
isShowPreview = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOrUpdateRoulette(onSuccess: @escaping (Bool) -> Void) {
|
func createOrUpdateRoulette(onSuccess: @escaping (Bool, String) -> Void) {
|
||||||
if !isLoading {
|
if !isLoading {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|
||||||
|
if rouletteId > 0 {
|
||||||
|
updateRoulette(onSuccess: onSuccess)
|
||||||
|
} else {
|
||||||
|
createRoulette(onSuccess: onSuccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createRoulette(onSuccess: @escaping (Bool, String) -> Void) {
|
||||||
var items = [RouletteItem]()
|
var items = [RouletteItem]()
|
||||||
for option in options {
|
for option in options {
|
||||||
if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
@ -171,8 +173,28 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
items.append(RouletteItem(title: option.title, weight: option.weight))
|
items.append(RouletteItem(title: option.title, weight: option.weight))
|
||||||
}
|
}
|
||||||
|
|
||||||
let request = CreateOrUpdateRouletteRequest(can: can, isActive: isActive, items: items)
|
let selectedRouletteTitle: String
|
||||||
repository.createOrUpdateRoulette(request: request)
|
let successMessage: String
|
||||||
|
|
||||||
|
switch (self.selectedRoulette) {
|
||||||
|
case .ROULETTE_2:
|
||||||
|
selectedRouletteTitle = "룰렛 2"
|
||||||
|
|
||||||
|
case .ROULETTE_3:
|
||||||
|
selectedRouletteTitle = "룰렛 3"
|
||||||
|
|
||||||
|
default:
|
||||||
|
selectedRouletteTitle = "룰렛 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if isActive {
|
||||||
|
successMessage = "\(selectedRouletteTitle)로 설정하였습니다."
|
||||||
|
} else {
|
||||||
|
successMessage = "\(selectedRouletteTitle)을 설정했습니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = CreateRouletteRequest(can: can, isActive: isActive, items: items)
|
||||||
|
repository.createRoulette(request: request)
|
||||||
.sink { result in
|
.sink { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished:
|
case .finished:
|
||||||
|
@ -189,7 +211,7 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||||
|
|
||||||
if decoded.success {
|
if decoded.success {
|
||||||
onSuccess(isActive)
|
onSuccess(isActive, successMessage)
|
||||||
} else {
|
} else {
|
||||||
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
self.isShowErrorPopup = true
|
self.isShowErrorPopup = true
|
||||||
|
@ -201,5 +223,133 @@ final class RouletteSettingsViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
.store(in: &subscription)
|
.store(in: &subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateRoulette(onSuccess: @escaping (Bool, String) -> Void) {
|
||||||
|
var items = [RouletteItem]()
|
||||||
|
for option in options {
|
||||||
|
if option.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
isLoading = false
|
||||||
|
errorMessage = "옵션은 빈칸일 수 없습니다."
|
||||||
|
isShowErrorPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items.append(RouletteItem(title: option.title, weight: option.weight))
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedRoulette = rouletteList[selectedRoulette!.rawValue]
|
||||||
|
if selectedRoulette.isActive == isActive && selectedRoulette.can == can && selectedRoulette.items == items {
|
||||||
|
self.errorMessage = "변동사항이 없습니다."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
|
self.isLoading = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedRouletteTitle: String
|
||||||
|
let successMessage: String
|
||||||
|
|
||||||
|
switch (self.selectedRoulette) {
|
||||||
|
case .ROULETTE_2:
|
||||||
|
selectedRouletteTitle = "룰렛 2"
|
||||||
|
|
||||||
|
case .ROULETTE_3:
|
||||||
|
selectedRouletteTitle = "룰렛 3"
|
||||||
|
|
||||||
|
default:
|
||||||
|
selectedRouletteTitle = "룰렛 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
var isAllActive = false
|
||||||
|
|
||||||
|
rouletteList
|
||||||
|
.filter {
|
||||||
|
$0.id != selectedRoulette.id
|
||||||
|
}
|
||||||
|
.forEach {
|
||||||
|
if $0.isActive {
|
||||||
|
isAllActive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isActive {
|
||||||
|
successMessage = "\(selectedRouletteTitle)로 설정하였습니다."
|
||||||
|
} else if !isAllActive {
|
||||||
|
successMessage = "\(selectedRouletteTitle)이 비활성화 되었습니다."
|
||||||
|
} else {
|
||||||
|
successMessage = "\(selectedRouletteTitle)을 설정했습니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = UpdateRouletteRequest(id: rouletteId, can: can, isActive: isActive, items: items)
|
||||||
|
repository.updateRoulette(request: request)
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { [unowned self] response in
|
||||||
|
self.isLoading = false
|
||||||
|
let responseData = response.data
|
||||||
|
|
||||||
|
do {
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||||
|
|
||||||
|
if decoded.success {
|
||||||
|
onSuccess(isActive, successMessage)
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowErrorPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectRoulette(selectedRoulette: SelectedRoulette) {
|
||||||
|
if rouletteList.isEmpty && (selectedRoulette == .ROULETTE_2 || selectedRoulette == .ROULETTE_3) {
|
||||||
|
errorMessage = "룰렛 1을 먼저 설정하세요"
|
||||||
|
isShowErrorPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if rouletteList.count == 1 && selectedRoulette == .ROULETTE_3 {
|
||||||
|
errorMessage = "룰렛 1과 룰렛 2를 먼저 설정하세요"
|
||||||
|
isShowErrorPopup = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.selectedRoulette != selectedRoulette {
|
||||||
|
self.selectedRoulette = selectedRoulette
|
||||||
|
|
||||||
|
if rouletteList.count > selectedRoulette.rawValue {
|
||||||
|
let roulette = rouletteList[selectedRoulette.rawValue]
|
||||||
|
if roulette.can > 0 {
|
||||||
|
self.canText = String(roulette.can)
|
||||||
|
} else {
|
||||||
|
self.canText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rouletteId = roulette.id
|
||||||
|
self.isActive = roulette.isActive
|
||||||
|
let options = roulette.items.map {
|
||||||
|
RouletteOption(title: $0.title, weight: $0.weight)
|
||||||
|
}
|
||||||
|
removeAllAndAddOptions(options: options)
|
||||||
|
recalculatePercentages()
|
||||||
|
} else {
|
||||||
|
self.canText = ""
|
||||||
|
self.isActive = false
|
||||||
|
self.rouletteId = 0
|
||||||
|
|
||||||
|
options.removeAll()
|
||||||
|
self.addOption()
|
||||||
|
self.addOption()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ struct GetRouletteResponse: Decodable {
|
||||||
let items: [RouletteItem]
|
let items: [RouletteItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RouletteItem: Codable {
|
struct RouletteItem: Codable, Equatable {
|
||||||
let title: String
|
let title: String
|
||||||
let weight: Int
|
let weight: Int
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ import Moya
|
||||||
|
|
||||||
enum RouletteApi {
|
enum RouletteApi {
|
||||||
case getRoulette(creatorId: Int)
|
case getRoulette(creatorId: Int)
|
||||||
case createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest)
|
case getAllRoulette(creatorId: Int)
|
||||||
|
case createRoulette(request: CreateRouletteRequest)
|
||||||
|
case updateRoulette(request: UpdateRouletteRequest)
|
||||||
case spinRoulette(request: SpinRouletteRequest)
|
case spinRoulette(request: SpinRouletteRequest)
|
||||||
case refundRouletteDonation(roomId: Int)
|
case refundRouletteDonation(roomId: Int)
|
||||||
}
|
}
|
||||||
|
@ -22,24 +24,30 @@ extension RouletteApi: TargetType {
|
||||||
|
|
||||||
var path: String {
|
var path: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .getRoulette, .createOrUpdateRoulette:
|
case .getRoulette, .createRoulette, .updateRoulette:
|
||||||
return "/roulette"
|
return "/new-roulette"
|
||||||
|
|
||||||
|
case .getAllRoulette:
|
||||||
|
return "/new-roulette/creator"
|
||||||
|
|
||||||
case .spinRoulette:
|
case .spinRoulette:
|
||||||
return "/roulette/spin"
|
return "/new-roulette/spin"
|
||||||
|
|
||||||
case .refundRouletteDonation(let roomId):
|
case .refundRouletteDonation(let roomId):
|
||||||
return "/roulette/refund/\(roomId)"
|
return "/new-roulette/refund/\(roomId)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var method: Moya.Method {
|
var method: Moya.Method {
|
||||||
switch self {
|
switch self {
|
||||||
case .getRoulette:
|
case .getRoulette, .getAllRoulette:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .createOrUpdateRoulette, .spinRoulette, .refundRouletteDonation:
|
case .createRoulette, .spinRoulette, .refundRouletteDonation:
|
||||||
return .post
|
return .post
|
||||||
|
|
||||||
|
case .updateRoulette:
|
||||||
|
return .put
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +63,20 @@ extension RouletteApi: TargetType {
|
||||||
encoding: URLEncoding.queryString
|
encoding: URLEncoding.queryString
|
||||||
)
|
)
|
||||||
|
|
||||||
case .createOrUpdateRoulette(let request):
|
case .getAllRoulette(let creatorId):
|
||||||
|
let parameters = [
|
||||||
|
"creatorId": creatorId
|
||||||
|
] as [String : Any]
|
||||||
|
|
||||||
|
return .requestParameters(
|
||||||
|
parameters: parameters,
|
||||||
|
encoding: URLEncoding.queryString
|
||||||
|
)
|
||||||
|
|
||||||
|
case .createRoulette(let request):
|
||||||
|
return .requestJSONEncodable(request)
|
||||||
|
|
||||||
|
case .updateRoulette(let request):
|
||||||
return .requestJSONEncodable(request)
|
return .requestJSONEncodable(request)
|
||||||
|
|
||||||
case .spinRoulette(let request):
|
case .spinRoulette(let request):
|
||||||
|
|
|
@ -17,8 +17,16 @@ final class RouletteRepository {
|
||||||
return api.requestPublisher(.getRoulette(creatorId: creatorId))
|
return api.requestPublisher(.getRoulette(creatorId: creatorId))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOrUpdateRoulette(request: CreateOrUpdateRouletteRequest) -> AnyPublisher<Response, MoyaError> {
|
func getAllRoulette(creatorId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.createOrUpdateRoulette(request: request))
|
return api.requestPublisher(.getAllRoulette(creatorId: creatorId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRoulette(request: CreateRouletteRequest) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.createRoulette(request: request))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateRoulette(request: UpdateRouletteRequest) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.updateRoulette(request: request))
|
||||||
}
|
}
|
||||||
|
|
||||||
func spinRoulette(request: SpinRouletteRequest) -> AnyPublisher<Response, MoyaError> {
|
func spinRoulette(request: SpinRouletteRequest) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
|
|
@ -20,7 +20,7 @@ struct LiveRoomOverlayStrokeTextButton: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Text(text)
|
Text(text)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(Color.red)
|
.foregroundColor(textColor)
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
.padding(.vertical, 6)
|
.padding(.vertical, 6)
|
||||||
.overlay(
|
.overlay(
|
||||||
|
|
|
@ -63,18 +63,17 @@ struct LiveRoomInfoCreatorView: View {
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 5.3) {
|
||||||
Text(creatorNickname)
|
Text(creatorNickname)
|
||||||
.font(.custom(Font.medium.rawValue, size: 12))
|
.font(.custom(Font.medium.rawValue, size: 12))
|
||||||
.foregroundColor(.gray77)
|
.foregroundColor(.gray77)
|
||||||
}
|
|
||||||
|
|
||||||
if isShowFollowingButton {
|
if isShowFollowingButton {
|
||||||
Image(isFollowing ? "btn_select_checked" : "btn_plus_round")
|
Image(isFollowing ? "btn_following" : "btn_follow")
|
||||||
.resizable()
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.onTapGesture { onClickFollow() }
|
.onTapGesture { onClickFollow() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
.padding(.horizontal, 5.3)
|
.padding(.horizontal, 5.3)
|
||||||
|
|
|
@ -14,6 +14,8 @@ struct LiveRoomInfoGuestView: View {
|
||||||
|
|
||||||
let isOnBg: Bool
|
let isOnBg: Bool
|
||||||
let isOnNotice: Bool
|
let isOnNotice: Bool
|
||||||
|
let isOnMenuPan: Bool
|
||||||
|
let isShowMenuPanButton: Bool
|
||||||
|
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
let creatorNickname: String
|
let creatorNickname: String
|
||||||
|
@ -31,7 +33,9 @@ struct LiveRoomInfoGuestView: View {
|
||||||
let onClickFollow: (Bool) -> Void
|
let onClickFollow: (Bool) -> Void
|
||||||
let onClickProfile: (Int) -> Void
|
let onClickProfile: (Int) -> Void
|
||||||
let onClickNotice: () -> Void
|
let onClickNotice: () -> Void
|
||||||
|
let onClickMenuPan: () -> Void
|
||||||
let onClickTotalDonation: () -> Void
|
let onClickTotalDonation: () -> Void
|
||||||
|
let onClickChangeListener: () -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
|
@ -47,6 +51,16 @@ struct LiveRoomInfoGuestView: View {
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if speakerList.contains(where: { $0.id == UserDefaults.int(forKey: .userId)}) {
|
||||||
|
LiveRoomOverlayStrokeTextButton(
|
||||||
|
text: "리스너 변경",
|
||||||
|
textColor: Color.grayee,
|
||||||
|
strokeColor: Color.graybb,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeCornerRadius: 5.3
|
||||||
|
) { onClickChangeListener() }
|
||||||
|
}
|
||||||
|
|
||||||
LiveRoomOverlayStrokeTextToggleButton(
|
LiveRoomOverlayStrokeTextToggleButton(
|
||||||
isOn: isOnBg,
|
isOn: isOnBg,
|
||||||
onText: "배경 ON",
|
onText: "배경 ON",
|
||||||
|
@ -113,6 +127,21 @@ struct LiveRoomInfoGuestView: View {
|
||||||
onClick: { onClickNotice() }
|
onClick: { onClickNotice() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isShowMenuPanButton {
|
||||||
|
LiveRoomOverlayStrokeTextToggleButton(
|
||||||
|
isOn: isOnMenuPan,
|
||||||
|
onText: "메뉴판",
|
||||||
|
onTextColor: .button,
|
||||||
|
onStrokeColor: .button,
|
||||||
|
offText: nil,
|
||||||
|
offTextColor: .graybb,
|
||||||
|
offStrokeColor: .graybb,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeCornerRadius: 5.3,
|
||||||
|
onClick: { onClickMenuPan() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(spacing: 2.7) {
|
HStack(spacing: 2.7) {
|
||||||
|
@ -151,6 +180,8 @@ struct LiveRoomInfoGuestView_Previews: PreviewProvider {
|
||||||
totalDonationCan: 123456,
|
totalDonationCan: 123456,
|
||||||
isOnBg: true,
|
isOnBg: true,
|
||||||
isOnNotice: false,
|
isOnNotice: false,
|
||||||
|
isOnMenuPan: false,
|
||||||
|
isShowMenuPanButton: false,
|
||||||
creatorId: 1,
|
creatorId: 1,
|
||||||
creatorNickname: "도화",
|
creatorNickname: "도화",
|
||||||
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
@ -184,7 +215,9 @@ struct LiveRoomInfoGuestView_Previews: PreviewProvider {
|
||||||
onClickFollow: { _ in },
|
onClickFollow: { _ in },
|
||||||
onClickProfile: { _ in },
|
onClickProfile: { _ in },
|
||||||
onClickNotice: {},
|
onClickNotice: {},
|
||||||
onClickTotalDonation: {}
|
onClickMenuPan: {},
|
||||||
|
onClickTotalDonation: {},
|
||||||
|
onClickChangeListener: {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ struct LiveRoomInfoHostView: View {
|
||||||
|
|
||||||
let isOnBg: Bool
|
let isOnBg: Bool
|
||||||
let isOnNotice: Bool
|
let isOnNotice: Bool
|
||||||
|
let isOnMenuPan: Bool
|
||||||
|
let isShowMenuPanButton: Bool
|
||||||
|
|
||||||
let creatorId: Int
|
let creatorId: Int
|
||||||
let creatorNickname: String
|
let creatorNickname: String
|
||||||
|
@ -32,6 +34,7 @@ struct LiveRoomInfoHostView: View {
|
||||||
let onClickEdit: () -> Void
|
let onClickEdit: () -> Void
|
||||||
let onClickProfile: (Int) -> Void
|
let onClickProfile: (Int) -> Void
|
||||||
let onClickNotice: () -> Void
|
let onClickNotice: () -> Void
|
||||||
|
let onClickMenuPan: () -> Void
|
||||||
let onClickTotalDonation: () -> Void
|
let onClickTotalDonation: () -> Void
|
||||||
let onClickParticipants: () -> Void
|
let onClickParticipants: () -> Void
|
||||||
|
|
||||||
|
@ -122,6 +125,21 @@ struct LiveRoomInfoHostView: View {
|
||||||
onClick: { onClickNotice() }
|
onClick: { onClickNotice() }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isShowMenuPanButton {
|
||||||
|
LiveRoomOverlayStrokeTextToggleButton(
|
||||||
|
isOn: isOnMenuPan,
|
||||||
|
onText: "메뉴판",
|
||||||
|
onTextColor: .button,
|
||||||
|
onStrokeColor: .button,
|
||||||
|
offText: nil,
|
||||||
|
offTextColor: .graybb,
|
||||||
|
offStrokeColor: .graybb,
|
||||||
|
strokeWidth: 1,
|
||||||
|
strokeCornerRadius: 5.3,
|
||||||
|
onClick: { onClickMenuPan() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
HStack(spacing: 2.7) {
|
HStack(spacing: 2.7) {
|
||||||
|
@ -178,6 +196,8 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider {
|
||||||
participantsCount: 18,
|
participantsCount: 18,
|
||||||
isOnBg: true,
|
isOnBg: true,
|
||||||
isOnNotice: true,
|
isOnNotice: true,
|
||||||
|
isOnMenuPan: false,
|
||||||
|
isShowMenuPanButton: false,
|
||||||
creatorId: 1,
|
creatorId: 1,
|
||||||
creatorNickname: "도화",
|
creatorNickname: "도화",
|
||||||
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||||
|
@ -210,6 +230,7 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider {
|
||||||
onClickEdit: {},
|
onClickEdit: {},
|
||||||
onClickProfile: { _ in },
|
onClickProfile: { _ in },
|
||||||
onClickNotice: {},
|
onClickNotice: {},
|
||||||
|
onClickMenuPan: {},
|
||||||
onClickTotalDonation: {},
|
onClickTotalDonation: {},
|
||||||
onClickParticipants: {}
|
onClickParticipants: {}
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Kingfisher
|
import Kingfisher
|
||||||
|
import SDWebImageSwiftUI
|
||||||
|
|
||||||
struct LiveRoomViewV2: View {
|
struct LiveRoomViewV2: View {
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ struct LiveRoomViewV2: View {
|
||||||
participantsCount: liveRoomInfo.participantsCount,
|
participantsCount: liveRoomInfo.participantsCount,
|
||||||
isOnBg: viewModel.isBgOn,
|
isOnBg: viewModel.isBgOn,
|
||||||
isOnNotice: viewModel.isShowNotice,
|
isOnNotice: viewModel.isShowNotice,
|
||||||
|
isOnMenuPan: viewModel.isShowMenuPan,
|
||||||
|
isShowMenuPanButton: !liveRoomInfo.menuPan.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||||
creatorId: liveRoomInfo.creatorId,
|
creatorId: liveRoomInfo.creatorId,
|
||||||
creatorNickname: liveRoomInfo.creatorNickname,
|
creatorNickname: liveRoomInfo.creatorNickname,
|
||||||
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
|
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
|
||||||
|
@ -55,6 +58,9 @@ struct LiveRoomViewV2: View {
|
||||||
onClickNotice: {
|
onClickNotice: {
|
||||||
viewModel.isShowNotice.toggle()
|
viewModel.isShowNotice.toggle()
|
||||||
},
|
},
|
||||||
|
onClickMenuPan: {
|
||||||
|
viewModel.isShowMenuPan.toggle()
|
||||||
|
},
|
||||||
onClickTotalDonation: {
|
onClickTotalDonation: {
|
||||||
viewModel.isShowDonationRankingPopup = true
|
viewModel.isShowDonationRankingPopup = true
|
||||||
},
|
},
|
||||||
|
@ -68,6 +74,8 @@ struct LiveRoomViewV2: View {
|
||||||
totalDonationCan: viewModel.totalDonationCan,
|
totalDonationCan: viewModel.totalDonationCan,
|
||||||
isOnBg: viewModel.isBgOn,
|
isOnBg: viewModel.isBgOn,
|
||||||
isOnNotice: viewModel.isShowNotice,
|
isOnNotice: viewModel.isShowNotice,
|
||||||
|
isOnMenuPan: viewModel.isShowMenuPan,
|
||||||
|
isShowMenuPanButton: !liveRoomInfo.menuPan.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||||
creatorId: liveRoomInfo.creatorId,
|
creatorId: liveRoomInfo.creatorId,
|
||||||
creatorNickname: liveRoomInfo.creatorNickname,
|
creatorNickname: liveRoomInfo.creatorNickname,
|
||||||
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
|
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
|
||||||
|
@ -100,8 +108,14 @@ struct LiveRoomViewV2: View {
|
||||||
onClickNotice: {
|
onClickNotice: {
|
||||||
viewModel.isShowNotice.toggle()
|
viewModel.isShowNotice.toggle()
|
||||||
},
|
},
|
||||||
|
onClickMenuPan: {
|
||||||
|
viewModel.isShowMenuPan.toggle()
|
||||||
|
},
|
||||||
onClickTotalDonation: {
|
onClickTotalDonation: {
|
||||||
viewModel.isShowDonationRankingPopup = true
|
viewModel.isShowDonationRankingPopup = true
|
||||||
|
},
|
||||||
|
onClickChangeListener: {
|
||||||
|
viewModel.setListener()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -241,6 +255,20 @@ struct LiveRoomViewV2: View {
|
||||||
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
|
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
|
||||||
}.padding(.bottom, 70)
|
}.padding(.bottom, 70)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.signatureImageUrl.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
AnimatedImage(url: URL(string: viewModel.signatureImageUrl))
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(height: 300)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.bottom, 65)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,6 +306,28 @@ struct LiveRoomViewV2: View {
|
||||||
.padding(.bottom, 120)
|
.padding(.bottom, 120)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowMenuPan {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Image("ic_notice_triangle")
|
||||||
|
.padding(.leading, 60)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("[메뉴판]")
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 11.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Text(liveRoomInfo.menuPan)
|
||||||
|
.font(.custom(Font.light.rawValue, size: 11.3))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.lineSpacing(4)
|
||||||
|
}
|
||||||
|
.padding(8)
|
||||||
|
.background(Color.gray33)
|
||||||
|
.padding(.horizontal, 60)
|
||||||
|
.padding(.bottom, 120)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -297,10 +347,12 @@ struct LiveRoomViewV2: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
|
if viewModel.liveRoomInfo == nil {
|
||||||
viewModel.quitRoom()
|
viewModel.quitRoom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
||||||
.offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0))
|
.offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
@ -548,8 +600,11 @@ struct LiveRoomViewV2: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
if viewModel.isShowRouletteSettings {
|
if viewModel.isShowRouletteSettings {
|
||||||
RouletteSettingsView(isShowing: $viewModel.isShowRouletteSettings) { isActiveRoulette in
|
RouletteSettingsView(isShowing: $viewModel.isShowRouletteSettings) { isActiveRoulette, message in
|
||||||
self.viewModel.setActiveRoulette(isActiveRoulette: isActiveRoulette)
|
self.viewModel.setActiveRoulette(
|
||||||
|
isActiveRoulette: isActiveRoulette,
|
||||||
|
message: message
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,6 +114,7 @@ struct HomeView: View {
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
pushTokenUpdate()
|
pushTokenUpdate()
|
||||||
|
viewModel.fetchAndUpdateIdfa()
|
||||||
viewModel.getMemberInfo()
|
viewModel.getMemberInfo()
|
||||||
viewModel.getEventPopup()
|
viewModel.getEventPopup()
|
||||||
viewModel.addAllPlaybackTracking()
|
viewModel.addAllPlaybackTracking()
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
|
import AppTrackingTransparency
|
||||||
|
import AdSupport
|
||||||
|
|
||||||
final class HomeViewModel: ObservableObject {
|
final class HomeViewModel: ObservableObject {
|
||||||
|
|
||||||
private var subscription = Set<AnyCancellable>()
|
private var subscription = Set<AnyCancellable>()
|
||||||
|
@ -37,6 +40,25 @@ final class HomeViewModel: ObservableObject {
|
||||||
.store(in: &subscription)
|
.store(in: &subscription)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchAndUpdateIdfa() {
|
||||||
|
ATTrackingManager.requestTrackingAuthorization { [unowned self] status in
|
||||||
|
if status == .authorized {
|
||||||
|
let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString
|
||||||
|
self.userRepository.updateIdfa(request: IdfaUpdateRequest(adid: idfa))
|
||||||
|
.sink { result in
|
||||||
|
switch result {
|
||||||
|
case .finished:
|
||||||
|
DEBUG_LOG("finish")
|
||||||
|
case .failure(let error):
|
||||||
|
ERROR_LOG(error.localizedDescription)
|
||||||
|
}
|
||||||
|
} receiveValue: { _ in
|
||||||
|
}
|
||||||
|
.store(in: &self.subscription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getMemberInfo() {
|
func getMemberInfo() {
|
||||||
userRepository.getMemberInfo()
|
userRepository.getMemberInfo()
|
||||||
.sink { result in
|
.sink { result in
|
||||||
|
|
|
@ -40,16 +40,6 @@ struct OnboardingView: View {
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.tag(4)
|
.tag(4)
|
||||||
|
|
||||||
Image("img_guide_5")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.tag(5)
|
|
||||||
|
|
||||||
Image("img_guide_6")
|
|
||||||
.resizable()
|
|
||||||
.scaledToFit()
|
|
||||||
.tag(6)
|
|
||||||
}
|
}
|
||||||
.tabViewStyle(PageTabViewStyle())
|
.tabViewStyle(PageTabViewStyle())
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
@ -59,7 +49,7 @@ struct OnboardingView: View {
|
||||||
|
|
||||||
Text("시작하기")
|
Text("시작하기")
|
||||||
.font(.custom(Font.bold.rawValue, size: 18.3))
|
.font(.custom(Font.bold.rawValue, size: 18.3))
|
||||||
.foregroundColor(Color(hex: "1313BC"))
|
.foregroundColor(.white)
|
||||||
.frame(width: screenSize().width, height: 60)
|
.frame(width: screenSize().width, height: 60)
|
||||||
.background(Color(hex: "80D8FF"))
|
.background(Color(hex: "80D8FF"))
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
|
|
@ -17,23 +17,19 @@ struct SplashView: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack(alignment: .top) {
|
ZStack(alignment: .top) {
|
||||||
Image("splash_bg_2024")
|
Image("splash_bg_2024_03")
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.edgesIgnoringSafeArea(.all)
|
.edgesIgnoringSafeArea(.all)
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
Image("splash_text_2024_03")
|
||||||
|
.padding(.top, 262)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Image("splash_logo_2024")
|
Image("splash_text_logo_2024_03")
|
||||||
|
.padding(.bottom, 35)
|
||||||
Image("splash_logo_dragon_2024")
|
|
||||||
.padding(.leading, 25)
|
|
||||||
.padding(.top, 50)
|
|
||||||
.padding(.bottom, 45)
|
|
||||||
|
|
||||||
Image("vividnext_logo")
|
|
||||||
.padding(.bottom, 45)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isShowUpdatePopup {
|
if isShowUpdatePopup {
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// SelectedButtonView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/23/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SelectedButtonView: View {
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let isActive: Bool
|
||||||
|
let isSelected: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 6.7) {
|
||||||
|
if isSelected {
|
||||||
|
Image("ic_select_check")
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.font(.custom(Font.bold.rawValue, size: 14.7))
|
||||||
|
.foregroundColor(!isActive ? Color.gray77 : isSelected ? .white : Color.button)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 14.3)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(!isActive ? Color.gray55 : isSelected ? Color.button : Color.bg)
|
||||||
|
.cornerRadius(6.7)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SelectedButtonView(title: "테스트", isActive: true, isSelected: true)
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ extension Color {
|
||||||
static let bg = Color(hex: "13181B")
|
static let bg = Color(hex: "13181B")
|
||||||
static let gray11 = Color(hex: "111111")
|
static let gray11 = Color(hex: "111111")
|
||||||
static let gray22 = Color(hex: "222222")
|
static let gray22 = Color(hex: "222222")
|
||||||
|
static let gray23 = Color(hex: "232323")
|
||||||
static let gray30 = Color(hex: "303030")
|
static let gray30 = Color(hex: "303030")
|
||||||
static let gray33 = Color(hex: "333333")
|
static let gray33 = Color(hex: "333333")
|
||||||
static let gray52 = Color(hex: "525252")
|
static let gray52 = Color(hex: "525252")
|
||||||
|
@ -27,5 +28,6 @@ extension Color {
|
||||||
|
|
||||||
static let mainRed = Color(hex: "ff5c49")
|
static let mainRed = Color(hex: "ff5c49")
|
||||||
static let mainRed2 = Color(hex: "ea3a25")
|
static let mainRed2 = Color(hex: "ea3a25")
|
||||||
|
static let mainRed3 = Color(hex: "dd4500")
|
||||||
static let mainYellow = Color(hex: "ffdc00")
|
static let mainYellow = Color(hex: "ffdc00")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
//
|
||||||
|
// IdfaUpdateRequest.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 2/26/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct IdfaUpdateRequest: Encodable {
|
||||||
|
let adid: String
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ enum UserApi {
|
||||||
case getChangeNicknamePrice
|
case getChangeNicknamePrice
|
||||||
case checkNickname(nickname: String)
|
case checkNickname(nickname: String)
|
||||||
case changeNickname(request: ProfileUpdateRequest)
|
case changeNickname(request: ProfileUpdateRequest)
|
||||||
|
case updateIdfa(request: IdfaUpdateRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UserApi: TargetType {
|
extension UserApi: TargetType {
|
||||||
|
@ -98,6 +99,9 @@ extension UserApi: TargetType {
|
||||||
|
|
||||||
case .changeNickname:
|
case .changeNickname:
|
||||||
return "/member/change/nickname"
|
return "/member/change/nickname"
|
||||||
|
|
||||||
|
case .updateIdfa:
|
||||||
|
return "/member/adid/update"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +114,7 @@ extension UserApi: TargetType {
|
||||||
case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname:
|
case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .updatePushToken, .profileUpdate, .changeNickname:
|
case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa:
|
||||||
return .put
|
return .put
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +171,9 @@ extension UserApi: TargetType {
|
||||||
|
|
||||||
case .changeNickname(let request):
|
case .changeNickname(let request):
|
||||||
return .requestJSONEncodable(request)
|
return .requestJSONEncodable(request)
|
||||||
|
|
||||||
|
case .updateIdfa(let request):
|
||||||
|
return .requestJSONEncodable(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -108,4 +108,8 @@ final class UserRepository {
|
||||||
func changeNickname(request: ProfileUpdateRequest) -> AnyPublisher<Response, MoyaError> {
|
func changeNickname(request: ProfileUpdateRequest) -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.changeNickname(request: request))
|
return api.requestPublisher(.changeNickname(request: request))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateIdfa(request: IdfaUpdateRequest) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.updateIdfa(request: request))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|