Compare commits
	
		
			85 Commits
		
	
	
		
			f0f1cd39b6
			...
			v1.1.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					41b7247a44 | ||
| 
						 | 
					63a259f890 | ||
| 
						 | 
					7c8f9b1460 | ||
| 
						 | 
					27df89d78e | ||
| 
						 | 
					cf7f1527b7 | ||
| 
						 | 
					68675ebfe8 | ||
| 
						 | 
					0079f248ee | ||
| 
						 | 
					6583e07f45 | ||
| 
						 | 
					902b772267 | ||
| 
						 | 
					cdad53ae7b | ||
| 
						 | 
					995c6adab7 | ||
| 
						 | 
					115a30a7b6 | ||
| 
						 | 
					c78b804678 | ||
| 
						 | 
					1a01244d85 | ||
| 
						 | 
					fea557560c | ||
| 
						 | 
					c440e8abd9 | ||
| 
						 | 
					41d5bad46f | ||
| 
						 | 
					6c8e19aed5 | ||
| 
						 | 
					282ee73de1 | ||
| 
						 | 
					91c43e679f | ||
| 
						 | 
					02835c4b2e | ||
| 
						 | 
					a4d15be57a | ||
| 
						 | 
					219128ea8d | ||
| 
						 | 
					47ae2ec8e1 | ||
| 
						 | 
					962197d319 | ||
| 
						 | 
					c75f94722b | ||
| 
						 | 
					c00931761c | ||
| 
						 | 
					8255065bba | ||
| 
						 | 
					1e1b97e2d4 | ||
| 
						 | 
					f653667df2 | ||
| 
						 | 
					658cff20eb | ||
| 
						 | 
					fd356451ae | ||
| 
						 | 
					91cd3fe995 | ||
| 
						 | 
					5f7924880e | ||
| 
						 | 
					00a8f7e8ff | ||
| 
						 | 
					d1b5ab31aa | ||
| 
						 | 
					225efc34e4 | ||
| 
						 | 
					4e607ed624 | ||
| 
						 | 
					c7522aa7c9 | ||
| 
						 | 
					b632a65a6f | ||
| 
						 | 
					d3c5a5bfb9 | ||
| 
						 | 
					2449eb14d0 | ||
| 
						 | 
					4c9e78f960 | ||
| 
						 | 
					6e9aaa0c8a | ||
| 
						 | 
					0294bbf223 | ||
| 
						 | 
					36aa167e1d | ||
| 
						 | 
					8c20ce9c62 | ||
| 
						 | 
					f44ef505cf | ||
| 
						 | 
					9a72e21fda | ||
| 
						 | 
					1c4503bda1 | ||
| 
						 | 
					b1773b117d | ||
| 
						 | 
					a16c38f4ab | ||
| 
						 | 
					475882570a | ||
| 
						 | 
					5b0cb44645 | ||
| 
						 | 
					b2f0975ad1 | ||
| 
						 | 
					48b1093dac | ||
| 
						 | 
					3bcd2b7dba | ||
| 
						 | 
					9863fc66de | ||
| 
						 | 
					58868f613a | ||
| 
						 | 
					1f992a11dc | ||
| 
						 | 
					948b1fd2b3 | ||
| 
						 | 
					5d95c0f1c9 | ||
| 
						 | 
					b31933715d | ||
| 
						 | 
					707b6f804c | ||
| 
						 | 
					05f5a4fe82 | ||
| 
						 | 
					f08d72745e | ||
| 
						 | 
					4611524f8f | ||
| 
						 | 
					a41c423991 | ||
| 
						 | 
					d76b1c7a59 | ||
| 
						 | 
					6fa183b89a | ||
| 
						 | 
					371d6d538a | ||
| 
						 | 
					2aa3f944c8 | ||
| 
						 | 
					e68961bd0d | ||
| 
						 | 
					88fcbc98f4 | ||
| 
						 | 
					3916a49e60 | ||
| 
						 | 
					594bcc27ca | ||
| 
						 | 
					bcb934a155 | ||
| 
						 | 
					b31dcf901b | ||
| 
						 | 
					2c8485a1cd | ||
| 
						 | 
					f0d2bda024 | ||
| 
						 | 
					828c507742 | ||
| 
						 | 
					65b43e4c0d | ||
| 
						 | 
					728510a72d | ||
| 
						 | 
					89fd2f5f0d | ||
| 
						 | 
					52921e78b6 | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/btn_audio_content_preview_play.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "btn_audio_content_preview_play.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
		 After Width: | Height: | Size: 5.8 KiB  | 
| 
		 Before Width: | Height: | Size: 978 B After Width: | Height: | Size: 2.8 KiB  | 
| 
		 Before Width: | Height: | Size: 845 B After Width: | Height: | Size: 3.1 KiB  | 
| 
		 Before Width: | Height: | Size: 927 B After Width: | Height: | Size: 4.6 KiB  | 
@@ -5,11 +5,11 @@
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "loading_4.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_alarm.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_alarm.imageset/ic_alarm.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 781 B  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_alarm_selected.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_alarm_selected.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_alarm_selected.imageset/ic_alarm_selected.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 804 B  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_blog_purple.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_blog_purple.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_blog_purple.imageset/ic_blog_purple.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
| 
		 Before Width: | Height: | Size: 1.3 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_heart_777.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_heart_777.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_heart_777.imageset/ic_heart_777.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 830 B  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_instagram_purple.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_instagram_purple.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_instagram_purple.imageset/ic_instagram_purple.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_kick_out.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_kick_out.imageset/ic_kick_out.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.5 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_message_send.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_message_send.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_message_send.imageset/ic_message_send.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 787 B  | 
@@ -9,7 +9,7 @@
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_headphones_purple.png",
 | 
			
		||||
      "filename" : "ic_message_square_777.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_message_square_777 1.imageset/ic_message_square_777.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 471 B  | 
| 
		 Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_mic_paint.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_mic_paint.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_mic_paint.imageset/ic_mic_paint.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 694 B  | 
@@ -5,11 +5,11 @@
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "loading_2.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_mute.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_mute.imageset/ic_mute.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 7.6 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_place_holder.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_place_holder.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_place_holder.imageset/ic_place_holder.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 70 KiB  | 
@@ -5,11 +5,11 @@
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "loading_3.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_record.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_record.imageset/ic_record.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.2 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_record_pause.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_record_pause.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_record_pause.imageset/ic_record_pause.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_record_play.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_record_play.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_record_play.imageset/ic_record_play.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_record_stop.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_record_stop.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_record_stop.imageset/ic_record_stop.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.9 KiB  | 
@@ -5,11 +5,11 @@
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "loading_1.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_save.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_save.imageset/ic_save.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 443 B  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_website_purple.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_website_purple.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_website_purple.imageset/ic_website_purple.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 926 B  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/ic_youtube_play_purple.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "ic_youtube_play_purple.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/ic_youtube_play_purple.imageset/ic_youtube_play_purple.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 858 B  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/img_compleate_book.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "img_compleate_book.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/img_compleate_book.imageset/img_compleate_book.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 21 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/img_noti_mute.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "img_noti_mute.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/img_noti_mute.imageset/img_noti_mute.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 23 KiB  | 
							
								
								
									
										21
									
								
								SodaLive/Resources/Assets.xcassets/img_thumb_default.imageset/Contents.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "img_thumb_default.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								SodaLive/Resources/Assets.xcassets/img_thumb_default.imageset/img_thumb_default.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
@@ -1,6 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 6.3 KiB  | 
| 
		 Before Width: | Height: | Size: 3.7 KiB  | 
| 
		 Before Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 5.1 KiB  | 
@@ -1,21 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "images" : [
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "1x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "filename" : "loading_5.png",
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "2x"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "idiom" : "universal",
 | 
			
		||||
      "scale" : "3x"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "info" : {
 | 
			
		||||
    "author" : "xcode",
 | 
			
		||||
    "version" : 1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 4.6 KiB  | 
| 
		 Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB  | 
@@ -1,25 +1,226 @@
 | 
			
		||||
<?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>FirebaseAppDelegateProxyEnabled</key>
 | 
			
		||||
	<false/>
 | 
			
		||||
	<key>NSAppTransportSecurity</key>
 | 
			
		||||
	<dict>
 | 
			
		||||
		<key>NSAllowsArbitraryLoads</key>
 | 
			
		||||
		<true/>
 | 
			
		||||
	</dict>
 | 
			
		||||
	<key>UIAppFonts</key>
 | 
			
		||||
	<array>
 | 
			
		||||
		<string>gmarket_sans_bold.otf</string>
 | 
			
		||||
		<string>gmarket_sans_medium.otf</string>
 | 
			
		||||
		<string>gmarket_sans_light.otf</string>
 | 
			
		||||
	</array>
 | 
			
		||||
	<key>UIBackgroundModes</key>
 | 
			
		||||
	<array>
 | 
			
		||||
		<string>audio</string>
 | 
			
		||||
		<string>fetch</string>
 | 
			
		||||
		<string>remote-notification</string>
 | 
			
		||||
	</array>
 | 
			
		||||
</dict>
 | 
			
		||||
    <dict>
 | 
			
		||||
        <key>FirebaseAppDelegateProxyEnabled</key>
 | 
			
		||||
        <false/>
 | 
			
		||||
        <key>NSAppTransportSecurity</key>
 | 
			
		||||
        <dict>
 | 
			
		||||
            <key>NSAllowsArbitraryLoads</key>
 | 
			
		||||
            <true/>
 | 
			
		||||
        </dict>
 | 
			
		||||
        <key>UIAppFonts</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <string>gmarket_sans_bold.otf</string>
 | 
			
		||||
            <string>gmarket_sans_medium.otf</string>
 | 
			
		||||
            <string>gmarket_sans_light.otf</string>
 | 
			
		||||
        </array>
 | 
			
		||||
        <key>UIBackgroundModes</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <string>audio</string>
 | 
			
		||||
            <string>fetch</string>
 | 
			
		||||
            <string>remote-notification</string>
 | 
			
		||||
        </array>
 | 
			
		||||
        <key>GADApplicationIdentifier</key>
 | 
			
		||||
        <string>ca-app-pub-1299501215847962~3447556960</string>
 | 
			
		||||
        <key>SKAdNetworkItems</key>
 | 
			
		||||
        <array>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>cstr6suwn9.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>4fzdc2evr5.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>4pfyvq9l8r.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>2fnua5tdw4.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>ydx93a7ass.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>5a6flpkh64.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>p78axxw29g.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>v72qych5uu.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>ludvb6z3bs.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>cp8zw746q7.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>3sh42y64q3.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>c6k4g5qg8m.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>s39g8k73mm.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>3qy4746246.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>f38h382jlk.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>hs6bdukanm.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>v4nxqhlyqp.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>wzmmz9fp6w.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>yclnxrl5pm.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>t38b2kh725.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>7ug5zh24hu.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>gta9lk7p23.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>vutu7akeur.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>y5ghdn5j9k.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>n6fk4nfna4.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>v9wttpbfk9.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>n38lu8286q.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>47vhws6wlr.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>kbd757ywx3.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>9t245vhmpl.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>eh6m2bh4zr.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>a2p9lx4jpn.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>22mmun2rn5.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>4468km3ulz.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>2u9pt9hc89.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>8s468mfl3y.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>klf5c3l5u5.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>ppxm28t8ap.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>ecpz2srf59.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>uw77j35x4d.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>pwa73g5rt2.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>mlmmfzh3r3.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>578prtvx9j.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>4dzt52r2t5.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>e5fvkxwrpn.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>8c4e2ghe7u.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>zq492l623r.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>3rd42ekr43.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
            <dict>
 | 
			
		||||
                <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
                <string>3qcr597p9d.skadnetwork</string>
 | 
			
		||||
            </dict>
 | 
			
		||||
        </array>
 | 
			
		||||
    </dict>
 | 
			
		||||
</plist>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,5 +21,206 @@
 | 
			
		||||
		<string>fetch</string>
 | 
			
		||||
		<string>remote-notification</string>
 | 
			
		||||
	</array>
 | 
			
		||||
    <key>GADApplicationIdentifier</key>
 | 
			
		||||
    <string>ca-app-pub-1299501215847962~8852459715</string>
 | 
			
		||||
    <key>SKAdNetworkItems</key>
 | 
			
		||||
    <array>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>cstr6suwn9.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>4fzdc2evr5.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>4pfyvq9l8r.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>2fnua5tdw4.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>ydx93a7ass.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>5a6flpkh64.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>p78axxw29g.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>v72qych5uu.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>ludvb6z3bs.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>cp8zw746q7.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>3sh42y64q3.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>c6k4g5qg8m.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>s39g8k73mm.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>3qy4746246.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>f38h382jlk.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>hs6bdukanm.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>v4nxqhlyqp.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>wzmmz9fp6w.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>yclnxrl5pm.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>t38b2kh725.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>7ug5zh24hu.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>gta9lk7p23.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>vutu7akeur.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>y5ghdn5j9k.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>n6fk4nfna4.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>v9wttpbfk9.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>n38lu8286q.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>47vhws6wlr.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>kbd757ywx3.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>9t245vhmpl.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>eh6m2bh4zr.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>a2p9lx4jpn.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>22mmun2rn5.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>4468km3ulz.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>2u9pt9hc89.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>8s468mfl3y.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>klf5c3l5u5.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>ppxm28t8ap.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>ecpz2srf59.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>uw77j35x4d.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>pwa73g5rt2.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>mlmmfzh3r3.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>578prtvx9j.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>4dzt52r2t5.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>e5fvkxwrpn.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>8c4e2ghe7u.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>zq492l623r.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>3rd42ekr43.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
      <dict>
 | 
			
		||||
        <key>SKAdNetworkIdentifier</key>
 | 
			
		||||
        <string>3qcr597p9d.skadnetwork</string>
 | 
			
		||||
      </dict>
 | 
			
		||||
    </array>
 | 
			
		||||
</dict>
 | 
			
		||||
</plist>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 | 
			
		||||
    
 | 
			
		||||
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
 | 
			
		||||
        FirebaseApp.configure()
 | 
			
		||||
        
 | 
			
		||||
        Messaging.messaging().delegate = self
 | 
			
		||||
        
 | 
			
		||||
        // For iOS 10 display notification (sent via APNS)
 | 
			
		||||
@@ -95,16 +94,26 @@ extension AppDelegate : UNUserNotificationCenterDelegate {
 | 
			
		||||
        Messaging.messaging().appDidReceiveMessage(userInfo)
 | 
			
		||||
        
 | 
			
		||||
        let roomIdString = userInfo["room_id"] as? String
 | 
			
		||||
        let audioContentIdString = userInfo["audio_content_id"] as? String
 | 
			
		||||
        let contentIdString = userInfo["content_id"] as? String
 | 
			
		||||
        let channelIdString = userInfo["channel_id"] as? String
 | 
			
		||||
        let messageIdString = userInfo["message_id"] as? String
 | 
			
		||||
        
 | 
			
		||||
        if let roomIdString = roomIdString, let roomId = Int(roomIdString), roomId > 0 {
 | 
			
		||||
            AppState.shared.pushRoomId = roomId
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let audioContentIdString = audioContentIdString, let audioContentId = Int(audioContentIdString), audioContentId > 0 {
 | 
			
		||||
        if let contentIdString = contentIdString, let audioContentId = Int(contentIdString), audioContentId > 0 {
 | 
			
		||||
            AppState.shared.pushAudioContentId = audioContentId
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let channelIdString = channelIdString, let channelId = Int(channelIdString), channelId > 0 {
 | 
			
		||||
            AppState.shared.pushChannelId = channelId
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let messageIdString = messageIdString, let messageId = Int(messageIdString), messageId > 0 {
 | 
			
		||||
            AppState.shared.pushMessageId = messageId
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        completionHandler()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ class AppState: ObservableObject {
 | 
			
		||||
    
 | 
			
		||||
    @Published var pushRoomId = 0
 | 
			
		||||
    @Published var pushChannelId = 0
 | 
			
		||||
    @Published var pushMessageId = 0
 | 
			
		||||
    @Published var pushAudioContentId = 0
 | 
			
		||||
    @Published var roomId = 0 {
 | 
			
		||||
        didSet {
 | 
			
		||||
 
 | 
			
		||||
@@ -105,4 +105,12 @@ enum AppStep {
 | 
			
		||||
    case profileUpdate(refresh: () -> Void)
 | 
			
		||||
    
 | 
			
		||||
    case followingList
 | 
			
		||||
    
 | 
			
		||||
    case orderListAll
 | 
			
		||||
    
 | 
			
		||||
    case newContentAll
 | 
			
		||||
    
 | 
			
		||||
    case curationAll(title: String, curationId: Int)
 | 
			
		||||
    
 | 
			
		||||
    case contentRankingAll
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,8 @@ struct SodaLiveApp: App {
 | 
			
		||||
        let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems
 | 
			
		||||
        let roomId = queryItems?.filter({$0.name == "room_id"}).first?.value
 | 
			
		||||
        let channelId = queryItems?.filter({$0.name == "channel_id"}).first?.value
 | 
			
		||||
        let audioContentId = queryItems?.filter({$0.name == "audio_content_id"}).first?.value
 | 
			
		||||
        let messageId = queryItems?.filter({$0.name == "message_id"}).first?.value
 | 
			
		||||
        let audioContentId = queryItems?.filter({$0.name == "content_id"}).first?.value
 | 
			
		||||
        
 | 
			
		||||
        if let roomId = roomId {
 | 
			
		||||
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
 | 
			
		||||
@@ -40,6 +41,12 @@ struct SodaLiveApp: App {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let messageId = messageId {
 | 
			
		||||
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
 | 
			
		||||
                AppState.shared.pushMessageId = Int(messageId) ?? 0
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let audioContentId = audioContentId {
 | 
			
		||||
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
 | 
			
		||||
                AppState.shared.pushAudioContentId = Int(audioContentId) ?? 0
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ struct AddAllPlaybackTrackingRequest: Encodable {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct PlaybackTrackingData: Encodable {
 | 
			
		||||
    let audioContentId: Int
 | 
			
		||||
    let contentId: Int
 | 
			
		||||
    let playDateTime: String
 | 
			
		||||
    let isPreview: Bool
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								SodaLive/Sources/Content/All/ContentNewAllItemView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,54 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentNewAllItemView.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
import Kingfisher
 | 
			
		||||
 | 
			
		||||
struct ContentNewAllItemView: View {
 | 
			
		||||
    
 | 
			
		||||
    let item: GetAudioContentMainItem
 | 
			
		||||
    
 | 
			
		||||
    @State var width: CGFloat = 0
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 8) {
 | 
			
		||||
            KFImage(URL(string: item.coverImageUrl))
 | 
			
		||||
                .resizable()
 | 
			
		||||
                .scaledToFill()
 | 
			
		||||
                .frame(width: width, height: width, alignment: .top)
 | 
			
		||||
                .cornerRadius(2.7)
 | 
			
		||||
            
 | 
			
		||||
            Text(item.title)
 | 
			
		||||
                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                .foregroundColor(Color(hex: "d2d2d2"))
 | 
			
		||||
                .frame(width: width, alignment: .leading)
 | 
			
		||||
                .multilineTextAlignment(.leading)
 | 
			
		||||
                .fixedSize(horizontal: false, vertical: true)
 | 
			
		||||
                .lineLimit(2)
 | 
			
		||||
            
 | 
			
		||||
            HStack(spacing: 5.3) {
 | 
			
		||||
                KFImage(URL(string: item.creatorProfileImageUrl))
 | 
			
		||||
                    .resizable()
 | 
			
		||||
                    .scaledToFill()
 | 
			
		||||
                    .frame(width: 21.3, height: 21.3)
 | 
			
		||||
                    .clipShape(Circle())
 | 
			
		||||
                    .onTapGesture { AppState.shared.setAppStep(step: .creatorDetail(userId: item.creatorId)) }
 | 
			
		||||
                
 | 
			
		||||
                Text(item.creatorNickname)
 | 
			
		||||
                    .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                    .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                    .lineLimit(1)
 | 
			
		||||
            }
 | 
			
		||||
            .padding(.bottom, 10)
 | 
			
		||||
        }
 | 
			
		||||
        .frame(width: width)
 | 
			
		||||
        .onTapGesture { AppState.shared.setAppStep(step: .contentDetail(contentId: item.contentId)) }
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            width = (screenSize().width - 40) / 2
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								SodaLive/Sources/Content/All/ContentNewAllView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,86 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentNewAllView.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ContentNewAllView: View {
 | 
			
		||||
    
 | 
			
		||||
    @StateObject var viewModel = ContentNewAllViewModel()
 | 
			
		||||
    
 | 
			
		||||
    let columns = [
 | 
			
		||||
        GridItem(.flexible(), alignment: .top),
 | 
			
		||||
        GridItem(.flexible(), alignment: .top)
 | 
			
		||||
    ]
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        BaseView(isLoading: $viewModel.isLoading) {
 | 
			
		||||
            VStack(spacing: 0) {
 | 
			
		||||
                DetailNavigationBar(title: "새로운 콘텐츠")
 | 
			
		||||
                
 | 
			
		||||
                Text("※ 최근 2주간 등록된 새로운 콘텐츠 입니다.")
 | 
			
		||||
                    .font(.custom(Font.medium.rawValue, size: 14.7))
 | 
			
		||||
                    .foregroundColor(Color(hex: "bbbbbb"))
 | 
			
		||||
                    .padding(.horizontal, 13.3)
 | 
			
		||||
                    .padding(.vertical, 8)
 | 
			
		||||
                    .frame(width: screenSize().width, alignment: .leading)
 | 
			
		||||
                    .background(Color(hex: "222222"))
 | 
			
		||||
                    .padding(.top, 13.3)
 | 
			
		||||
                
 | 
			
		||||
                ContentMainNewContentThemeView(
 | 
			
		||||
                    themes: viewModel.themeList,
 | 
			
		||||
                    selectTheme: {
 | 
			
		||||
                        viewModel.selectedTheme = $0
 | 
			
		||||
                    },
 | 
			
		||||
                    selectedTheme: $viewModel.selectedTheme
 | 
			
		||||
                ).padding(.top, 13.3)
 | 
			
		||||
                
 | 
			
		||||
                HStack(spacing: 0) {
 | 
			
		||||
                    Text("전체")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "e2e2e2"))
 | 
			
		||||
                    
 | 
			
		||||
                    Text("\(viewModel.totalCount)")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "ff5c49"))
 | 
			
		||||
                        .padding(.leading, 8)
 | 
			
		||||
                    
 | 
			
		||||
                    Text("개")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "e2e2e2"))
 | 
			
		||||
                        .padding(.leading, 2)
 | 
			
		||||
                    
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
                .padding(.vertical, 13.3)
 | 
			
		||||
                .padding(.horizontal, 20)
 | 
			
		||||
                
 | 
			
		||||
                ScrollView(.vertical, showsIndicators: false) {
 | 
			
		||||
                    LazyVGrid(columns: columns, spacing: 13.3) {
 | 
			
		||||
                        ForEach(0..<viewModel.newContentList.count, id: \.self) { index in
 | 
			
		||||
                            ContentNewAllItemView(item: viewModel.newContentList[index])
 | 
			
		||||
                                .onAppear {
 | 
			
		||||
                                    if index == viewModel.newContentList.count - 1 {
 | 
			
		||||
                                        viewModel.getNewContentList()
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .onAppear {
 | 
			
		||||
                viewModel.getThemeList()
 | 
			
		||||
                viewModel.getNewContentList()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ContentNewAllView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        ContentNewAllView()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								SodaLive/Sources/Content/All/ContentNewAllViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,130 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentNewAllViewModel.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
final class ContentNewAllViewModel: ObservableObject {
 | 
			
		||||
    
 | 
			
		||||
    private let repository = ContentRepository()
 | 
			
		||||
    private var subscription = Set<AnyCancellable>()
 | 
			
		||||
    
 | 
			
		||||
    @Published var errorMessage = ""
 | 
			
		||||
    @Published var isShowPopup = false
 | 
			
		||||
    @Published var isLoading = false
 | 
			
		||||
    
 | 
			
		||||
    @Published var themeList = [String]()
 | 
			
		||||
    @Published var newContentList = [GetAudioContentMainItem]()
 | 
			
		||||
    
 | 
			
		||||
    @Published var selectedTheme = "전체" {
 | 
			
		||||
        didSet {
 | 
			
		||||
            page = 1
 | 
			
		||||
            isLast = false
 | 
			
		||||
            getNewContentList()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    @Published var totalCount = 0
 | 
			
		||||
    
 | 
			
		||||
    var page = 1
 | 
			
		||||
    var isLast = false
 | 
			
		||||
    private let pageSize = 10
 | 
			
		||||
    
 | 
			
		||||
    func getNewContentList() {
 | 
			
		||||
        if (!isLast && !isLoading) {
 | 
			
		||||
            isLoading = true
 | 
			
		||||
            
 | 
			
		||||
            repository.getNewContentAllOfTheme(
 | 
			
		||||
                theme: selectedTheme == "전체" ? "" : selectedTheme,
 | 
			
		||||
                page: page,
 | 
			
		||||
                size: pageSize
 | 
			
		||||
            )
 | 
			
		||||
                .sink { result in
 | 
			
		||||
                    switch result {
 | 
			
		||||
                    case .finished:
 | 
			
		||||
                        DEBUG_LOG("finish")
 | 
			
		||||
                    case .failure(let error):
 | 
			
		||||
                        ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                    }
 | 
			
		||||
                } receiveValue: { [unowned self] response in
 | 
			
		||||
                    self.isLoading = false
 | 
			
		||||
                    let responseData = response.data
 | 
			
		||||
                    
 | 
			
		||||
                    do {
 | 
			
		||||
                        let jsonDecoder = JSONDecoder()
 | 
			
		||||
                        let decoded = try jsonDecoder.decode(ApiResponse<GetNewContentAllResponse>.self, from: responseData)
 | 
			
		||||
                        self.isLoading = false
 | 
			
		||||
                        
 | 
			
		||||
                        if let data = decoded.data, decoded.success {
 | 
			
		||||
                            if page == 1 {
 | 
			
		||||
                                newContentList.removeAll()
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            self.totalCount = data.totalCount
 | 
			
		||||
                            
 | 
			
		||||
                            if !data.items.isEmpty {
 | 
			
		||||
                                page += 1
 | 
			
		||||
                                self.newContentList.append(contentsOf: data.items)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                isLast = true
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if let message = decoded.message {
 | 
			
		||||
                                self.errorMessage = message
 | 
			
		||||
                            } else {
 | 
			
		||||
                                self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            self.isShowPopup = true
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch {
 | 
			
		||||
                        self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                        self.isLoading = false
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .store(in: &subscription)
 | 
			
		||||
        } else {
 | 
			
		||||
            isLoading = false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getThemeList() {
 | 
			
		||||
        repository.getNewContentThemeList()
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
                    DEBUG_LOG("finish")
 | 
			
		||||
                case .failure(let error):
 | 
			
		||||
                    ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                }
 | 
			
		||||
            } receiveValue: { [unowned self] response in
 | 
			
		||||
                let responseData = response.data
 | 
			
		||||
                
 | 
			
		||||
                do {
 | 
			
		||||
                    let jsonDecoder = JSONDecoder()
 | 
			
		||||
                    let decoded = try jsonDecoder.decode(ApiResponse<[String]>.self, from: responseData)
 | 
			
		||||
                    
 | 
			
		||||
                    if let data = decoded.data, decoded.success {
 | 
			
		||||
                        self.themeList.append("전체")
 | 
			
		||||
                        self.themeList.append(contentsOf: data)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if let message = decoded.message {
 | 
			
		||||
                            self.errorMessage = message
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                    }
 | 
			
		||||
                } catch {
 | 
			
		||||
                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                    self.isShowPopup = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										162
									
								
								SodaLive/Sources/Content/All/ContentRankingAllView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,162 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentRankingAllView.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/10/15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
import Kingfisher
 | 
			
		||||
 | 
			
		||||
struct ContentRankingAllView: View {
 | 
			
		||||
    
 | 
			
		||||
    @StateObject var viewModel = ContentRankingAllViewModel()
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        BaseView(isLoading: $viewModel.isLoading) {
 | 
			
		||||
            VStack(spacing: 0) {
 | 
			
		||||
                DetailNavigationBar(title: "인기 콘텐츠")
 | 
			
		||||
                
 | 
			
		||||
                VStack(spacing: 8) {
 | 
			
		||||
                    Text("\(viewModel.dateString)")
 | 
			
		||||
                        .font(.custom(Font.bold.rawValue, size: 14.7))
 | 
			
		||||
                        .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                    
 | 
			
		||||
                    Text("※ 인기 콘텐츠의 순위는 매주 업데이트됩니다.")
 | 
			
		||||
                        .font(.custom(Font.light.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "bbbbbb"))
 | 
			
		||||
                }
 | 
			
		||||
                .padding(.vertical, 8)
 | 
			
		||||
                .frame(width: screenSize().width - 26.7)
 | 
			
		||||
                .background(Color(hex: "222222"))
 | 
			
		||||
                .padding(.top, 13.3)
 | 
			
		||||
                
 | 
			
		||||
                ContentMainRankingSortView(
 | 
			
		||||
                    sorts: viewModel.contentRankingSortList,
 | 
			
		||||
                    selectSort: { viewModel.selectedContentRankingSort = $0 },
 | 
			
		||||
                    selectedSort: $viewModel.selectedContentRankingSort
 | 
			
		||||
                )
 | 
			
		||||
                .frame(width: screenSize().width - 26.7)
 | 
			
		||||
                .padding(.vertical, 16.7)
 | 
			
		||||
                
 | 
			
		||||
                ScrollView(.vertical, showsIndicators: false) {
 | 
			
		||||
                    VStack(spacing: 20) {
 | 
			
		||||
                        ForEach(0..<viewModel.contentRankingItemList.count, id: \.self) { index in
 | 
			
		||||
                            let item = viewModel.contentRankingItemList[index]
 | 
			
		||||
                            HStack(spacing: 0) {
 | 
			
		||||
                                KFImage(URL(string: item.coverImageUrl))
 | 
			
		||||
                                    .resizable()
 | 
			
		||||
                                    .scaledToFill()
 | 
			
		||||
                                    .frame(width: 66.7, height: 66.7, alignment: .top)
 | 
			
		||||
                                    .clipped()
 | 
			
		||||
                                    .cornerRadius(5.3)
 | 
			
		||||
                                
 | 
			
		||||
                                Text("\(index + 1)")
 | 
			
		||||
                                    .font(.custom(Font.bold.rawValue, size: 16.7))
 | 
			
		||||
                                    .foregroundColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                                    .padding(.horizontal, 12)
 | 
			
		||||
                                
 | 
			
		||||
                                VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
                                    HStack(spacing: 8) {
 | 
			
		||||
                                        Text(item.themeStr)
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 8))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "3bac6a"))
 | 
			
		||||
                                            .padding(2.6)
 | 
			
		||||
                                            .background(Color(hex: "28312b"))
 | 
			
		||||
                                            .cornerRadius(2.6)
 | 
			
		||||
                                        
 | 
			
		||||
                                        Text(item.duration)
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 8))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                            .padding(2.6)
 | 
			
		||||
                                            .background(Color(hex: "222222"))
 | 
			
		||||
                                            .cornerRadius(2.6)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    
 | 
			
		||||
                                    Text(item.creatorNickname)
 | 
			
		||||
                                        .font(.custom(Font.medium.rawValue, size: 10.7))
 | 
			
		||||
                                        .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                        .padding(.vertical, 8)
 | 
			
		||||
                                    
 | 
			
		||||
                                    Text(item.title)
 | 
			
		||||
                                        .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                                        .foregroundColor(Color(hex: "d2d2d2"))
 | 
			
		||||
                                        .lineLimit(2)
 | 
			
		||||
                                        .padding(.top, 2.7)
 | 
			
		||||
                                }
 | 
			
		||||
                                
 | 
			
		||||
                                Spacer()
 | 
			
		||||
                                
 | 
			
		||||
                                if item.price > 0 {
 | 
			
		||||
                                    HStack(spacing: 8) {
 | 
			
		||||
                                        Image("ic_can")
 | 
			
		||||
                                            .resizable()
 | 
			
		||||
                                            .frame(width: 17, height: 17)
 | 
			
		||||
                                        
 | 
			
		||||
                                        Text("\(item.price)")
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "909090"))
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    Text("무료")
 | 
			
		||||
                                        .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                                        .foregroundColor(Color(hex: "ffffff"))
 | 
			
		||||
                                        .padding(.horizontal, 5.3)
 | 
			
		||||
                                        .padding(.vertical, 2.7)
 | 
			
		||||
                                        .background(Color(hex: "cf5c37"))
 | 
			
		||||
                                        .cornerRadius(2.6)
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            .frame(height: 66.7)
 | 
			
		||||
                            .contentShape(Rectangle())
 | 
			
		||||
                            .onTapGesture {
 | 
			
		||||
                                AppState
 | 
			
		||||
                                    .shared
 | 
			
		||||
                                    .setAppStep(step: .contentDetail(contentId: item.contentId))
 | 
			
		||||
                            }
 | 
			
		||||
                            .onAppear {
 | 
			
		||||
                                if index == viewModel.contentRankingItemList.count - 1 {
 | 
			
		||||
                                    viewModel.getContentRanking()
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .padding(13.3)
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if viewModel.isLoading {
 | 
			
		||||
                LoadingView()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) {
 | 
			
		||||
            GeometryReader { geo in
 | 
			
		||||
                HStack {
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                    Text(viewModel.errorMessage)
 | 
			
		||||
                        .padding(.vertical, 13.3)
 | 
			
		||||
                        .padding(.horizontal, 6.7)
 | 
			
		||||
                        .frame(width: geo.size.width - 66.7, alignment: .center)
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                        .background(Color(hex: "9970ff"))
 | 
			
		||||
                        .foregroundColor(Color.white)
 | 
			
		||||
                        .multilineTextAlignment(.leading)
 | 
			
		||||
                        .fixedSize(horizontal: false, vertical: true)
 | 
			
		||||
                        .cornerRadius(20)
 | 
			
		||||
                        .padding(.top, 66.7)
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            viewModel.getContentRankingSortType()
 | 
			
		||||
            viewModel.getContentRanking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ContentRankingAllView_Previews: PreviewProvider {
 | 
			
		||||
    static var previews: some View {
 | 
			
		||||
        ContentRankingAllView()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										124
									
								
								SodaLive/Sources/Content/All/ContentRankingAllViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,124 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentRankingAllViewModel.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/10/15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
final class ContentRankingAllViewModel: ObservableObject {
 | 
			
		||||
    
 | 
			
		||||
    private let repository = ContentRepository()
 | 
			
		||||
    private var subscription = Set<AnyCancellable>()
 | 
			
		||||
    
 | 
			
		||||
    @Published var errorMessage = ""
 | 
			
		||||
    @Published var isShowPopup = false
 | 
			
		||||
    @Published var isLoading = false
 | 
			
		||||
    
 | 
			
		||||
    @Published var dateString = ""
 | 
			
		||||
    @Published var contentRankingItemList = [GetAudioContentRankingItem]()
 | 
			
		||||
    @Published var contentRankingSortList = [String]()
 | 
			
		||||
    
 | 
			
		||||
    @Published var selectedContentRankingSort = "매출" {
 | 
			
		||||
        didSet {
 | 
			
		||||
            page = 1
 | 
			
		||||
            isLast = false
 | 
			
		||||
            getContentRanking()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var page = 1
 | 
			
		||||
    var isLast = false
 | 
			
		||||
    private let pageSize = 10
 | 
			
		||||
    
 | 
			
		||||
    func getContentRanking() {
 | 
			
		||||
        if (!isLast && !isLoading && page <= 5) {
 | 
			
		||||
            isLoading = true
 | 
			
		||||
            
 | 
			
		||||
            repository.getContentRanking(page: page, size: pageSize, sortType: selectedContentRankingSort)
 | 
			
		||||
                .sink { result in
 | 
			
		||||
                    switch result {
 | 
			
		||||
                    case .finished:
 | 
			
		||||
                        DEBUG_LOG("finish")
 | 
			
		||||
                    case .failure(let error):
 | 
			
		||||
                        ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                    }
 | 
			
		||||
                } receiveValue: { [unowned self] response in
 | 
			
		||||
                    self.isLoading = false
 | 
			
		||||
                    let responseData = response.data
 | 
			
		||||
                    
 | 
			
		||||
                    do {
 | 
			
		||||
                        let jsonDecoder = JSONDecoder()
 | 
			
		||||
                        let decoded = try jsonDecoder.decode(ApiResponse<GetAudioContentRanking>.self, from: responseData)
 | 
			
		||||
                        self.isLoading = false
 | 
			
		||||
                        
 | 
			
		||||
                        if let data = decoded.data, decoded.success {
 | 
			
		||||
                            if page == 1 {
 | 
			
		||||
                                contentRankingItemList.removeAll()
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            dateString = "\(data.startDate)~\(data.endDate)"
 | 
			
		||||
                            
 | 
			
		||||
                            if !data.items.isEmpty {
 | 
			
		||||
                                page += 1
 | 
			
		||||
                                self.contentRankingItemList.append(contentsOf: data.items)
 | 
			
		||||
                            } else {
 | 
			
		||||
                                isLast = true
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if let message = decoded.message {
 | 
			
		||||
                                self.errorMessage = message
 | 
			
		||||
                            } else {
 | 
			
		||||
                                self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            self.isShowPopup = true
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch {
 | 
			
		||||
                        self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                        self.isLoading = false
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .store(in: &subscription)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getContentRankingSortType() {
 | 
			
		||||
        repository.getContentRankingSortType()
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
                    DEBUG_LOG("finish")
 | 
			
		||||
                case .failure(let error):
 | 
			
		||||
                    ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                }
 | 
			
		||||
            } receiveValue: { [unowned self] response in
 | 
			
		||||
                let responseData = response.data
 | 
			
		||||
                
 | 
			
		||||
                do {
 | 
			
		||||
                    let jsonDecoder = JSONDecoder()
 | 
			
		||||
                    let decoded = try jsonDecoder.decode(ApiResponse<[String]>.self, from: responseData)
 | 
			
		||||
                    
 | 
			
		||||
                    if let data = decoded.data, decoded.success {
 | 
			
		||||
                        self.contentRankingSortList.removeAll()
 | 
			
		||||
                        self.contentRankingSortList.append(contentsOf: data)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if let message = decoded.message {
 | 
			
		||||
                            self.errorMessage = message
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                    }
 | 
			
		||||
                } catch {
 | 
			
		||||
                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                    self.isShowPopup = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										11
									
								
								SodaLive/Sources/Content/All/GetNewContentAllResponse.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,11 @@
 | 
			
		||||
//
 | 
			
		||||
//  GetNewContentAllResponse.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
struct GetNewContentAllResponse: Decodable {
 | 
			
		||||
    let totalCount: Int
 | 
			
		||||
    let items: [GetAudioContentMainItem]
 | 
			
		||||
}
 | 
			
		||||
@@ -25,6 +25,12 @@ enum ContentApi {
 | 
			
		||||
    case getMain
 | 
			
		||||
    case getNewContentOfTheme(theme: String)
 | 
			
		||||
    case donation(request: AudioContentDonationRequest)
 | 
			
		||||
    case modifyComment(request: ModifyCommentRequest)
 | 
			
		||||
    case getNewContentThemeList
 | 
			
		||||
    case getNewContentAllOfTheme(theme: String, page: Int, size: Int)
 | 
			
		||||
    case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
 | 
			
		||||
    case getContentRanking(page: Int, size: Int, sortType: String)
 | 
			
		||||
    case getContentRankingSortType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension ContentApi: TargetType {
 | 
			
		||||
@@ -81,15 +87,36 @@ extension ContentApi: TargetType {
 | 
			
		||||
            
 | 
			
		||||
        case .donation:
 | 
			
		||||
            return "/audio-content/donation"
 | 
			
		||||
            
 | 
			
		||||
        case .modifyComment:
 | 
			
		||||
            return "/audio-content/comment"
 | 
			
		||||
            
 | 
			
		||||
        case .getNewContentThemeList:
 | 
			
		||||
            return "/audio-content/main/theme"
 | 
			
		||||
            
 | 
			
		||||
        case .getNewContentAllOfTheme:
 | 
			
		||||
            return "/audio-content/main/new/all"
 | 
			
		||||
            
 | 
			
		||||
        case .getAudioContentListByCurationId(let curationId, _, _, _):
 | 
			
		||||
            return "/audio-content/curation/\(curationId)"
 | 
			
		||||
            
 | 
			
		||||
        case .getContentRanking:
 | 
			
		||||
            return  "/audio-content/ranking"
 | 
			
		||||
            
 | 
			
		||||
        case .getContentRankingSortType:
 | 
			
		||||
            return  "/audio-content/ranking-sort-type"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    var method: Moya.Method {
 | 
			
		||||
        switch self {
 | 
			
		||||
        case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList, .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme:
 | 
			
		||||
        case .getAudioContentList, .getAudioContentDetail, .getOrderList, .getAudioContentThemeList,
 | 
			
		||||
                .getAudioContentCommentList, .getAudioContentCommentReplyList, .getMain, .getNewContentOfTheme,
 | 
			
		||||
                .getNewContentThemeList, .getNewContentAllOfTheme, .getAudioContentListByCurationId, .getContentRanking,
 | 
			
		||||
                .getContentRankingSortType:
 | 
			
		||||
            return .get
 | 
			
		||||
            
 | 
			
		||||
        case .likeContent, .modifyAudioContent:
 | 
			
		||||
        case .likeContent, .modifyAudioContent, .modifyComment:
 | 
			
		||||
            return .put
 | 
			
		||||
            
 | 
			
		||||
        case .registerComment, .orderAudioContent, .addAllPlaybackTracking, .uploadAudioContent, .donation:
 | 
			
		||||
@@ -173,6 +200,42 @@ extension ContentApi: TargetType {
 | 
			
		||||
            
 | 
			
		||||
        case .donation(let request):
 | 
			
		||||
            return .requestJSONEncodable(request)
 | 
			
		||||
            
 | 
			
		||||
        case .modifyComment(let request):
 | 
			
		||||
            return .requestJSONEncodable(request)
 | 
			
		||||
            
 | 
			
		||||
        case .getNewContentThemeList:
 | 
			
		||||
            return .requestPlain
 | 
			
		||||
            
 | 
			
		||||
        case .getNewContentAllOfTheme(let theme, let page, let size):
 | 
			
		||||
            let parameters = [
 | 
			
		||||
                "theme": theme,
 | 
			
		||||
                "page": page - 1,
 | 
			
		||||
                "size": size
 | 
			
		||||
            ] as [String : Any]
 | 
			
		||||
            
 | 
			
		||||
            return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
 | 
			
		||||
            
 | 
			
		||||
        case .getAudioContentListByCurationId(_, let page, let size, let sort):
 | 
			
		||||
            let parameters = [
 | 
			
		||||
                "page": page - 1,
 | 
			
		||||
                "size": size,
 | 
			
		||||
                "sort-type": sort
 | 
			
		||||
            ] as [String : Any]
 | 
			
		||||
            
 | 
			
		||||
            return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
 | 
			
		||||
            
 | 
			
		||||
        case .getContentRanking(let page, let size, let sortType):
 | 
			
		||||
            let parameters = [
 | 
			
		||||
                "page": page - 1,
 | 
			
		||||
                "size": size,
 | 
			
		||||
                "sort-type": sortType
 | 
			
		||||
            ] as [String : Any]
 | 
			
		||||
            
 | 
			
		||||
            return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
 | 
			
		||||
            
 | 
			
		||||
        case .getContentRankingSortType:
 | 
			
		||||
            return .requestPlain
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ struct ContentListItemView: View {
 | 
			
		||||
                    
 | 
			
		||||
                    HStack(spacing: 13.3) {
 | 
			
		||||
                        HStack(spacing: 6) {
 | 
			
		||||
                            Image("ic_heart")
 | 
			
		||||
                            Image("ic_heart_777")
 | 
			
		||||
                                .resizable()
 | 
			
		||||
                                .frame(width: 13.3, height: 13.3)
 | 
			
		||||
                            
 | 
			
		||||
@@ -77,7 +77,7 @@ struct ContentListItemView: View {
 | 
			
		||||
                
 | 
			
		||||
                if item.price > 0 {
 | 
			
		||||
                    HStack(spacing: 8) {
 | 
			
		||||
                        Image("ic_coin_w")
 | 
			
		||||
                        Image("ic_can")
 | 
			
		||||
                            .resizable()
 | 
			
		||||
                            .frame(width: 17, height: 17)
 | 
			
		||||
                        
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,8 @@ final class ContentRepository {
 | 
			
		||||
        return api.requestPublisher(.registerComment(request: RegisterAudioContentCommentRequest(comment: comment, contentId: audioContentId, parentId: parentId)))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func orderAudioContent(audioContentId: Int, orderType: OrderType) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.orderAudioContent(request: OrderRequest(audioContentId: audioContentId, orderType: orderType)))
 | 
			
		||||
    func orderAudioContent(contentId: Int, orderType: OrderType) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.orderAudioContent(request: OrderRequest(contentId: contentId, orderType: orderType)))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getOrderList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
@@ -74,6 +74,30 @@ final class ContentRepository {
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func donation(contentId: Int, can: Int, comment: String) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.donation(request: AudioContentDonationRequest(audioContentId: contentId, donationCan: can, comment: comment)))
 | 
			
		||||
        return api.requestPublisher(.donation(request: AudioContentDonationRequest(contentId: contentId, donationCan: can, comment: comment)))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func modifyComment(request: ModifyCommentRequest) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.modifyComment(request: request))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getNewContentThemeList() -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.getNewContentThemeList)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getNewContentAllOfTheme(theme: String, page: Int, size: Int) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.getNewContentAllOfTheme(theme: theme, page: page, size: size))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort) -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.getAudioContentListByCurationId(curationId: curationId, page: page, size: size, sort: sort))
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getContentRankingSortType() -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.getContentRankingSortType)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> {
 | 
			
		||||
        return api.requestPublisher(.getContentRanking(page: page, size: size, sortType: sortType))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -194,7 +194,7 @@ struct ContentCreateView: View {
 | 
			
		||||
                                .padding(.top, 26.7)
 | 
			
		||||
                            
 | 
			
		||||
                            VStack(spacing: 13.3) {
 | 
			
		||||
                                Text("가격설정")
 | 
			
		||||
                                Text("가격 설정")
 | 
			
		||||
                                    .font(.custom(Font.bold.rawValue, size: 16.7))
 | 
			
		||||
                                    .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                                    .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
@@ -214,14 +214,36 @@ struct ContentCreateView: View {
 | 
			
		||||
                                }
 | 
			
		||||
                                
 | 
			
		||||
                                if !viewModel.isFree {
 | 
			
		||||
                                    VStack(spacing: 13.3) {
 | 
			
		||||
                                        Text("소장 설정")
 | 
			
		||||
                                            .font(.custom(Font.bold.rawValue, size: 16.7))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                                            .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                        
 | 
			
		||||
                                        HStack(spacing: 13.3) {
 | 
			
		||||
                                            SelectButtonView(title: "소장/대여", isChecked: !viewModel.isOnlyRental) {
 | 
			
		||||
                                                if viewModel.isOnlyRental {
 | 
			
		||||
                                                    viewModel.isOnlyRental = false
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                            
 | 
			
		||||
                                            SelectButtonView(title: "대여만", isChecked: viewModel.isOnlyRental) {
 | 
			
		||||
                                                if !viewModel.isOnlyRental {
 | 
			
		||||
                                                    viewModel.isOnlyRental = true
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    .padding(.top, 13.3)
 | 
			
		||||
                                    
 | 
			
		||||
                                    VStack(spacing: 0) {
 | 
			
		||||
                                        Text("소장가격")
 | 
			
		||||
                                        Text(viewModel.isOnlyRental ? "대여 가격" : "소장 가격")
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "d2d2d2"))
 | 
			
		||||
                                            .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                        
 | 
			
		||||
                                        HStack(spacing: 0) {
 | 
			
		||||
                                            TextField("가격을 입력하세요(10캔 이상)", text: $viewModel.priceString)
 | 
			
		||||
                                            TextField("가격을 입력하세요(5캔 이상)", text: $viewModel.priceString)
 | 
			
		||||
                                                .autocapitalization(.none)
 | 
			
		||||
                                                .disableAutocorrection(true)
 | 
			
		||||
                                                .font(.custom(Font.bold.rawValue, size: 14.7))
 | 
			
		||||
@@ -247,18 +269,18 @@ struct ContentCreateView: View {
 | 
			
		||||
                                            .frame(height: 1)
 | 
			
		||||
                                            .padding(.top, 11)
 | 
			
		||||
                                        
 | 
			
		||||
                                        Text("※ 이용기간 대여 (7일) | 소장 (서비스종료시까지)")
 | 
			
		||||
                                        Text("※ 이용기간 대여 (15일) | 소장 (서비스종료시까지)")
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                            .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                            .padding(.top, 13.3)
 | 
			
		||||
                                        
 | 
			
		||||
                                        Text("※ 대여가격은 소장가격의 70%로 자동 반영")
 | 
			
		||||
                                        Text("※ 대여가격은 소장가격의 60%로 자동 반영")
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                            .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                        
 | 
			
		||||
                                        Text("※ 콘텐츠의 최소금액은 10캔 입니다")
 | 
			
		||||
                                        Text("※ 콘텐츠의 최소금액은 5캔 입니다")
 | 
			
		||||
                                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                            .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                            .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
@@ -321,6 +343,63 @@ struct ContentCreateView: View {
 | 
			
		||||
                            .padding(.top, 26.7)
 | 
			
		||||
                            .padding(.horizontal, 13.3)
 | 
			
		||||
                            
 | 
			
		||||
                            if !viewModel.isFree {
 | 
			
		||||
                                VStack(spacing: 10) {
 | 
			
		||||
                                    Text("미리듣기 시간 설정")
 | 
			
		||||
                                        .font(.custom(Font.bold.rawValue, size: 16.7))
 | 
			
		||||
                                        .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                                        .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                    
 | 
			
		||||
                                    Text("미리듣기 시간을 직접 설정하지 않으면 콘텐츠 앞부분 30초가 자동으로 설정됩니다. 미리듣기의 시간제한은 없습니다.")
 | 
			
		||||
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                        .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                        .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                    
 | 
			
		||||
                                    HStack(spacing: 13.3) {
 | 
			
		||||
                                        VStack(spacing: 5.3) {
 | 
			
		||||
                                            Text("시작 시간")
 | 
			
		||||
                                                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                                .foregroundColor(Color(hex: "d2d2d2"))
 | 
			
		||||
                                                .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                            
 | 
			
		||||
                                            TextField("00:00:00", text: $viewModel.previewStartTime)
 | 
			
		||||
                                                .autocapitalization(.none)
 | 
			
		||||
                                                .disableAutocorrection(true)
 | 
			
		||||
                                                .font(.custom(Font.bold.rawValue, size: 14.6))
 | 
			
		||||
                                                .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                                .padding(.vertical, 16.7)
 | 
			
		||||
                                                .padding(.horizontal, 13.3)
 | 
			
		||||
                                                .background(Color(hex: "222222"))
 | 
			
		||||
                                                .cornerRadius(6.7)
 | 
			
		||||
                                                .keyboardType(.default)
 | 
			
		||||
                                                .multilineTextAlignment(.center)
 | 
			
		||||
                                        }
 | 
			
		||||
                                        
 | 
			
		||||
                                        VStack(spacing: 5.3) {
 | 
			
		||||
                                            Text("종료 시간")
 | 
			
		||||
                                                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                                .foregroundColor(Color(hex: "d2d2d2"))
 | 
			
		||||
                                                .frame(maxWidth: .infinity, alignment: .leading)
 | 
			
		||||
                                            
 | 
			
		||||
                                            TextField("00:00:30", text: $viewModel.previewEndTime)
 | 
			
		||||
                                                .autocapitalization(.none)
 | 
			
		||||
                                                .disableAutocorrection(true)
 | 
			
		||||
                                                .font(.custom(Font.bold.rawValue, size: 14.6))
 | 
			
		||||
                                                .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                                .padding(.vertical, 16.7)
 | 
			
		||||
                                                .padding(.horizontal, 13.3)
 | 
			
		||||
                                                .background(Color(hex: "222222"))
 | 
			
		||||
                                                .cornerRadius(6.7)
 | 
			
		||||
                                                .keyboardType(.default)
 | 
			
		||||
                                                .multilineTextAlignment(.center)
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                    .padding(.top, 3.3)
 | 
			
		||||
                                }
 | 
			
		||||
                                .padding(.top, 26.7)
 | 
			
		||||
                                .padding(.horizontal, 13.3)
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            VStack(spacing: 0) {
 | 
			
		||||
                                HStack(alignment: .top, spacing: 0) {
 | 
			
		||||
                                    Text("등록")
 | 
			
		||||
 
 | 
			
		||||
@@ -54,10 +54,16 @@ final class ContentCreateViewModel: ObservableObject {
 | 
			
		||||
        didSet {
 | 
			
		||||
            if isFree {
 | 
			
		||||
                priceString = "0"
 | 
			
		||||
                isOnlyRental = false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Published var isOnlyRental = false
 | 
			
		||||
    
 | 
			
		||||
    @Published var previewStartTime: String = ""
 | 
			
		||||
    @Published var previewEndTime: String = ""
 | 
			
		||||
    
 | 
			
		||||
    var placeholder = "내용을 입력하세요"
 | 
			
		||||
    
 | 
			
		||||
    func uploadAudioContent() {
 | 
			
		||||
@@ -71,7 +77,10 @@ final class ContentCreateViewModel: ObservableObject {
 | 
			
		||||
                price: price,
 | 
			
		||||
                themeId: theme!.id,
 | 
			
		||||
                isAdult: isAdult,
 | 
			
		||||
                isCommentAvailable: isAvailableComment
 | 
			
		||||
                isOnlyRental: isOnlyRental,
 | 
			
		||||
                isCommentAvailable: isAvailableComment,
 | 
			
		||||
                previewStartTime: previewStartTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewStartTime : nil,
 | 
			
		||||
                previewEndTime: previewEndTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewEndTime : nil
 | 
			
		||||
            )
 | 
			
		||||
            
 | 
			
		||||
            var multipartData = [MultipartFormData]()
 | 
			
		||||
@@ -205,12 +214,68 @@ final class ContentCreateViewModel: ObservableObject {
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if !isFree && price < 10 {
 | 
			
		||||
            errorMessage = "콘텐츠의 최소금액은 10캔 입니다."
 | 
			
		||||
        if !isFree && price < 5 {
 | 
			
		||||
            errorMessage = "콘텐츠의 최소금액은 5캔 입니다."
 | 
			
		||||
            isShowPopup = true
 | 
			
		||||
            return false
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if previewStartTime.count > 0 && previewEndTime.count > 0 {
 | 
			
		||||
            let startTimeArray = previewStartTime.split(separator: ":")
 | 
			
		||||
            if startTimeArray.count != 3 {
 | 
			
		||||
                errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
 | 
			
		||||
                isShowPopup = true
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            for time in startTimeArray {
 | 
			
		||||
                if time.count != 2 {
 | 
			
		||||
                    errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
 | 
			
		||||
                    isShowPopup = true
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let endTimeArray = previewStartTime.split(separator: ":")
 | 
			
		||||
            if endTimeArray.count != 3 {
 | 
			
		||||
                errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
 | 
			
		||||
                isShowPopup = true
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            for time in endTimeArray {
 | 
			
		||||
                if time.count != 2 {
 | 
			
		||||
                    errorMessage = "미리 듣기 시간 형식은 00:30:00 과 같아야 합니다"
 | 
			
		||||
                    isShowPopup = true
 | 
			
		||||
                    return false
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            let timeDifference = timeDifference(startTime: previewStartTime, endTime: previewEndTime)
 | 
			
		||||
            if timeDifference < 30.0 {
 | 
			
		||||
                errorMessage = "미리 듣기의 최소 시간은 30초 입니다"
 | 
			
		||||
                isShowPopup = true
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if previewStartTime.count > 0 || previewEndTime.count > 0 {
 | 
			
		||||
                errorMessage = "미리 듣기 시작 시간과 종료 시간 둘 다 입력을 하거나 둘 다 입력 하지 않아야 합니다."
 | 
			
		||||
                isShowPopup = true
 | 
			
		||||
                return false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private func timeDifference(startTime: String, endTime: String) -> Double {
 | 
			
		||||
        let dateFormatter = DateFormatter()
 | 
			
		||||
        dateFormatter.dateFormat = "HH:mm:ss"
 | 
			
		||||
        
 | 
			
		||||
        if let date1 = dateFormatter.date(from: startTime), let date2 = dateFormatter.date(from: endTime) {
 | 
			
		||||
            return date2.timeIntervalSince(date1)
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return 0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,5 +14,8 @@ struct CreateAudioContentRequest: Encodable {
 | 
			
		||||
    let price: Int
 | 
			
		||||
    let themeId: Int
 | 
			
		||||
    let isAdult: Bool
 | 
			
		||||
    let isOnlyRental: Bool
 | 
			
		||||
    let isCommentAvailable: Bool
 | 
			
		||||
    let previewStartTime: String?
 | 
			
		||||
    let previewEndTime: String?
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										110
									
								
								SodaLive/Sources/Content/Curation/ContentCurationView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,110 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentCurationView.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ContentCurationView: View {
 | 
			
		||||
    
 | 
			
		||||
    @StateObject var viewModel = ContentCurationViewModel()
 | 
			
		||||
    
 | 
			
		||||
    let title: String
 | 
			
		||||
    let curationId: Int
 | 
			
		||||
    
 | 
			
		||||
    let columns = [
 | 
			
		||||
        GridItem(.flexible(), alignment: .top),
 | 
			
		||||
        GridItem(.flexible(), alignment: .top)
 | 
			
		||||
    ]
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        BaseView(isLoading: $viewModel.isLoading) {
 | 
			
		||||
            VStack(spacing: 0) {
 | 
			
		||||
                DetailNavigationBar(title: title)
 | 
			
		||||
                
 | 
			
		||||
                HStack(spacing: 13.3) {
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                    
 | 
			
		||||
                    Text("최신순")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(
 | 
			
		||||
                            Color(hex: "e2e2e2")
 | 
			
		||||
                                .opacity(viewModel.sort == .NEWEST ? 1 : 0.5)
 | 
			
		||||
                        )
 | 
			
		||||
                        .onTapGesture {
 | 
			
		||||
                            if viewModel.sort != .NEWEST {
 | 
			
		||||
                                viewModel.sort = .NEWEST
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    
 | 
			
		||||
                    Text("높은 가격순")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(
 | 
			
		||||
                            Color(hex: "e2e2e2")
 | 
			
		||||
                                .opacity(viewModel.sort == .PRICE_HIGH ? 1 : 0.5)
 | 
			
		||||
                        )
 | 
			
		||||
                        .onTapGesture {
 | 
			
		||||
                            if viewModel.sort != .PRICE_HIGH {
 | 
			
		||||
                                viewModel.sort = .PRICE_HIGH
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    
 | 
			
		||||
                    Text("낮은 가격순")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(
 | 
			
		||||
                            Color(hex: "e2e2e2")
 | 
			
		||||
                                .opacity(viewModel.sort == .PRICE_LOW ? 1 : 0.5)
 | 
			
		||||
                        )
 | 
			
		||||
                        .onTapGesture {
 | 
			
		||||
                            if viewModel.sort != .PRICE_LOW {
 | 
			
		||||
                                viewModel.sort = .PRICE_LOW
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                }
 | 
			
		||||
                .padding(.vertical, 13.3)
 | 
			
		||||
                .padding(.horizontal, 20)
 | 
			
		||||
                .background(Color(hex: "161616"))
 | 
			
		||||
                .padding(.top, 13.3)
 | 
			
		||||
                
 | 
			
		||||
                HStack(spacing: 0) {
 | 
			
		||||
                    Text("전체")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "e2e2e2"))
 | 
			
		||||
                    
 | 
			
		||||
                    Text("\(viewModel.totalCount)")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "ff5c49"))
 | 
			
		||||
                        .padding(.leading, 8)
 | 
			
		||||
                    
 | 
			
		||||
                    Text("개")
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "e2e2e2"))
 | 
			
		||||
                        .padding(.leading, 2)
 | 
			
		||||
                    
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
                .padding(.vertical, 13.3)
 | 
			
		||||
                .padding(.horizontal, 20)
 | 
			
		||||
                
 | 
			
		||||
                ScrollView(.vertical, showsIndicators: false) {
 | 
			
		||||
                    LazyVGrid(columns: columns, spacing: 13.3) {
 | 
			
		||||
                        ForEach(0..<viewModel.contentList.count, id: \.self) { index in
 | 
			
		||||
                            ContentNewAllItemView(item: viewModel.contentList[index])
 | 
			
		||||
                                .onAppear {
 | 
			
		||||
                                    if index == viewModel.contentList.count - 1 {
 | 
			
		||||
                                        viewModel.getContentList()
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .onAppear {
 | 
			
		||||
                viewModel.curationId = curationId
 | 
			
		||||
                viewModel.getContentList()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
//
 | 
			
		||||
//  ContentCurationViewModel.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
import Combine
 | 
			
		||||
 | 
			
		||||
final class ContentCurationViewModel: ObservableObject {
 | 
			
		||||
    
 | 
			
		||||
    enum Sort: String {
 | 
			
		||||
        case NEWEST, PRICE_HIGH, PRICE_LOW
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private let repository = ContentRepository()
 | 
			
		||||
    private var subscription = Set<AnyCancellable>()
 | 
			
		||||
    
 | 
			
		||||
    @Published var errorMessage = ""
 | 
			
		||||
    @Published var isShowPopup = false
 | 
			
		||||
    @Published var isLoading = false
 | 
			
		||||
    
 | 
			
		||||
    @Published var totalCount = 0
 | 
			
		||||
    @Published var sort = Sort.NEWEST {
 | 
			
		||||
        didSet {
 | 
			
		||||
            page = 1
 | 
			
		||||
            isLast = false
 | 
			
		||||
            getContentList()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    @Published var contentList: [GetAudioContentMainItem] = []
 | 
			
		||||
    
 | 
			
		||||
    var curationId = 0
 | 
			
		||||
    var page = 1
 | 
			
		||||
    var isLast = false
 | 
			
		||||
    private let pageSize = 10
 | 
			
		||||
    
 | 
			
		||||
    func getContentList() {
 | 
			
		||||
        if (!isLast && !isLoading) {
 | 
			
		||||
            isLoading = true
 | 
			
		||||
            
 | 
			
		||||
            repository.getAudioContentListByCurationId(
 | 
			
		||||
                curationId: curationId,
 | 
			
		||||
                page: page,
 | 
			
		||||
                size: pageSize,
 | 
			
		||||
                sort: sort
 | 
			
		||||
            )
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
                    DEBUG_LOG("finish")
 | 
			
		||||
                case .failure(let error):
 | 
			
		||||
                    ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                }
 | 
			
		||||
            } receiveValue: { [unowned self] response in
 | 
			
		||||
                let responseData = response.data
 | 
			
		||||
                
 | 
			
		||||
                do {
 | 
			
		||||
                    let jsonDecoder = JSONDecoder()
 | 
			
		||||
                    let decoded = try jsonDecoder.decode(ApiResponse<GetCurationContentResponse>.self, from: responseData)
 | 
			
		||||
                    
 | 
			
		||||
                    if let data = decoded.data, decoded.success {
 | 
			
		||||
                        if page == 1 {
 | 
			
		||||
                            self.contentList.removeAll()
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        if !data.items.isEmpty {
 | 
			
		||||
                            page += 1
 | 
			
		||||
                            self.totalCount = data.totalCount
 | 
			
		||||
                            self.contentList.append(contentsOf: data.items)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            isLast = true
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if let message = decoded.message {
 | 
			
		||||
                            self.errorMessage = message
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                    }
 | 
			
		||||
                } catch {
 | 
			
		||||
                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                    self.isShowPopup = true
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                self.isLoading = false
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
//
 | 
			
		||||
//  GetCurationContentResponse.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/27.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
struct GetCurationContentResponse: Decodable {
 | 
			
		||||
    let totalCount: Int
 | 
			
		||||
    let items: [GetAudioContentMainItem]
 | 
			
		||||
}
 | 
			
		||||
@@ -9,81 +9,168 @@ import SwiftUI
 | 
			
		||||
import Kingfisher
 | 
			
		||||
 | 
			
		||||
struct AudioContentCommentItemView: View {
 | 
			
		||||
    
 | 
			
		||||
    let comment: GetAudioContentCommentListItem
 | 
			
		||||
    let contentCreatorId: Int
 | 
			
		||||
    let audioContentId: Int
 | 
			
		||||
    let commentItem: GetAudioContentCommentListItem
 | 
			
		||||
    let isReplyComment: Bool
 | 
			
		||||
    let isShowPopupMenuButton: Bool
 | 
			
		||||
    
 | 
			
		||||
    let modifyComment: (Int, String) -> Void
 | 
			
		||||
    let onClickDelete: (Int) -> Void
 | 
			
		||||
    
 | 
			
		||||
    @State var isShowPopupMenu: Bool = false
 | 
			
		||||
    @State var isModeModify: Bool = false
 | 
			
		||||
    @State var comment: String = ""
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
            HStack(spacing: 6.7) {
 | 
			
		||||
                KFImage(URL(string: comment.profileUrl))
 | 
			
		||||
                    .resizable()
 | 
			
		||||
                    .frame(width: 40, height: 40)
 | 
			
		||||
                    .clipShape(Circle())
 | 
			
		||||
                
 | 
			
		||||
                VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
                    Text(comment.nickname)
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                    
 | 
			
		||||
                    Text(comment.date)
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 10.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "525252"))
 | 
			
		||||
                        .padding(.top, 4)
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if comment.donationCan > 0 {
 | 
			
		||||
                HStack(spacing: 3) {
 | 
			
		||||
                    Image("ic_can")
 | 
			
		||||
        ZStack(alignment: .topTrailing) {
 | 
			
		||||
            VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
                HStack(spacing: 6.7) {
 | 
			
		||||
                    KFImage(URL(string: commentItem.profileUrl))
 | 
			
		||||
                        .resizable()
 | 
			
		||||
                        .frame(width: 13.3, height: 13.3)
 | 
			
		||||
                        .frame(width: 40, height: 40)
 | 
			
		||||
                        .clipShape(Circle())
 | 
			
		||||
                    
 | 
			
		||||
                    Text("\(comment.donationCan)")
 | 
			
		||||
                        .font(.custom(Font.bold.rawValue, size: 12))
 | 
			
		||||
                        .foregroundColor(.white)
 | 
			
		||||
                }
 | 
			
		||||
                .padding(.horizontal, 6.7)
 | 
			
		||||
                .padding(.vertical, 2.7)
 | 
			
		||||
                .background(
 | 
			
		||||
                    comment.donationCan >= 100000 ? Color(hex: "973a3a") :
 | 
			
		||||
                        comment.donationCan >= 50000 ? Color(hex: "d85e37") :
 | 
			
		||||
                        comment.donationCan >= 10000 ? Color(hex: "d38c38") :
 | 
			
		||||
                        comment.donationCan >= 5000 ? Color(hex: "59548f") :
 | 
			
		||||
                        comment.donationCan >= 1000 ? Color(hex: "4d6aa4") :
 | 
			
		||||
                        comment.donationCan >= 500 ? Color(hex: "2d7390") :
 | 
			
		||||
                        Color(hex: "548f7d")
 | 
			
		||||
                )
 | 
			
		||||
                .cornerRadius(10.7)
 | 
			
		||||
                .padding(.leading, 46.7)
 | 
			
		||||
                .padding(.bottom, 5)
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            HStack(spacing: 0) {
 | 
			
		||||
                VStack(alignment: .leading, spacing: 13.3) {
 | 
			
		||||
                    Text(comment.comment)
 | 
			
		||||
                        .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                        .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                        .fixedSize(horizontal: false, vertical: true)
 | 
			
		||||
                        .padding(.top, comment.donationCan > 0 ? 0 : 13.3)
 | 
			
		||||
                    
 | 
			
		||||
                    if !isReplyComment {
 | 
			
		||||
                        Text(comment.replyCount > 0 ? "답글 \(comment.replyCount)개" : "답글 쓰기")
 | 
			
		||||
                    VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
                        Text(commentItem.nickname)
 | 
			
		||||
                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                            .foregroundColor(Color(hex: "9970ff"))
 | 
			
		||||
                            .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                        
 | 
			
		||||
                        Text(commentItem.date)
 | 
			
		||||
                            .font(.custom(Font.medium.rawValue, size: 10.3))
 | 
			
		||||
                            .foregroundColor(Color(hex: "525252"))
 | 
			
		||||
                            .padding(.top, 4)
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                    
 | 
			
		||||
                    if isShowPopupMenuButton && (contentCreatorId == UserDefaults.int(forKey: .userId) || commentItem.writerId == UserDefaults.int(forKey: .userId)) {
 | 
			
		||||
                        Image("ic_seemore_vertical")
 | 
			
		||||
                            .onTapGesture { isShowPopupMenu.toggle() }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
                if commentItem.donationCan > 0 {
 | 
			
		||||
                    HStack(spacing: 3) {
 | 
			
		||||
                        Image("ic_can")
 | 
			
		||||
                            .resizable()
 | 
			
		||||
                            .frame(width: 13.3, height: 13.3)
 | 
			
		||||
                        
 | 
			
		||||
                        Text("\(commentItem.donationCan)")
 | 
			
		||||
                            .font(.custom(Font.bold.rawValue, size: 12))
 | 
			
		||||
                            .foregroundColor(.white)
 | 
			
		||||
                    }
 | 
			
		||||
                    .padding(.horizontal, 6.7)
 | 
			
		||||
                    .padding(.vertical, 2.7)
 | 
			
		||||
                    .background(
 | 
			
		||||
                        commentItem.donationCan >= 100000 ? Color(hex: "973a3a") :
 | 
			
		||||
                            commentItem.donationCan >= 50000 ? Color(hex: "d85e37") :
 | 
			
		||||
                            commentItem.donationCan >= 10000 ? Color(hex: "d38c38") :
 | 
			
		||||
                            commentItem.donationCan >= 5000 ? Color(hex: "59548f") :
 | 
			
		||||
                            commentItem.donationCan >= 1000 ? Color(hex: "4d6aa4") :
 | 
			
		||||
                            commentItem.donationCan >= 500 ? Color(hex: "2d7390") :
 | 
			
		||||
                            Color(hex: "548f7d")
 | 
			
		||||
                    )
 | 
			
		||||
                    .cornerRadius(10.7)
 | 
			
		||||
                    .padding(.leading, 46.7)
 | 
			
		||||
                    .padding(.bottom, 5)
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                HStack(spacing: 0) {
 | 
			
		||||
                    if isModeModify {
 | 
			
		||||
                        HStack(spacing: 0) {
 | 
			
		||||
                            TextField("댓글을 입력해 보세요.", text: $comment)
 | 
			
		||||
                                .autocapitalization(.none)
 | 
			
		||||
                                .disableAutocorrection(true)
 | 
			
		||||
                                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                                .accentColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                                .keyboardType(.default)
 | 
			
		||||
                                .padding(.horizontal, 13.3)
 | 
			
		||||
                            
 | 
			
		||||
                            Spacer()
 | 
			
		||||
                            
 | 
			
		||||
                            Image("btn_message_send")
 | 
			
		||||
                                .resizable()
 | 
			
		||||
                                .frame(width: 35, height: 35)
 | 
			
		||||
                                .padding(6.7)
 | 
			
		||||
                                .onTapGesture {
 | 
			
		||||
                                    hideKeyboard()
 | 
			
		||||
                                    if commentItem.comment != comment {
 | 
			
		||||
                                        modifyComment(commentItem.id, comment)
 | 
			
		||||
                                    }
 | 
			
		||||
                                    isModeModify = false
 | 
			
		||||
                                }
 | 
			
		||||
                        }
 | 
			
		||||
                        .background(Color(hex: "232323"))
 | 
			
		||||
                        .cornerRadius(10)
 | 
			
		||||
                        .overlay(
 | 
			
		||||
                            RoundedRectangle(cornerRadius: 10)
 | 
			
		||||
                                .strokeBorder(lineWidth: 1)
 | 
			
		||||
                                .foregroundColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                        )
 | 
			
		||||
                    } else {
 | 
			
		||||
                        VStack(alignment: .leading, spacing: 13.3) {
 | 
			
		||||
                            Text(commentItem.comment)
 | 
			
		||||
                                .font(.custom(Font.medium.rawValue, size: 12))
 | 
			
		||||
                                .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                                .fixedSize(horizontal: false, vertical: true)
 | 
			
		||||
                                .padding(.top, commentItem.donationCan > 0 ? 0 : 13.3)
 | 
			
		||||
                            
 | 
			
		||||
                            if !isReplyComment {
 | 
			
		||||
                                NavigationLink(
 | 
			
		||||
                                    destination: AudioContentListReplyView(
 | 
			
		||||
                                        creatorId: contentCreatorId,
 | 
			
		||||
                                        audioContentId: audioContentId,
 | 
			
		||||
                                        parentComment: commentItem
 | 
			
		||||
                                    )
 | 
			
		||||
                                ) {
 | 
			
		||||
                                    Text(commentItem.replyCount > 0 ? "답글 \(commentItem.replyCount)개" : "답글 쓰기")
 | 
			
		||||
                                        .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                        .foregroundColor(Color(hex: "9970ff"))
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
                .padding(.leading, 46.7)
 | 
			
		||||
                
 | 
			
		||||
                Rectangle()
 | 
			
		||||
                    .foregroundColor(Color(hex: "595959"))
 | 
			
		||||
                    .frame(height: 0.5)
 | 
			
		||||
                    .padding(.top, 16.7)
 | 
			
		||||
            }
 | 
			
		||||
            .padding(.leading, 46.7)
 | 
			
		||||
            
 | 
			
		||||
            Rectangle()
 | 
			
		||||
                .foregroundColor(Color(hex: "595959"))
 | 
			
		||||
                .frame(height: 0.5)
 | 
			
		||||
                .padding(.top, 16.7)
 | 
			
		||||
            if isShowPopupMenu {
 | 
			
		||||
                VStack(spacing: 10) {                    
 | 
			
		||||
                    if commentItem.writerId == UserDefaults.int(forKey: .userId) {
 | 
			
		||||
                        Text("수정")
 | 
			
		||||
                            .font(.custom(Font.medium.rawValue, size: 14))
 | 
			
		||||
                            .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                            .onTapGesture {
 | 
			
		||||
                                isModeModify = true
 | 
			
		||||
                                isShowPopupMenu = false
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    if contentCreatorId == UserDefaults.int(forKey: .userId) ||
 | 
			
		||||
                        commentItem.writerId == UserDefaults.int(forKey: .userId)
 | 
			
		||||
                    {
 | 
			
		||||
                        Text("삭제")
 | 
			
		||||
                            .font(.custom(Font.medium.rawValue, size: 14))
 | 
			
		||||
                            .foregroundColor(Color(hex: "777777"))
 | 
			
		||||
                            .onTapGesture {
 | 
			
		||||
                                onClickDelete(commentItem.id)
 | 
			
		||||
                                isShowPopupMenu = false
 | 
			
		||||
                            }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                .padding(10)
 | 
			
		||||
                .background(Color(hex: "222222"))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .onAppear { comment = commentItem.comment }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,15 @@ import Kingfisher
 | 
			
		||||
struct AudioContentCommentListView: View {
 | 
			
		||||
    
 | 
			
		||||
    @Binding var isPresented: Bool
 | 
			
		||||
    
 | 
			
		||||
    let creatorId: Int
 | 
			
		||||
    let audioContentId: Int
 | 
			
		||||
    
 | 
			
		||||
    @StateObject var viewModel = AudioContentCommentListViewModel()
 | 
			
		||||
    
 | 
			
		||||
    @State private var commentId: Int = 0
 | 
			
		||||
    @State private var isShowDeletePopup: Bool = false
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        NavigationView {
 | 
			
		||||
            ZStack {
 | 
			
		||||
@@ -59,7 +64,7 @@ struct AudioContentCommentListView: View {
 | 
			
		||||
                                .disableAutocorrection(true)
 | 
			
		||||
                                .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                                .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                                .accentColor(Color(hex: "9970ff"))
 | 
			
		||||
                                .accentColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                                .keyboardType(.default)
 | 
			
		||||
                                .padding(.horizontal, 13.3)
 | 
			
		||||
                            
 | 
			
		||||
@@ -79,7 +84,7 @@ struct AudioContentCommentListView: View {
 | 
			
		||||
                        .overlay(
 | 
			
		||||
                            RoundedRectangle(cornerRadius: 10)
 | 
			
		||||
                                .strokeBorder(lineWidth: 1)
 | 
			
		||||
                                .foregroundColor(Color(hex: "9970ff"))
 | 
			
		||||
                                .foregroundColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                        )
 | 
			
		||||
                        
 | 
			
		||||
                        Spacer()
 | 
			
		||||
@@ -97,13 +102,22 @@ struct AudioContentCommentListView: View {
 | 
			
		||||
                        LazyVStack(spacing: 13.3) {
 | 
			
		||||
                            ForEach(0..<viewModel.commentList.count, id: \.self) { index in
 | 
			
		||||
                                let comment = viewModel.commentList[index]
 | 
			
		||||
                                NavigationLink {
 | 
			
		||||
                                    AudioContentListReplyView(
 | 
			
		||||
                                VStack {
 | 
			
		||||
                                    AudioContentCommentItemView(
 | 
			
		||||
                                        contentCreatorId: creatorId,
 | 
			
		||||
                                        audioContentId: audioContentId,
 | 
			
		||||
                                        parentComment: comment
 | 
			
		||||
                                        commentItem: comment,
 | 
			
		||||
                                        isReplyComment: false,
 | 
			
		||||
                                        isShowPopupMenuButton: true,
 | 
			
		||||
                                        modifyComment: { commentId, comment in
 | 
			
		||||
                                            hideKeyboard()
 | 
			
		||||
                                            viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, comment: comment)
 | 
			
		||||
                                        },
 | 
			
		||||
                                        onClickDelete: {
 | 
			
		||||
                                            commentId = $0
 | 
			
		||||
                                            isShowDeletePopup = true
 | 
			
		||||
                                        }
 | 
			
		||||
                                    )
 | 
			
		||||
                                } label: {
 | 
			
		||||
                                    AudioContentCommentItemView(comment: comment, isReplyComment: false)
 | 
			
		||||
                                    .padding(.horizontal, 26.7)
 | 
			
		||||
                                    .onAppear {
 | 
			
		||||
                                        if index == viewModel.commentList.count - 1 {
 | 
			
		||||
@@ -116,6 +130,24 @@ struct AudioContentCommentListView: View {
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if isShowDeletePopup && commentId > 0 {
 | 
			
		||||
                    SodaDialog(
 | 
			
		||||
                        title: "댓글 삭제",
 | 
			
		||||
                        desc: "삭제하시겠습니까?",
 | 
			
		||||
                        confirmButtonTitle: "삭제",
 | 
			
		||||
                        confirmButtonAction: {
 | 
			
		||||
                            viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, isActive: false)
 | 
			
		||||
                            commentId = 0
 | 
			
		||||
                            isShowDeletePopup = false
 | 
			
		||||
                        },
 | 
			
		||||
                        cancelButtonTitle: "취소",
 | 
			
		||||
                        cancelButtonAction: {
 | 
			
		||||
                            commentId = 0
 | 
			
		||||
                            isShowDeletePopup = false
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if viewModel.isLoading {
 | 
			
		||||
                    LoadingView()
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -121,4 +121,77 @@ class AudioContentCommentListViewModel: ObservableObject {
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func modifyComment(
 | 
			
		||||
        commentId: Int,
 | 
			
		||||
        audioContentId: Int,
 | 
			
		||||
        comment: String? = nil,
 | 
			
		||||
        isActive: Bool? = nil
 | 
			
		||||
    ) {
 | 
			
		||||
        if comment == nil && isActive == nil {
 | 
			
		||||
            errorMessage = "변경사항이 없습니다."
 | 
			
		||||
            isShowPopup = true
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
 | 
			
		||||
            errorMessage = "내용을 입력하세요."
 | 
			
		||||
            isShowPopup = true
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        
 | 
			
		||||
        var request = ModifyCommentRequest(commentId: commentId)
 | 
			
		||||
        
 | 
			
		||||
        if let comment = comment {
 | 
			
		||||
            request.comment = comment
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let isActive = isActive {
 | 
			
		||||
            request.isActive = isActive
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        repository.modifyComment(request: request)
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
                    DEBUG_LOG("finish")
 | 
			
		||||
                case .failure(let error):
 | 
			
		||||
                    ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                }
 | 
			
		||||
            } receiveValue: { [unowned self] response in
 | 
			
		||||
                self.isLoading = false
 | 
			
		||||
                let responseData = response.data
 | 
			
		||||
                
 | 
			
		||||
                do {
 | 
			
		||||
                    let jsonDecoder = JSONDecoder()
 | 
			
		||||
                    let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
 | 
			
		||||
                    
 | 
			
		||||
                    if decoded.success {
 | 
			
		||||
                        self.comment = ""
 | 
			
		||||
                        self.page = 1
 | 
			
		||||
                        self.isLast = false
 | 
			
		||||
                        
 | 
			
		||||
                        self.commentList.removeAll()
 | 
			
		||||
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
 | 
			
		||||
                            self.getCommentList()
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if let message = decoded.message {
 | 
			
		||||
                            self.errorMessage = message
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                    }
 | 
			
		||||
                } catch {
 | 
			
		||||
                    self.isLoading = false
 | 
			
		||||
                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                    self.isShowPopup = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,12 +10,16 @@ import Kingfisher
 | 
			
		||||
 | 
			
		||||
struct AudioContentListReplyView: View {
 | 
			
		||||
    
 | 
			
		||||
    let creatorId: Int
 | 
			
		||||
    let audioContentId: Int
 | 
			
		||||
    let parentComment: GetAudioContentCommentListItem
 | 
			
		||||
    
 | 
			
		||||
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
 | 
			
		||||
    @StateObject var viewModel = AudioContentListReplyViewModel()
 | 
			
		||||
    
 | 
			
		||||
    @State private var commentId: Int = 0
 | 
			
		||||
    @State private var isShowDeletePopup: Bool = false
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        ZStack {
 | 
			
		||||
            VStack(spacing: 0) {
 | 
			
		||||
@@ -53,7 +57,7 @@ struct AudioContentListReplyView: View {
 | 
			
		||||
                            .disableAutocorrection(true)
 | 
			
		||||
                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                            .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                            .accentColor(Color(hex: "9970ff"))
 | 
			
		||||
                            .accentColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                            .keyboardType(.default)
 | 
			
		||||
                            .padding(.horizontal, 13.3)
 | 
			
		||||
                        
 | 
			
		||||
@@ -73,7 +77,7 @@ struct AudioContentListReplyView: View {
 | 
			
		||||
                    .overlay(
 | 
			
		||||
                        RoundedRectangle(cornerRadius: 10)
 | 
			
		||||
                            .strokeBorder(lineWidth: 1)
 | 
			
		||||
                            .foregroundColor(Color(hex: "9970ff"))
 | 
			
		||||
                            .foregroundColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                    )
 | 
			
		||||
                    
 | 
			
		||||
                    Spacer()
 | 
			
		||||
@@ -87,7 +91,15 @@ struct AudioContentListReplyView: View {
 | 
			
		||||
                    .padding(.bottom, 13.3)
 | 
			
		||||
                    .padding(.horizontal, 13.3)
 | 
			
		||||
                
 | 
			
		||||
                AudioContentCommentItemView(comment: parentComment, isReplyComment: true)
 | 
			
		||||
                AudioContentCommentItemView(
 | 
			
		||||
                    contentCreatorId: creatorId,
 | 
			
		||||
                    audioContentId: audioContentId,
 | 
			
		||||
                    commentItem: parentComment,
 | 
			
		||||
                    isReplyComment: true,
 | 
			
		||||
                    isShowPopupMenuButton: false,
 | 
			
		||||
                    modifyComment: { _, _ in },
 | 
			
		||||
                    onClickDelete: { _ in }
 | 
			
		||||
                )
 | 
			
		||||
                    .padding(.horizontal, 26.7)
 | 
			
		||||
                    .padding(.bottom, 13.3)
 | 
			
		||||
                
 | 
			
		||||
@@ -95,7 +107,21 @@ struct AudioContentListReplyView: View {
 | 
			
		||||
                    LazyVStack(spacing: 13.3) {
 | 
			
		||||
                        ForEach(0..<viewModel.commentList.count, id: \.self) { index in
 | 
			
		||||
                            let comment = viewModel.commentList[index]
 | 
			
		||||
                            AudioContentCommentItemView(comment: comment, isReplyComment: true)
 | 
			
		||||
                            AudioContentCommentItemView(
 | 
			
		||||
                                contentCreatorId: creatorId,
 | 
			
		||||
                                audioContentId: audioContentId,
 | 
			
		||||
                                commentItem: comment,
 | 
			
		||||
                                isReplyComment: true,
 | 
			
		||||
                                isShowPopupMenuButton: true,
 | 
			
		||||
                                modifyComment: { commentId, comment in
 | 
			
		||||
                                    hideKeyboard()
 | 
			
		||||
                                    viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, comment: comment)
 | 
			
		||||
                                },
 | 
			
		||||
                                onClickDelete: {
 | 
			
		||||
                                    commentId = $0
 | 
			
		||||
                                    isShowDeletePopup = true
 | 
			
		||||
                                }
 | 
			
		||||
                            )
 | 
			
		||||
                                .padding(.horizontal, 40)
 | 
			
		||||
                                .onAppear {
 | 
			
		||||
                                    if index == viewModel.commentList.count - 1 {
 | 
			
		||||
@@ -108,6 +134,28 @@ struct AudioContentListReplyView: View {
 | 
			
		||||
            }
 | 
			
		||||
            .navigationTitle("")
 | 
			
		||||
            .navigationBarBackButtonHidden()
 | 
			
		||||
            
 | 
			
		||||
            if isShowDeletePopup && commentId > 0 {
 | 
			
		||||
                SodaDialog(
 | 
			
		||||
                    title: "댓글 삭제",
 | 
			
		||||
                    desc: "삭제하시겠습니까?",
 | 
			
		||||
                    confirmButtonTitle: "삭제",
 | 
			
		||||
                    confirmButtonAction: {
 | 
			
		||||
                        viewModel.modifyComment(commentId: commentId, audioContentId: audioContentId, isActive: false)
 | 
			
		||||
                        commentId = 0
 | 
			
		||||
                        isShowDeletePopup = false
 | 
			
		||||
                    },
 | 
			
		||||
                    cancelButtonTitle: "취소",
 | 
			
		||||
                    cancelButtonAction: {
 | 
			
		||||
                        commentId = 0
 | 
			
		||||
                        isShowDeletePopup = false
 | 
			
		||||
                    }
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if viewModel.isLoading {
 | 
			
		||||
                LoadingView()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            viewModel.audioContentId = audioContentId
 | 
			
		||||
 
 | 
			
		||||
@@ -121,4 +121,77 @@ final class AudioContentListReplyViewModel: ObservableObject {
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func modifyComment(
 | 
			
		||||
        commentId: Int,
 | 
			
		||||
        audioContentId: Int,
 | 
			
		||||
        comment: String? = nil,
 | 
			
		||||
        isActive: Bool? = nil
 | 
			
		||||
    ) {
 | 
			
		||||
        if comment == nil && isActive == nil {
 | 
			
		||||
            errorMessage = "변경사항이 없습니다."
 | 
			
		||||
            isShowPopup = true
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let comment = comment, comment.trimmingCharacters(in: .whitespaces).isEmpty {
 | 
			
		||||
            errorMessage = "내용을 입력하세요."
 | 
			
		||||
            isShowPopup = true
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        
 | 
			
		||||
        var request = ModifyCommentRequest(commentId: commentId)
 | 
			
		||||
        
 | 
			
		||||
        if let comment = comment {
 | 
			
		||||
            request.comment = comment
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if let isActive = isActive {
 | 
			
		||||
            request.isActive = isActive
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        repository.modifyComment(request: request)
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
                    DEBUG_LOG("finish")
 | 
			
		||||
                case .failure(let error):
 | 
			
		||||
                    ERROR_LOG(error.localizedDescription)
 | 
			
		||||
                }
 | 
			
		||||
            } receiveValue: { [unowned self] response in
 | 
			
		||||
                self.isLoading = false
 | 
			
		||||
                let responseData = response.data
 | 
			
		||||
                
 | 
			
		||||
                do {
 | 
			
		||||
                    let jsonDecoder = JSONDecoder()
 | 
			
		||||
                    let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
 | 
			
		||||
                    
 | 
			
		||||
                    if decoded.success {
 | 
			
		||||
                        self.comment = ""
 | 
			
		||||
                        self.page = 1
 | 
			
		||||
                        self.isLast = false
 | 
			
		||||
                        
 | 
			
		||||
                        self.commentList.removeAll()
 | 
			
		||||
                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
 | 
			
		||||
                            self.getCommentList()
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if let message = decoded.message {
 | 
			
		||||
                            self.errorMessage = message
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                    }
 | 
			
		||||
                } catch {
 | 
			
		||||
                    self.isLoading = false
 | 
			
		||||
                    self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                    self.isShowPopup = true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .store(in: &subscription)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ struct ContentDetailCommentView: View {
 | 
			
		||||
                            .disableAutocorrection(true)
 | 
			
		||||
                            .font(.custom(Font.medium.rawValue, size: 13.3))
 | 
			
		||||
                            .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                            .accentColor(Color(hex: "9970ff"))
 | 
			
		||||
                            .accentColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                            .keyboardType(.default)
 | 
			
		||||
                            .padding(.horizontal, 13.3)
 | 
			
		||||
                        
 | 
			
		||||
@@ -78,7 +78,7 @@ struct ContentDetailCommentView: View {
 | 
			
		||||
                    .overlay(
 | 
			
		||||
                        RoundedRectangle(cornerRadius: 10)
 | 
			
		||||
                            .strokeBorder(lineWidth: 1)
 | 
			
		||||
                            .foregroundColor(Color(hex: "9970ff"))
 | 
			
		||||
                            .foregroundColor(Color(hex: "3bb9f1"))
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
//
 | 
			
		||||
//  ModifyCommentRequest.swift
 | 
			
		||||
//  SodaLive
 | 
			
		||||
//
 | 
			
		||||
//  Created by klaus on 2023/09/08.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
struct ModifyCommentRequest: Encodable {
 | 
			
		||||
    let commentId: Int
 | 
			
		||||
    var comment: String? = nil
 | 
			
		||||
    var isActive: Bool? = nil
 | 
			
		||||
}
 | 
			
		||||
@@ -43,7 +43,7 @@ struct ContentDetailOtherContentView: View {
 | 
			
		||||
                }
 | 
			
		||||
                .padding(13.3)
 | 
			
		||||
                .frame(maxWidth: .infinity)
 | 
			
		||||
                .background(Color(hex: "2b2635"))
 | 
			
		||||
                .background(Color(hex: "13181b"))
 | 
			
		||||
                .cornerRadius(4.7)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import Sliders
 | 
			
		||||
struct ContentDetailPlayView: View {
 | 
			
		||||
    
 | 
			
		||||
    let audioContent: GetAudioContentDetailResponse
 | 
			
		||||
    let isAlertPreview: Bool
 | 
			
		||||
    @Binding var isShowPreviewAlert: Bool
 | 
			
		||||
    
 | 
			
		||||
    @StateObject var contentPlayManager = ContentPlayManager.shared
 | 
			
		||||
@@ -34,7 +35,7 @@ struct ContentDetailPlayView: View {
 | 
			
		||||
                    )
 | 
			
		||||
                    .cornerRadius(10.7, corners: [.topLeft, .topRight])
 | 
			
		||||
                
 | 
			
		||||
                Image(isPlaying() ? "btn_audio_content_pause" : "btn_audio_content_play")
 | 
			
		||||
                Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
 | 
			
		||||
                    .onTapGesture {
 | 
			
		||||
                        if isPlaying() {
 | 
			
		||||
                            contentPlayManager.pauseAudio()
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import SwiftUI
 | 
			
		||||
struct ContentDetailPurchaseButton: View {
 | 
			
		||||
    
 | 
			
		||||
    let price: Int
 | 
			
		||||
    let isOnlyRental: Bool
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        HStack(spacing: 0) {
 | 
			
		||||
@@ -26,7 +27,7 @@ struct ContentDetailPurchaseButton: View {
 | 
			
		||||
                .font(.custom(Font.light.rawValue, size: 12))
 | 
			
		||||
                .foregroundColor(.white)
 | 
			
		||||
            
 | 
			
		||||
            Text(" 구매하기")
 | 
			
		||||
            Text(isOnlyRental ? " 대여하기" : " 구매하기")
 | 
			
		||||
                .font(.custom(Font.bold.rawValue, size: 14.7))
 | 
			
		||||
                .foregroundColor(.white)
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -66,61 +66,57 @@ struct ContentDetailView: View {
 | 
			
		||||
                                    viewModel.getAudioContentDetail()
 | 
			
		||||
                                }) {
 | 
			
		||||
                                    VStack(spacing: 0) {
 | 
			
		||||
                                        LazyVStack(spacing: 0) {
 | 
			
		||||
                                            ContentDetailPlayView(
 | 
			
		||||
                                                audioContent: audioContent,
 | 
			
		||||
                                                isShowPreviewAlert: $viewModel.isShowPreviewAlert
 | 
			
		||||
                                            )
 | 
			
		||||
                                            
 | 
			
		||||
                                            ContentDetailInfoView(
 | 
			
		||||
                                                isExpandDescription: $viewModel.isExpandDescription,
 | 
			
		||||
                                                isShowPreviewAlert: $viewModel.isShowPreviewAlert,
 | 
			
		||||
                                                audioContent: audioContent,
 | 
			
		||||
                                                onClickLike: { viewModel.likeContent() },
 | 
			
		||||
                                                onClickShare: {
 | 
			
		||||
                                                    viewModel.shareAudioContent(
 | 
			
		||||
                                                        contentImage: audioContent.coverImageUrl,
 | 
			
		||||
                                                        contentTitle: "\(audioContent.title) - \(audioContent.creator.nickname)"
 | 
			
		||||
                                                    )
 | 
			
		||||
                                                },
 | 
			
		||||
                                                onClickDonation: { viewModel.isShowDonationPopup = true }
 | 
			
		||||
                                            )
 | 
			
		||||
                                            
 | 
			
		||||
                                            if audioContent.price > 0 &&
 | 
			
		||||
                                                !audioContent.existOrdered &&
 | 
			
		||||
                                                audioContent.orderType == nil &&
 | 
			
		||||
                                                audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
 | 
			
		||||
                                                ContentDetailPurchaseButton(price: audioContent.price)
 | 
			
		||||
                                                    .contentShape(Rectangle())
 | 
			
		||||
                                                    .onTapGesture { isShowOrderView = true }
 | 
			
		||||
                                            }
 | 
			
		||||
                                            
 | 
			
		||||
                                            if audioContent.isCommentAvailable {
 | 
			
		||||
                                                ContentDetailCommentView(
 | 
			
		||||
                                                    commentCount: audioContent.commentCount,
 | 
			
		||||
                                                    commentList: audioContent.commentList,
 | 
			
		||||
                                                    registerComment: { comment in
 | 
			
		||||
                                                        self.viewModel.registerComment(comment: comment)
 | 
			
		||||
                                                    }
 | 
			
		||||
                                        ContentDetailPlayView(
 | 
			
		||||
                                            audioContent: audioContent,
 | 
			
		||||
                                            isAlertPreview: audioContent.price > 0 && !audioContent.existOrdered && audioContent.orderType == nil && audioContent.creator.creatorId != UserDefaults.int(forKey: .userId),
 | 
			
		||||
                                            isShowPreviewAlert: $viewModel.isShowPreviewAlert
 | 
			
		||||
                                        )
 | 
			
		||||
                                        
 | 
			
		||||
                                        ContentDetailInfoView(
 | 
			
		||||
                                            isExpandDescription: $viewModel.isExpandDescription,
 | 
			
		||||
                                            isShowPreviewAlert: $viewModel.isShowPreviewAlert,
 | 
			
		||||
                                            audioContent: audioContent,
 | 
			
		||||
                                            onClickLike: { viewModel.likeContent() },
 | 
			
		||||
                                            onClickShare: {
 | 
			
		||||
                                                viewModel.shareAudioContent(
 | 
			
		||||
                                                    contentImage: audioContent.coverImageUrl,
 | 
			
		||||
                                                    contentTitle: "\(audioContent.title) - \(audioContent.creator.nickname)"
 | 
			
		||||
                                                )
 | 
			
		||||
                                                .padding(10.3)
 | 
			
		||||
                                                .background(Color.white.opacity(0.1))
 | 
			
		||||
                                                .cornerRadius(5.3)
 | 
			
		||||
                                                .padding(.top, 13.3)
 | 
			
		||||
                                            },
 | 
			
		||||
                                            onClickDonation: { viewModel.isShowDonationPopup = true }
 | 
			
		||||
                                        )
 | 
			
		||||
                                        .padding(.horizontal, 13.3)
 | 
			
		||||
                                        
 | 
			
		||||
                                        if audioContent.price > 0 &&
 | 
			
		||||
                                            !audioContent.existOrdered &&
 | 
			
		||||
                                            audioContent.orderType == nil &&
 | 
			
		||||
                                            audioContent.creator.creatorId != UserDefaults.int(forKey: .userId) {
 | 
			
		||||
                                            ContentDetailPurchaseButton(price: audioContent.price, isOnlyRental: audioContent.isOnlyRental)
 | 
			
		||||
                                                .contentShape(Rectangle())
 | 
			
		||||
                                                .onTapGesture {
 | 
			
		||||
                                                    if audioContent.commentCount > 0 {
 | 
			
		||||
                                                        isShowCommentListView = true
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                .padding(.horizontal, 13.3)
 | 
			
		||||
                                                .onTapGesture { isShowOrderView = true }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        
 | 
			
		||||
                                        if audioContent.isCommentAvailable {
 | 
			
		||||
                                            ContentDetailCommentView(
 | 
			
		||||
                                                commentCount: audioContent.commentCount,
 | 
			
		||||
                                                commentList: audioContent.commentList,
 | 
			
		||||
                                                registerComment: { comment in
 | 
			
		||||
                                                    self.viewModel.registerComment(comment: comment)
 | 
			
		||||
                                                }
 | 
			
		||||
                                            )
 | 
			
		||||
                                            .padding(10.3)
 | 
			
		||||
                                            .background(Color.white.opacity(0.1))
 | 
			
		||||
                                            .cornerRadius(5.3)
 | 
			
		||||
                                            .padding(.top, 13.3)
 | 
			
		||||
                                            .contentShape(Rectangle())
 | 
			
		||||
                                            .padding(.horizontal, 13.3)
 | 
			
		||||
                                            .onTapGesture {
 | 
			
		||||
                                                if audioContent.commentCount > 0 {
 | 
			
		||||
                                                    isShowCommentListView = true
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                        }
 | 
			
		||||
                                        .padding(.horizontal, 13.3)
 | 
			
		||||
                                        
 | 
			
		||||
                                        Rectangle()
 | 
			
		||||
                                            .foregroundColor(Color(hex: "232323"))
 | 
			
		||||
                                            .frame(height: 6.7)
 | 
			
		||||
                                            .padding(.top, 24)
 | 
			
		||||
                                        
 | 
			
		||||
                                        ContentDetailOtherContentView(
 | 
			
		||||
                                            title: "크리에이터의 다른 콘텐츠",
 | 
			
		||||
@@ -165,6 +161,7 @@ struct ContentDetailView: View {
 | 
			
		||||
                        ContentOrderDialogView(
 | 
			
		||||
                            isShowing: $isShowOrderView,
 | 
			
		||||
                            price: audioContent.price,
 | 
			
		||||
                            isOnlyRental: audioContent.isOnlyRental,
 | 
			
		||||
                            onTapPurchase: {
 | 
			
		||||
                                viewModel.orderType = $0
 | 
			
		||||
                                isShowOrderConfirmView = true
 | 
			
		||||
@@ -190,6 +187,7 @@ struct ContentDetailView: View {
 | 
			
		||||
                            isShowing: $isShowOrderConfirmView,
 | 
			
		||||
                            audioContent: audioContent,
 | 
			
		||||
                            orderType: orderType,
 | 
			
		||||
                            isOnlyRental: audioContent.isOnlyRental,
 | 
			
		||||
                            onClickConfirm: {
 | 
			
		||||
                                viewModel.order(orderType: orderType)
 | 
			
		||||
                            }
 | 
			
		||||
@@ -282,6 +280,7 @@ struct ContentDetailView: View {
 | 
			
		||||
                content: {
 | 
			
		||||
                    AudioContentCommentListView(
 | 
			
		||||
                        isPresented: $isShowCommentListView,
 | 
			
		||||
                        creatorId: viewModel.audioContent!.creator.creatorId,
 | 
			
		||||
                        audioContentId: viewModel.audioContent!.contentId
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -280,7 +280,7 @@ final class ContentDetailViewModel: ObservableObject {
 | 
			
		||||
        isShowPreviewAlert = false
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        
 | 
			
		||||
        repository.orderAudioContent(audioContentId: contentId, orderType: orderType)
 | 
			
		||||
        repository.orderAudioContent(contentId: contentId, orderType: orderType)
 | 
			
		||||
            .sink { result in
 | 
			
		||||
                switch result {
 | 
			
		||||
                case .finished:
 | 
			
		||||
@@ -298,13 +298,18 @@ final class ContentDetailViewModel: ObservableObject {
 | 
			
		||||
                    
 | 
			
		||||
                    if decoded.success {
 | 
			
		||||
                        self.orderType = nil
 | 
			
		||||
                        self.errorMessage = "구매가 완료되었습니다."
 | 
			
		||||
                        self.errorMessage = orderType == .RENTAL ? "대여가 완료되었습니다." : "구매가 완료되었습니다."
 | 
			
		||||
                        self.isShowPopup = true
 | 
			
		||||
                        self.getAudioContentDetail()
 | 
			
		||||
                        ContentPlayManager.shared.conditionalStopAudio(contentId: contentId)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if let message = decoded.message {
 | 
			
		||||
                            self.errorMessage = message
 | 
			
		||||
                            if message.contains("캔이 부족합니다") {
 | 
			
		||||
                                DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
 | 
			
		||||
                                    AppState.shared.setAppStep(step: .canCharge(refresh: {}, afterCompletionToGoBack: true))
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        } else {
 | 
			
		||||
                            self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
 | 
			
		||||
                        }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ struct ContentOrderConfirmDialogView: View {
 | 
			
		||||
    
 | 
			
		||||
    let audioContent: GetAudioContentDetailResponse
 | 
			
		||||
    let orderType: OrderType
 | 
			
		||||
    let isOnlyRental: Bool
 | 
			
		||||
    let onClickConfirm: () -> Void
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
@@ -90,9 +91,15 @@ struct ContentOrderConfirmDialogView: View {
 | 
			
		||||
                        .resizable()
 | 
			
		||||
                        .frame(width: 16.7, height: 16.7)
 | 
			
		||||
                    
 | 
			
		||||
                    Text("\(orderType == .RENTAL ? Int(ceil(Double(audioContent.price) * 0.7)) : audioContent.price)")
 | 
			
		||||
                        .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                        .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                    if orderType == .RENTAL {
 | 
			
		||||
                        Text("\(isOnlyRental ? audioContent.price : Int(ceil(Double(audioContent.price) * 0.6)))")
 | 
			
		||||
                            .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                            .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Text("\(audioContent.price)")
 | 
			
		||||
                            .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                            .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    Spacer()
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ struct ContentOrderDialogView: View {
 | 
			
		||||
    @Binding var isShowing: Bool
 | 
			
		||||
    
 | 
			
		||||
    let price: Int
 | 
			
		||||
    let isOnlyRental: Bool
 | 
			
		||||
    let onTapPurchase: (OrderType) -> Void
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
@@ -31,7 +32,7 @@ struct ContentOrderDialogView: View {
 | 
			
		||||
                                .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                                .foregroundColor(.white)
 | 
			
		||||
                            
 | 
			
		||||
                            Text("(이용기간 7일)")
 | 
			
		||||
                            Text("(이용기간 15일)")
 | 
			
		||||
                                .font(.custom(Font.light.rawValue, size: 12))
 | 
			
		||||
                                .foregroundColor(.white)
 | 
			
		||||
                        }
 | 
			
		||||
@@ -43,7 +44,7 @@ struct ContentOrderDialogView: View {
 | 
			
		||||
                                .resizable()
 | 
			
		||||
                                .frame(width: 16.7, height: 16.7)
 | 
			
		||||
                            
 | 
			
		||||
                            Text("\(Int(ceil(Double(price) * 0.7)))")
 | 
			
		||||
                            Text(isOnlyRental ? "\(price)" : "\(Int(ceil(Double(price) * 0.6)))")
 | 
			
		||||
                                .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                                .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                        }
 | 
			
		||||
@@ -57,35 +58,37 @@ struct ContentOrderDialogView: View {
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    HStack(spacing: 0) {
 | 
			
		||||
                        VStack(alignment: .leading, spacing: 5.3) {
 | 
			
		||||
                            Text("소장")
 | 
			
		||||
                                .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                                .foregroundColor(.white)
 | 
			
		||||
                    if !isOnlyRental {
 | 
			
		||||
                        HStack(spacing: 0) {
 | 
			
		||||
                            VStack(alignment: .leading, spacing: 5.3) {
 | 
			
		||||
                                Text("소장")
 | 
			
		||||
                                    .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                                    .foregroundColor(.white)
 | 
			
		||||
                                
 | 
			
		||||
                                Text("(서비스 종료시까지)")
 | 
			
		||||
                                    .font(.custom(Font.light.rawValue, size: 12))
 | 
			
		||||
                                    .foregroundColor(.white)
 | 
			
		||||
                            }
 | 
			
		||||
                            
 | 
			
		||||
                            Text("(서비스 종료시까지)")
 | 
			
		||||
                                .font(.custom(Font.light.rawValue, size: 12))
 | 
			
		||||
                                .foregroundColor(.white)
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        Spacer()
 | 
			
		||||
                        
 | 
			
		||||
                        HStack(spacing: 8) {
 | 
			
		||||
                            Image("ic_coin_w")
 | 
			
		||||
                                .resizable()
 | 
			
		||||
                                .frame(width: 16.7, height: 16.7)
 | 
			
		||||
                            Spacer()
 | 
			
		||||
                            
 | 
			
		||||
                            Text("\(price)")
 | 
			
		||||
                                .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                                .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                        }
 | 
			
		||||
                        .padding(.vertical, 8)
 | 
			
		||||
                        .padding(.horizontal, 13.3)
 | 
			
		||||
                        .background(Color(hex: "9970ff"))
 | 
			
		||||
                        .cornerRadius(5.3)
 | 
			
		||||
                        .onTapGesture {
 | 
			
		||||
                            onTapPurchase(.KEEP)
 | 
			
		||||
                            isShowing = false
 | 
			
		||||
                            HStack(spacing: 8) {
 | 
			
		||||
                                Image("ic_can")
 | 
			
		||||
                                    .resizable()
 | 
			
		||||
                                    .frame(width: 16.7, height: 16.7)
 | 
			
		||||
                                
 | 
			
		||||
                                Text("\(price)")
 | 
			
		||||
                                    .font(.custom(Font.bold.rawValue, size: 13.3))
 | 
			
		||||
                                    .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                            }
 | 
			
		||||
                            .padding(.vertical, 8)
 | 
			
		||||
                            .padding(.horizontal, 13.3)
 | 
			
		||||
                            .background(Color(hex: "9970ff"))
 | 
			
		||||
                            .cornerRadius(5.3)
 | 
			
		||||
                            .onTapGesture {
 | 
			
		||||
                                onTapPurchase(.KEEP)
 | 
			
		||||
                                isShowing = false
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ struct GetAudioContentDetailResponse: Decodable {
 | 
			
		||||
    let duration: String
 | 
			
		||||
    let isAdult: Bool
 | 
			
		||||
    let isMosaic: Bool
 | 
			
		||||
    let isOnlyRental: Bool
 | 
			
		||||
    let existOrdered: Bool
 | 
			
		||||
    let orderType: OrderType?
 | 
			
		||||
    let remainingTime: String?
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,12 @@ import Kingfisher
 | 
			
		||||
 | 
			
		||||
struct LiveRoomDonationDialogView: View {
 | 
			
		||||
    
 | 
			
		||||
    @AppStorage("can") private var can: Int = UserDefaults.int(forKey: .can)
 | 
			
		||||
    
 | 
			
		||||
    @State private var donationCan = ""
 | 
			
		||||
    @State private var donationMessage = ""
 | 
			
		||||
    @State private var isShowErrorPopup = false
 | 
			
		||||
    @State private var errorMessage = ""
 | 
			
		||||
    @State private var can = 0
 | 
			
		||||
    
 | 
			
		||||
    @Binding var isShowing: Bool
 | 
			
		||||
    let isAudioContentDonation: Bool
 | 
			
		||||
@@ -59,7 +60,7 @@ struct LiveRoomDonationDialogView: View {
 | 
			
		||||
                            Image("ic_forward")
 | 
			
		||||
                        }
 | 
			
		||||
                        .onTapGesture {
 | 
			
		||||
                            AppState.shared.setAppStep(step: .canCharge(refresh: {}))
 | 
			
		||||
                            AppState.shared.setAppStep(step: .canCharge(refresh: {}, afterCompletionToGoBack: true))
 | 
			
		||||
                            self.isShowing = false
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
@@ -243,9 +244,6 @@ struct LiveRoomDonationDialogView: View {
 | 
			
		||||
            }
 | 
			
		||||
            .offset(y: isAudioContentDonation ? 0 : 0 - keyboardHandler.keyboardHeight)
 | 
			
		||||
        }
 | 
			
		||||
        .onAppear {
 | 
			
		||||
            self.can = UserDefaults.int(forKey: .can)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    func limitText() {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
struct AudioContentDonationRequest: Encodable {
 | 
			
		||||
    let audioContentId: Int
 | 
			
		||||
    let contentId: Int
 | 
			
		||||
    let donationCan: Int
 | 
			
		||||
    let comment: String
 | 
			
		||||
    let container: String = "ios"
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,26 @@ struct ContentMainCurationItemView: View {
 | 
			
		||||
    
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        VStack(alignment: .leading, spacing: 0) {
 | 
			
		||||
            Text(item.title)
 | 
			
		||||
                .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
			
		||||
                .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
            HStack(spacing: 0) {
 | 
			
		||||
                Text(item.title)
 | 
			
		||||
                    .font(.custom(Font.bold.rawValue, size: 18.3))
 | 
			
		||||
                    .foregroundColor(Color(hex: "eeeeee"))
 | 
			
		||||
                
 | 
			
		||||
                Spacer()
 | 
			
		||||
                
 | 
			
		||||
                Image("ic_forward")
 | 
			
		||||
                    .resizable()
 | 
			
		||||
                    .frame(width: 20, height: 20)
 | 
			
		||||
                    .onTapGesture {
 | 
			
		||||
                        AppState.shared
 | 
			
		||||
                            .setAppStep(
 | 
			
		||||
                                step: .curationAll(
 | 
			
		||||
                                    title: item.title,
 | 
			
		||||
                                    curationId: item.curationId
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
                    }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            Text(item.description)
 | 
			
		||||
                .font(.custom(Font.medium.rawValue, size: 13))
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,8 @@ struct ContentMainCurationView: View {
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        LazyVStack(spacing: 40) {
 | 
			
		||||
            ForEach(0..<items.count, id: \.self) {
 | 
			
		||||
                let item = items[$0]
 | 
			
		||||
                ContentMainCurationItemView(item: item)
 | 
			
		||||
                ContentMainCurationItemView(item: items[$0])
 | 
			
		||||
                    .padding(.horizontal, 13.3)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,6 @@ struct ContentMainItemView_Previews: PreviewProvider {
 | 
			
		||||
                contentId: 2,
 | 
			
		||||
                coverImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
 | 
			
		||||
                title: "ㅓ처랴햐햫햐햐",
 | 
			
		||||
                isAdult: true,
 | 
			
		||||
                creatorId: 8,
 | 
			
		||||
                creatorProfileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png",
 | 
			
		||||
                creatorNickname: "유저2"
 | 
			
		||||
 
 | 
			
		||||