Compare commits
5 Commits
31bd137f4d
...
8f62c17971
Author | SHA1 | Date |
---|---|---|
![]() |
8f62c17971 | |
![]() |
12d2c09434 | |
![]() |
bd818918f3 | |
![]() |
aa87f0367b | |
![]() |
34f2348aa0 |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_pin.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_pin_cancel.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/ic_pin_cancel.png
vendored
Normal file
BIN
SodaLive/Resources/Assets.xcassets/ic_pin_cancel.imageset/ic_pin_cancel.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "ic_trash_can.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 787 B |
|
@ -34,6 +34,8 @@ enum ContentApi {
|
||||||
case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
|
case getAudioContentListByCurationId(curationId: Int, page: Int, size: Int, sort: ContentCurationViewModel.Sort)
|
||||||
case getContentRanking(page: Int, size: Int, sortType: String)
|
case getContentRanking(page: Int, size: Int, sortType: String)
|
||||||
case getContentRankingSortType
|
case getContentRankingSortType
|
||||||
|
case pinContent(contentId: Int)
|
||||||
|
case unpinContent(contentId: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ContentApi: TargetType {
|
extension ContentApi: TargetType {
|
||||||
|
@ -117,6 +119,12 @@ extension ContentApi: TargetType {
|
||||||
|
|
||||||
case .getContentRankingSortType:
|
case .getContentRankingSortType:
|
||||||
return "/audio-content/ranking-sort-type"
|
return "/audio-content/ranking-sort-type"
|
||||||
|
|
||||||
|
case .pinContent(let contentId):
|
||||||
|
return "/audio-content/pin-to-the-top/\(contentId)"
|
||||||
|
|
||||||
|
case .unpinContent(let contentId):
|
||||||
|
return "/audio-content/unpin-at-the-top/\(contentId)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,10 +139,10 @@ extension ContentApi: TargetType {
|
||||||
case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList:
|
case .getMainBannerList, .getMainOrderList, .getNewContentUploadCreatorList, .getCurationList:
|
||||||
return .get
|
return .get
|
||||||
|
|
||||||
case .likeContent, .modifyAudioContent, .modifyComment:
|
case .likeContent, .modifyAudioContent, .modifyComment, .unpinContent:
|
||||||
return .put
|
return .put
|
||||||
|
|
||||||
case .registerComment, .orderAudioContent, .addAllPlaybackTracking, .uploadAudioContent, .donation:
|
case .registerComment, .orderAudioContent, .addAllPlaybackTracking, .uploadAudioContent, .donation, .pinContent:
|
||||||
return .post
|
return .post
|
||||||
|
|
||||||
case .deleteAudioContent:
|
case .deleteAudioContent:
|
||||||
|
@ -259,6 +267,9 @@ extension ContentApi: TargetType {
|
||||||
] as [String : Any]
|
] as [String : Any]
|
||||||
|
|
||||||
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString)
|
||||||
|
|
||||||
|
case .pinContent, .unpinContent:
|
||||||
|
return .requestPlain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,12 @@ struct ContentListItemView: View {
|
||||||
.padding(2.6)
|
.padding(2.6)
|
||||||
.background(Color(hex: "222222"))
|
.background(Color(hex: "222222"))
|
||||||
.cornerRadius(2.6)
|
.cornerRadius(2.6)
|
||||||
|
|
||||||
|
if item.isPin {
|
||||||
|
Image("ic_pin")
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 13.3, height: 13.3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(item.title)
|
Text(item.title)
|
||||||
|
@ -121,6 +127,7 @@ struct ContentListItemView_Previews: PreviewProvider {
|
||||||
duration: "00:04:43",
|
duration: "00:04:43",
|
||||||
likeCount: 2,
|
likeCount: 2,
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
|
isPin: true,
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
isScheduledToOpen: true
|
isScheduledToOpen: true
|
||||||
)
|
)
|
||||||
|
|
|
@ -112,4 +112,12 @@ final class ContentRepository {
|
||||||
func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> {
|
func getContentRanking(page: Int, size: Int, sortType: String = "매출") -> AnyPublisher<Response, MoyaError> {
|
||||||
return api.requestPublisher(.getContentRanking(page: page, size: size, sortType: sortType))
|
return api.requestPublisher(.getContentRanking(page: page, size: size, sortType: sortType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pinContent(contentId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.pinContent(contentId: contentId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpinContent(contentId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||||
|
return api.requestPublisher(.unpinContent(contentId: contentId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,62 +288,84 @@ struct ContentCreateView: View {
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
.padding(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
}
|
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
if !viewModel.isFree {
|
Text("미리듣기")
|
||||||
VStack(spacing: 10) {
|
|
||||||
Text("미리듣기 시간 설정")
|
|
||||||
.font(.custom(Font.bold.rawValue, size: 16.7))
|
.font(.custom(Font.bold.rawValue, size: 16.7))
|
||||||
.foregroundColor(Color(hex: "eeeeee"))
|
.foregroundColor(Color(hex: "eeeeee"))
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.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) {
|
HStack(spacing: 13.3) {
|
||||||
VStack(spacing: 5.3) {
|
SelectButtonView(title: "생성", isChecked: viewModel.isGeneratePreview) {
|
||||||
Text("시작 시간")
|
if !viewModel.isGeneratePreview {
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
viewModel.isGeneratePreview = true
|
||||||
.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) {
|
SelectButtonView(title: "생성 안 함", isChecked: !viewModel.isGeneratePreview) {
|
||||||
Text("종료 시간")
|
if viewModel.isGeneratePreview {
|
||||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
viewModel.isGeneratePreview = false
|
||||||
.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(.top, 26.7)
|
||||||
|
|
||||||
|
if viewModel.isGeneratePreview {
|
||||||
|
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(.top, 26.7)
|
.padding(.top, 26.7)
|
||||||
|
|
|
@ -56,11 +56,13 @@ final class ContentCreateViewModel: ObservableObject {
|
||||||
if isFree {
|
if isFree {
|
||||||
priceString = "0"
|
priceString = "0"
|
||||||
isOnlyRental = false
|
isOnlyRental = false
|
||||||
|
isGeneratePreview = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var isOnlyRental = false
|
@Published var isOnlyRental = false
|
||||||
|
@Published var isGeneratePreview = true
|
||||||
|
|
||||||
@Published var previewStartTime: String = ""
|
@Published var previewStartTime: String = ""
|
||||||
@Published var previewEndTime: String = ""
|
@Published var previewEndTime: String = ""
|
||||||
|
@ -94,10 +96,11 @@ final class ContentCreateViewModel: ObservableObject {
|
||||||
timezone: TimeZone.current.identifier,
|
timezone: TimeZone.current.identifier,
|
||||||
themeId: theme!.id,
|
themeId: theme!.id,
|
||||||
isAdult: isAdult,
|
isAdult: isAdult,
|
||||||
isOnlyRental: isOnlyRental,
|
isOnlyRental: isOnlyRental,
|
||||||
|
isGeneratePreview: isGeneratePreview,
|
||||||
isCommentAvailable: isAvailableComment,
|
isCommentAvailable: isAvailableComment,
|
||||||
previewStartTime: previewStartTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewStartTime : nil,
|
previewStartTime: isGeneratePreview && previewStartTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewStartTime : nil,
|
||||||
previewEndTime: previewEndTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewEndTime : nil
|
previewEndTime: isGeneratePreview && previewEndTime.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 ? previewEndTime : nil
|
||||||
)
|
)
|
||||||
|
|
||||||
var multipartData = [MultipartFormData]()
|
var multipartData = [MultipartFormData]()
|
||||||
|
|
|
@ -17,6 +17,7 @@ struct CreateAudioContentRequest: Encodable {
|
||||||
let themeId: Int
|
let themeId: Int
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isOnlyRental: Bool
|
let isOnlyRental: Bool
|
||||||
|
let isGeneratePreview: Bool
|
||||||
let isCommentAvailable: Bool
|
let isCommentAvailable: Bool
|
||||||
let previewStartTime: String?
|
let previewStartTime: String?
|
||||||
let previewEndTime: String?
|
let previewEndTime: String?
|
||||||
|
|
|
@ -11,7 +11,10 @@ struct ContentDetailMenuView: View {
|
||||||
|
|
||||||
@Binding var isShowing: Bool
|
@Binding var isShowing: Bool
|
||||||
|
|
||||||
|
let isPin: Bool
|
||||||
let isShowCreatorMenu: Bool
|
let isShowCreatorMenu: Bool
|
||||||
|
|
||||||
|
let pinAction: () -> Void
|
||||||
let modifyAction: () -> Void
|
let modifyAction: () -> Void
|
||||||
let deleteAction: () -> Void
|
let deleteAction: () -> Void
|
||||||
let reportAction: () -> Void
|
let reportAction: () -> Void
|
||||||
|
@ -28,7 +31,26 @@ struct ContentDetailMenuView: View {
|
||||||
|
|
||||||
VStack(spacing: 13.3) {
|
VStack(spacing: 13.3) {
|
||||||
if isShowCreatorMenu {
|
if isShowCreatorMenu {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 13.3) {
|
||||||
|
Image(isPin ? "ic_pin_cancel" : "ic_pin")
|
||||||
|
|
||||||
|
Text(isPin ? "내 채널에 고정 취소" : "내 채널에 고정")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
pinAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
Image("ic_make_message")
|
||||||
|
|
||||||
Text("수정")
|
Text("수정")
|
||||||
.font(.custom(Font.medium.rawValue, size: 16.7))
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
@ -43,7 +65,9 @@ struct ContentDetailMenuView: View {
|
||||||
modifyAction()
|
modifyAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 13.3) {
|
||||||
|
Image("ic_trash_can")
|
||||||
|
|
||||||
Text("삭제")
|
Text("삭제")
|
||||||
.font(.custom(Font.medium.rawValue, size: 16.7))
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
|
|
@ -35,7 +35,7 @@ struct ContentDetailPlayView: View {
|
||||||
)
|
)
|
||||||
.cornerRadius(10.7, corners: [.topLeft, .topRight])
|
.cornerRadius(10.7, corners: [.topLeft, .topRight])
|
||||||
|
|
||||||
if audioContent.releaseDate == nil || audioContent.creator.creatorId == UserDefaults.int(forKey: .userId) {
|
if audioContent.releaseDate == nil && !isAlertPreview || (isAlertPreview && audioContent.isActivePreview) {
|
||||||
Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
|
Image(isPlaying() ? "btn_audio_content_pause" : isAlertPreview ? "btn_audio_content_preview_play" : "btn_audio_content_play")
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if isPlaying() {
|
if isPlaying() {
|
||||||
|
|
|
@ -219,7 +219,19 @@ struct ContentDetailView: View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
ContentDetailMenuView(
|
ContentDetailMenuView(
|
||||||
isShowing: $viewModel.isShowReportMenu,
|
isShowing: $viewModel.isShowReportMenu,
|
||||||
|
isPin: viewModel.audioContent!.isPin,
|
||||||
isShowCreatorMenu: viewModel.audioContent!.creator.creatorId == UserDefaults.int(forKey: .userId),
|
isShowCreatorMenu: viewModel.audioContent!.creator.creatorId == UserDefaults.int(forKey: .userId),
|
||||||
|
pinAction: {
|
||||||
|
if viewModel.audioContent!.isPin {
|
||||||
|
viewModel.unpinContent(contentId: contentId)
|
||||||
|
} else {
|
||||||
|
if viewModel.audioContent!.isAvailablePin {
|
||||||
|
viewModel.pinContent(contentId: contentId)
|
||||||
|
} else {
|
||||||
|
viewModel.isShowNoticePinContentPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
modifyAction: {
|
modifyAction: {
|
||||||
if viewModel.audioContent!.creator.creatorId == UserDefaults.int(forKey: .userId) {
|
if viewModel.audioContent!.creator.creatorId == UserDefaults.int(forKey: .userId) {
|
||||||
AppState
|
AppState
|
||||||
|
@ -284,6 +296,21 @@ struct ContentDetailView: View {
|
||||||
viewModel.donation(can: can, comment: comment)
|
viewModel.donation(can: can, comment: comment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if viewModel.isShowNoticePinContentPopup {
|
||||||
|
SodaDialog(
|
||||||
|
title: "고정 한도 도달",
|
||||||
|
desc: "이 콘텐츠를 고정하시겠어요? " +
|
||||||
|
"채널에 콘텐츠를 최대 3개까지 고정할 수 있습니다." +
|
||||||
|
"이 콘텐츠를 고정하면 가장 오래된 콘텐츠가 대체됩니다.",
|
||||||
|
confirmButtonTitle: "확인",
|
||||||
|
confirmButtonAction: {
|
||||||
|
viewModel.pinContent(contentId: contentId)
|
||||||
|
},
|
||||||
|
cancelButtonTitle: "취소",
|
||||||
|
cancelButtonAction: {}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(
|
.sheet(
|
||||||
|
|
|
@ -34,6 +34,7 @@ final class ContentDetailViewModel: ObservableObject {
|
||||||
@Published var isShowReportMenu = false
|
@Published var isShowReportMenu = false
|
||||||
@Published var isShowReportView = false
|
@Published var isShowReportView = false
|
||||||
@Published var isShowDeleteConfirm = false
|
@Published var isShowDeleteConfirm = false
|
||||||
|
@Published var isShowNoticePinContentPopup = false
|
||||||
|
|
||||||
var contentId: Int = 0 {
|
var contentId: Int = 0 {
|
||||||
didSet {
|
didSet {
|
||||||
|
@ -448,4 +449,84 @@ final class ContentDetailViewModel: ObservableObject {
|
||||||
.store(in: &subscription)
|
.store(in: &subscription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pinContent(contentId: Int) {
|
||||||
|
isLoading = true
|
||||||
|
repository.pinContent(contentId: contentId)
|
||||||
|
.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.errorMessage = "고정되었습니다"
|
||||||
|
self.isShowPopup = true
|
||||||
|
|
||||||
|
self.getAudioContentDetail()
|
||||||
|
} else {
|
||||||
|
if let message = decoded.message {
|
||||||
|
self.errorMessage = message
|
||||||
|
} else {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
|
||||||
|
self.isShowPopup = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &subscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpinContent(contentId: Int) {
|
||||||
|
isLoading = true
|
||||||
|
repository.unpinContent(contentId: contentId)
|
||||||
|
.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.errorMessage = "해제되었습니다"
|
||||||
|
self.isShowPopup = true
|
||||||
|
|
||||||
|
self.getAudioContentDetail()
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct GetAudioContentDetailResponse: Decodable {
|
||||||
let price: Int
|
let price: Int
|
||||||
let duration: String
|
let duration: String
|
||||||
let releaseDate: String?
|
let releaseDate: String?
|
||||||
|
let isActivePreview: Bool
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isMosaic: Bool
|
let isMosaic: Bool
|
||||||
let isOnlyRental: Bool
|
let isOnlyRental: Bool
|
||||||
|
@ -31,6 +32,8 @@ struct GetAudioContentDetailResponse: Decodable {
|
||||||
let likeCount: Int
|
let likeCount: Int
|
||||||
let commentList: [GetAudioContentCommentListItem]
|
let commentList: [GetAudioContentCommentListItem]
|
||||||
let commentCount: Int
|
let commentCount: Int
|
||||||
|
let isPin: Bool
|
||||||
|
let isAvailablePin: Bool
|
||||||
let creator: AudioContentCreator
|
let creator: AudioContentCreator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,18 @@ struct ContentMainRankingSortView: View {
|
||||||
let sort = sorts[index]
|
let sort = sorts[index]
|
||||||
Text(sort)
|
Text(sort)
|
||||||
.font(.custom(Font.medium.rawValue, size: 14.7))
|
.font(.custom(Font.medium.rawValue, size: 14.7))
|
||||||
.foregroundColor(Color(hex: selectedSort == sort ? "9970ff" : "777777"))
|
.foregroundColor(selectedSort == sort ? Color.button : Color.gray77)
|
||||||
.padding(.horizontal, 13.3)
|
.padding(.horizontal, 13.3)
|
||||||
.padding(.vertical, 9.3)
|
.padding(.vertical, 9.3)
|
||||||
.border(
|
.border(
|
||||||
Color(hex: selectedSort == sort ? "9970ff" : "eeeeee"),
|
selectedSort == sort ? Color.button : Color.grayee,
|
||||||
width: 0.5
|
width: 0.5
|
||||||
)
|
)
|
||||||
.cornerRadius(16.7)
|
.cornerRadius(16.7)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: CGFloat(16.7))
|
RoundedRectangle(cornerRadius: CGFloat(16.7))
|
||||||
.stroke(lineWidth: 0.5)
|
.stroke(lineWidth: 0.5)
|
||||||
.foregroundColor(Color(hex: selectedSort == sort ? "9970ff" : "eeeeee"))
|
.foregroundColor(selectedSort == sort ? Color.button : Color.grayee)
|
||||||
)
|
)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
if selectedSort != sort {
|
if selectedSort != sort {
|
||||||
|
|
|
@ -67,7 +67,7 @@ struct CreatorCommunityAllView: View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if viewModel.isShowReportMenu {
|
if viewModel.isShowReportMenu {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
ContentDetailMenuView(
|
CreatorCommunityMenuView(
|
||||||
isShowing: $viewModel.isShowReportMenu,
|
isShowing: $viewModel.isShowReportMenu,
|
||||||
isShowCreatorMenu: creatorId == UserDefaults.int(forKey: .userId),
|
isShowCreatorMenu: creatorId == UserDefaults.int(forKey: .userId),
|
||||||
modifyAction: {
|
modifyAction: {
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
//
|
||||||
|
// CreatorCommunityMenuView.swift
|
||||||
|
// SodaLive
|
||||||
|
//
|
||||||
|
// Created by klaus on 1/29/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CreatorCommunityMenuView: View {
|
||||||
|
@Binding var isShowing: Bool
|
||||||
|
|
||||||
|
let isShowCreatorMenu: Bool
|
||||||
|
|
||||||
|
let modifyAction: () -> Void
|
||||||
|
let deleteAction: () -> Void
|
||||||
|
let reportAction: () -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.black
|
||||||
|
.opacity(0.7)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.onTapGesture { isShowing = false }
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
VStack(spacing: 13.3) {
|
||||||
|
if isShowCreatorMenu {
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
Image("ic_make_message")
|
||||||
|
|
||||||
|
Text("수정")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
modifyAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(spacing: 13.3) {
|
||||||
|
Image("ic_trash_can")
|
||||||
|
|
||||||
|
Text("삭제")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
deleteAction()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
Text("신고")
|
||||||
|
.font(.custom(Font.medium.rawValue, size: 16.7))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.horizontal, 26.7)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
isShowing = false
|
||||||
|
reportAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(24)
|
||||||
|
.background(Color(hex: "222222"))
|
||||||
|
.cornerRadius(13.3, corners: [.topLeft, .topRight])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
CreatorCommunityMenuView(
|
||||||
|
isShowing: .constant(true),
|
||||||
|
isShowCreatorMenu: true,
|
||||||
|
modifyAction: {},
|
||||||
|
deleteAction: {},
|
||||||
|
reportAction: {}
|
||||||
|
)
|
||||||
|
}
|
|
@ -77,6 +77,7 @@ struct GetAudioContentListItem: Decodable {
|
||||||
let duration: String?
|
let duration: String?
|
||||||
let likeCount: Int
|
let likeCount: Int
|
||||||
let commentCount: Int
|
let commentCount: Int
|
||||||
|
let isPin: Bool
|
||||||
let isAdult: Bool
|
let isAdult: Bool
|
||||||
let isScheduledToOpen: Bool
|
let isScheduledToOpen: Bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ struct UserProfileContentView_Previews: PreviewProvider {
|
||||||
duration: "00:04:43",
|
duration: "00:04:43",
|
||||||
likeCount: 2,
|
likeCount: 2,
|
||||||
commentCount: 0,
|
commentCount: 0,
|
||||||
|
isPin: true,
|
||||||
isAdult: false,
|
isAdult: false,
|
||||||
isScheduledToOpen: false
|
isScheduledToOpen: false
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue