From d03fee372abef1467c12b18cb6583b7d6bac7cc4 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Tue, 10 Dec 2024 03:01:40 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=AC=EC=83=9D=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ic_edit_white.imageset/Contents.json | 21 ++ .../ic_edit_white.imageset/ic_edit_white.png | Bin 0 -> 496 bytes .../ic_playlist_play.imageset/Contents.json | 21 ++ .../ic_playlist_play.png | Bin 0 -> 491 bytes .../Contents.json | 21 ++ .../ic_playlist_shuffle.png | Bin 0 -> 563 bytes .../Contents.json | 21 ++ .../ic_seemore_vertical_white.png | Bin 0 -> 360 bytes .../Sources/Content/Box/ContentBoxView.swift | 17 +- .../ContentPlaylistListRepository.swift | 4 + .../Playlist/ContentPlaylistListView.swift | 9 +- .../Detail/ContentPlaylistDetailView.swift | 197 ++++++++++++++++++ .../ContentPlaylistDetailViewModel.swift | 67 ++++++ .../Detail/PlaylistContentItemView.swift | 66 ++++++ .../Content/Playlist/PlaylistApi.swift | 8 +- 15 files changed, 447 insertions(+), 5 deletions(-) create mode 100644 SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/ic_edit_white.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_playlist_play.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_playlist_play.imageset/ic_playlist_play.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/ic_playlist_shuffle.png create mode 100644 SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/Contents.json create mode 100644 SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/ic_seemore_vertical_white.png create mode 100644 SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailView.swift create mode 100644 SodaLive/Sources/Content/Playlist/Detail/ContentPlaylistDetailViewModel.swift create mode 100644 SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift diff --git a/SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/Contents.json new file mode 100644 index 0000000..180a976 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_edit_white.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/ic_edit_white.png b/SodaLive/Resources/Assets.xcassets/ic_edit_white.imageset/ic_edit_white.png new file mode 100644 index 0000000000000000000000000000000000000000..9e0d7e711ade17f752d207b02ae9371f1ab31295 GIT binary patch literal 496 zcmVeuaw@`q-7DdtE!hhOw|FKJ;9jzE(P19t3d(OGcdw(M4 zRs&zdw#7iN0v>{G<3=wI9)oS;L@x@Sf^Dm+>M>2zGtDfag_?s!yWQ@d7+M587aQ(9 zg-7~>7>fQ#b6^jLLq<$R1xv%CXkdw06a_35i`c=Ev4|Bc9gEn&8nB23tObi^gEe8% zEU-2#nhe&6MU%i2)Vw#vFEQ&hukcLIkz!I@2 z3Ro%@v4bUJ5i3|a7O{afV9`+U_qEM{ebIomEX%Si%d#xSE{=F`*t4$dBPlFNk}vQ5 zXWw2gc<#_nLVq7LZ{77?a2VQ^;pdKY_p{&ceRpxOb6QgB1><9$lot%|ed`xrT6vy7 mIp-coVc}(a<~XKkY3vv7A7snig*HV100000a|84$w4B(=_Q3(H+l*=Saaf&yJ^qbsVDq4Rpx{R{0J66D~TK+$JA-PxBpl23SHe zHY!V@o>C)KsO$~O8FkvZftn9*yR;g_14W1iNpt3asdL1-XHgQF#K66yI z_6E=O2-|!_LYe}49nT?UWwiy2wy7$sHK3QIvYG>W9YSRX2x$)(?I2_&U?<(8m4IFk z0ZRczECu|6fR%u5wOV~Zz)HX^gtP}d@x1W%-XU}gy&TUIhZFtxBCQ+r8@pc`&eIk! zR&vdALIRp9>X4w4Q(89ohD4QIB*QBUf5-8RjA~ZY@jUR>95Sj^(O5|bO%hZzdnKnz zR@9+DB^RM^Z7uQYe+vsJ+$pH7o#ngRkjYDtQToC+eR5evmMdwLAGa h%vP~!nx<*4+z%3#h!Ww7h}HlA002ovPDHLkV1n>X#a93T literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/Contents.json new file mode 100644 index 0000000..9b2410f --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_playlist_shuffle.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/ic_playlist_shuffle.png b/SodaLive/Resources/Assets.xcassets/ic_playlist_shuffle.imageset/ic_playlist_shuffle.png new file mode 100644 index 0000000000000000000000000000000000000000..56fa8d459080d40a2519e7e128b28087da6ad9ad GIT binary patch literal 563 zcmV-30?hr1P))_OswkAToiRaJkO8zk83a8?+}3nH+0-E?N|>My3_4I_yX zL^q_i<^##rB)LI6pL8^IyfS?(kOPuhJ45Fi2{HLRLsVfZOlOaf9FUt<&=!+alG=Yc z`OE~g%_eAzN+L<^R*c-j$*+)udnFmUlkR3}6d}kI5-jPFt37!Yaxzwut}ln=n4Ai^ zVI&(*oRK>Z$uap9O28?qWv&Ow+1Ofl6VA~WlLaej3j1)r50;x9_SZOx+}vQ~&d)Bl z#4{jrbA^#Jteu#peYX8zPU!9pU{KD+9edYz1Nmwj+h(7btXz9b&c-%Q+|CnM%-aMf z*W75MZb=p)=#f)_Z3iVw5j@1m_1b$SvkRJH?vd-|VI{K)rjqOByOPW%m|Cuv*Ofd^ zE~u{;eiahJNJjS)yL8`QQs2lk`gvRYdXCLgwAsVR1gF~5ClQ+@el7d(FG66*sTBn002ovPDHLkV1o4( B<}CmK literal 0 HcmV?d00001 diff --git a/SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/Contents.json b/SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/Contents.json new file mode 100644 index 0000000..8106340 --- /dev/null +++ b/SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "ic_seemore_vertical_white.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/ic_seemore_vertical_white.png b/SodaLive/Resources/Assets.xcassets/ic_seemore_vertical_white.imageset/ic_seemore_vertical_white.png new file mode 100644 index 0000000000000000000000000000000000000000..69c0cd6b0d4165be2ce2b1fcd9590e61a44716e0 GIT binary patch literal 360 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{$EcQ$B+ufw>J*<9x@PUedu|K!Ey<|*NL!A zw>@uoHFy8J*VkZ>b-w7ah@bY&4fcO%QCa3r@s8N>-*-< zle-pKTrI6N6O2sYzg>Cb@U7YHb`>w`g(tAcwLM&~bNcb>nU(YEXD#y-TVuk=|ND2| zvQ?|jv8Xi0?oLe0XIgb&JFl^|Z~*JRycwQq3Y>4`FWr{6Sg#J$es2Hz^-Gp7{5-oz z{PdHrCVh{Oo_~~n>ifR;(aa1C2gDa#?s8iGwZTuDV{Vv$)mqV$EHAbwUC6wc$;iM^ zqiD5Oqa=#|#g?WEnXC&hM@{qrDrA}~Hdl-xrS6x literal 0 HcmV?d00001 diff --git a/SodaLive/Sources/Content/Box/ContentBoxView.swift b/SodaLive/Sources/Content/Box/ContentBoxView.swift index 5dd9ee7..8f0e074 100644 --- a/SodaLive/Sources/Content/Box/ContentBoxView.swift +++ b/SodaLive/Sources/Content/Box/ContentBoxView.swift @@ -11,9 +11,12 @@ struct ContentBoxView: View { @StateObject var viewModel = ContentBoxViewModel() + @State private var isShowDetailPlaylist = false @State private var isShowCreatePlaylist = false @State private var isReloadData = false + @State private var selectedPlaylistId = 0 + var body: some View { ZStack { NavigationView { @@ -55,7 +58,11 @@ struct ContentBoxView: View { } } else { ContentPlaylistListView( - onClickCreate: { isShowCreatePlaylist = true } + onClickCreate: { isShowCreatePlaylist = true }, + onClickItem: { playlistId in + selectedPlaylistId = playlistId + isShowDetailPlaylist = true + } ) .padding(.bottom, 13.3) .padding(.horizontal, 13.3) @@ -72,6 +79,14 @@ struct ContentBoxView: View { reloadData: $isReloadData ) } + + if isShowDetailPlaylist { + ContentPlaylistDetailView( + playlistId: selectedPlaylistId, + isShowing: $isShowDetailPlaylist, + reloadData: $isReloadData + ) + } } } } diff --git a/SodaLive/Sources/Content/Playlist/ContentPlaylistListRepository.swift b/SodaLive/Sources/Content/Playlist/ContentPlaylistListRepository.swift index ae739de..0724c62 100644 --- a/SodaLive/Sources/Content/Playlist/ContentPlaylistListRepository.swift +++ b/SodaLive/Sources/Content/Playlist/ContentPlaylistListRepository.swift @@ -19,4 +19,8 @@ class ContentPlaylistListRepository { func createPlaylist(request: CreatePlaylistRequest) -> AnyPublisher { return api.requestPublisher(.createPlaylist(request: request)) } + + func getPlaylistDetail(playlistId: Int) -> AnyPublisher { + return api.requestPublisher(.getPlaylistDetail(playlistId: playlistId)) + } } diff --git a/SodaLive/Sources/Content/Playlist/ContentPlaylistListView.swift b/SodaLive/Sources/Content/Playlist/ContentPlaylistListView.swift index 982c4d5..2009265 100644 --- a/SodaLive/Sources/Content/Playlist/ContentPlaylistListView.swift +++ b/SodaLive/Sources/Content/Playlist/ContentPlaylistListView.swift @@ -9,9 +9,10 @@ import SwiftUI struct ContentPlaylistListView: View { - @ObservedObject var viewModel = ContentPlaylistListViewModel() + @StateObject var viewModel = ContentPlaylistListViewModel() let onClickCreate: () -> Void + let onClickItem: (Int) -> Void var body: some View { BaseView(isLoading: $viewModel.isLoading) { @@ -61,6 +62,10 @@ struct ContentPlaylistListView: View { ForEach(0.. 2 { + KFImage(URL(string: response.playlistCoverImageList[1])) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 80, height: 80)) + .resizable() + .scaledToFill() + .clipped() + .frame(maxWidth: 40, maxHeight: 40) + } + } + + HStack(spacing: 0) { + if response.playlistCoverImageList.count > 2 { + KFImage(URL(string: response.playlistCoverImageList[2])) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 80, height: 80)) + .resizable() + .scaledToFill() + .clipped() + .frame(maxWidth: 40, maxHeight: 40) + } + + if response.playlistCoverImageList.count > 3 { + KFImage(URL(string: response.playlistCoverImageList[3])) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 80, height: 80)) + .resizable() + .scaledToFill() + .clipped() + .frame(maxWidth: 40, maxHeight: 40) + } + } + } + .frame(width: 80, height: 80) + .background(Color.graybb) + .cornerRadius(4) + + VStack(spacing: 6.7) { + Text(response.title) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color.grayd2) + .lineLimit(2) + .truncationMode(.tail) + + Text(response.desc.prefix(100)) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.gray90) + .truncationMode(.tail) + } + } + + HStack(spacing: 0) { + Text("만든 날짜 \(response.createdDate)") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.gray90) + + Spacer() + + Text("\(response.contentCount)개") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.grayee) + } + .padding(.top, 13.3) + + HStack(spacing: 13.3) { + HStack(spacing: 5.3) { + Image("ic_playlist_play") + + Text("Play") + .font(.custom(Font.bold.rawValue, size: 14.7)) + .foregroundColor(Color.white) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 11) + .background(Color.button) + .cornerRadius(5.3) + .contentShape(Rectangle()) + .onTapGesture { + } + + HStack(spacing: 5.3) { + Image("ic_playlist_shuffle") + + Text("Shuffle") + .font(.custom(Font.bold.rawValue, size: 14.7)) + .foregroundColor(Color.white) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 11) + .background(Color.button) + .cornerRadius(5.3) + .contentShape(Rectangle()) + .onTapGesture { + } + } + .padding(.top, 18) + + LazyVStack(alignment: .leading, spacing: 0) { + ForEach(0..() + + @Published var isLoading = false + @Published var errorMessage = "" + @Published var isShowPopup = false + + @Published var response: GetPlaylistDetailResponse? = nil + + var playlistId: Int = 0 { + didSet { + if playlistId > 0 { + getPlaylistDetail() + } + } + } + + private func getPlaylistDetail() { + isLoading = true + + repository.getPlaylistDetail(playlistId: playlistId) + .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.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.response = data + } else { + if let message = decoded.message { + self.errorMessage = message + } else { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + } + + self.isShowPopup = true + } + } catch { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.isShowPopup = true + } + + self.isLoading = false + } + .store(in: &subscription) + } +} diff --git a/SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift b/SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift new file mode 100644 index 0000000..4524c63 --- /dev/null +++ b/SodaLive/Sources/Content/Playlist/Detail/PlaylistContentItemView.swift @@ -0,0 +1,66 @@ +// +// PlaylistContentItemView.swift +// SodaLive +// +// Created by klaus on 12/10/24. +// + +import SwiftUI +import Kingfisher + +struct PlaylistContentItemView: View { + + let item: AudioContentPlaylistContent + + var body: some View { + HStack(spacing: 13.3) { + KFImage(URL(string: item.coverUrl)) + .cancelOnDisappear(true) + .downsampling(size: CGSize(width: 40, height: 40)) + .resizable() + .scaledToFill() + .frame(width: 40, height: 40, alignment: .center) + .cornerRadius(5.3) + .clipped() + .padding(.vertical, 7.5) + + VStack(alignment: .leading, spacing: 2.6) { + HStack(spacing: 8) { + Text(item.category) + .font(.custom(Font.medium.rawValue, size: 10)) + .foregroundColor(Color(hex: "3bac6a")) + .padding(2.6) + .background(Color(hex: "28312b")) + .cornerRadius(2.6) + + Text(item.duration) + .font(.custom(Font.medium.rawValue, size: 10)) + .foregroundColor(Color.gray77) + .padding(2.6) + .background(Color.gray22) + .cornerRadius(2.6) + } + + Text(item.title) + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.grayd2) + .lineLimit(2) + .truncationMode(.tail) + } + } + } +} + +#Preview { + PlaylistContentItemView( + item: AudioContentPlaylistContent( + id: 1, + title: "안녕하세요 오늘은 커버곡을 들려드릴께요....안녕하세요 오늘은 커버곡을 들려드릴께요....", + category: "커버곡", + coverUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + duration: "00:30:20", + creatorNickname: "유저1", + creatorProfileUrl: "https://test-cf.sodalive.net/profile/default-profile.png" + ) + ) +} diff --git a/SodaLive/Sources/Content/Playlist/PlaylistApi.swift b/SodaLive/Sources/Content/Playlist/PlaylistApi.swift index 23a05b4..ff4a592 100644 --- a/SodaLive/Sources/Content/Playlist/PlaylistApi.swift +++ b/SodaLive/Sources/Content/Playlist/PlaylistApi.swift @@ -11,6 +11,7 @@ import Moya enum PlaylistApi { case getPlaylistList case createPlaylist(request: CreatePlaylistRequest) + case getPlaylistDetail(playlistId: Int) } extension PlaylistApi: TargetType { @@ -22,12 +23,15 @@ extension PlaylistApi: TargetType { switch self { case .getPlaylistList, .createPlaylist: return "/audio-content/playlist" + + case .getPlaylistDetail(let playlistId): + return "/audio-content/playlist/\(playlistId)" } } var method: Moya.Method { switch self { - case .getPlaylistList: + case .getPlaylistList, .getPlaylistDetail: return .get case .createPlaylist: @@ -37,7 +41,7 @@ extension PlaylistApi: TargetType { var task: Moya.Task { switch self { - case .getPlaylistList: + case .getPlaylistList, .getPlaylistDetail: return .requestPlain case .createPlaylist(let request):