From fc50417ed04addb5feecdb4652527904afa48e97 Mon Sep 17 00:00:00 2001 From: Jaewift Date: Mon, 25 Nov 2024 01:22:41 +0900 Subject: [PATCH] =?UTF-8?q?[feat]=20=ED=94=8C=EB=A0=88=EC=9D=B4=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=98=AE=EA=B8=B0=EA=B8=B0=20->=20?= =?UTF-8?q?=EC=9D=8C=EC=95=85=20=EC=84=A0=ED=83=9D=20=ED=9B=84=20=EC=98=AE?= =?UTF-8?q?=EA=B8=B0=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20(#59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PLUV.xcodeproj/project.pbxproj | 4 + PLUV/Component/LoadingView.swift | 1 + PLUV/Component/SaveMoveView.swift | 5 +- PLUV/Component/ValidationView.swift | 10 +- PLUV/Feed/FeedDetailTableViewCell.swift | 6 +- PLUV/Feed/FeedDetailViewController.swift | 22 +- PLUV/Move/MovePlaylistViewController.swift | 129 +- PLUV/Save/Cell/SaveSongsTableViewCell.swift | 3 +- PLUV/Save/SaveDetailViewController.swift | 17 +- PLUV/Select/SelectMeViewModel.swift | 23 +- PLUV/Select/SelectMusicProtocol.swift | 42 + PLUV/Select/SelectMusicViewController.swift | 1356 +++++++++---------- PLUV/Select/SelectMusicViewModel.swift | 23 +- PLUV/Select/SelectSaveViewModel.swift | 23 +- 14 files changed, 749 insertions(+), 915 deletions(-) create mode 100644 PLUV/Select/SelectMusicProtocol.swift diff --git a/PLUV.xcodeproj/project.pbxproj b/PLUV.xcodeproj/project.pbxproj index 36e8ab4..ddd1e64 100644 --- a/PLUV.xcodeproj/project.pbxproj +++ b/PLUV.xcodeproj/project.pbxproj @@ -112,6 +112,7 @@ C7EB31622CD4ED5700DA439A /* ValidationNotFoundTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EB31612CD4ED5700DA439A /* ValidationNotFoundTableViewCell.swift */; }; C7EEA4F22CC3A59F00FBAA4D /* RecentCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EEA4F12CC3A59F00FBAA4D /* RecentCollectionViewCell.swift */; }; C7EEA4F42CC3A5AE00FBAA4D /* SaveCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7EEA4F32CC3A5AE00FBAA4D /* SaveCollectionViewCell.swift */; }; + C7F7AD5D2CF345170082FDB5 /* SelectMusicProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7F7AD5C2CF345170082FDB5 /* SelectMusicProtocol.swift */; }; C7FA72812CDF1B3A00EB3D91 /* SelectMeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FA72802CDF1B3A00EB3D91 /* SelectMeViewModel.swift */; }; C7FA72832CDF1B4600EB3D91 /* SelectSaveViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C7FA72822CDF1B4600EB3D91 /* SelectSaveViewModel.swift */; }; /* End PBXBuildFile section */ @@ -259,6 +260,7 @@ C7EB31612CD4ED5700DA439A /* ValidationNotFoundTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationNotFoundTableViewCell.swift; sourceTree = ""; }; C7EEA4F12CC3A59F00FBAA4D /* RecentCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentCollectionViewCell.swift; sourceTree = ""; }; C7EEA4F32CC3A5AE00FBAA4D /* SaveCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveCollectionViewCell.swift; sourceTree = ""; }; + C7F7AD5C2CF345170082FDB5 /* SelectMusicProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectMusicProtocol.swift; sourceTree = ""; }; C7FA72802CDF1B3A00EB3D91 /* SelectMeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectMeViewModel.swift; sourceTree = ""; }; C7FA72822CDF1B4600EB3D91 /* SelectSaveViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectSaveViewModel.swift; sourceTree = ""; }; CDAD2C02EE2D55B201611407 /* Pods_PLUVTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PLUVTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -327,6 +329,7 @@ C78221A52CE3B42900568DFF /* SelectMePlaylistViewModel.swift */, C78221A72CE3B43900568DFF /* SelectSavePlaylistViewModel.swift */, 6889F5152C6CE56F001F5FC2 /* SelectMusicViewController.swift */, + C7F7AD5C2CF345170082FDB5 /* SelectMusicProtocol.swift */, 6889F51D2C6D52B7001F5FC2 /* SelectMusicViewModel.swift */, C7FA72802CDF1B3A00EB3D91 /* SelectMeViewModel.swift */, C7FA72822CDF1B4600EB3D91 /* SelectSaveViewModel.swift */, @@ -943,6 +946,7 @@ 68E312252CBC024D00243658 /* FeedDetailTableViewCell.swift in Sources */, 68DC4C772C6B6E9E006D8E97 /* SelectPlaylistCollectionViewCell.swift in Sources */, 68F6FAB22C4A9CCA001128FF /* WhiteButton.swift in Sources */, + C7F7AD5D2CF345170082FDB5 /* SelectMusicProtocol.swift in Sources */, C7C806FE2CCCD7F600245FEA /* RecentTabViewController.swift in Sources */, 68830FAF2C8264CA00995785 /* FeedViewModel.swift in Sources */, 68EE372D2CB0749E0077632B /* FeedInfo.swift in Sources */, diff --git a/PLUV/Component/LoadingView.swift b/PLUV/Component/LoadingView.swift index e5e5328..1fba9cb 100644 --- a/PLUV/Component/LoadingView.swift +++ b/PLUV/Component/LoadingView.swift @@ -18,6 +18,7 @@ final class LoadingView: UIView { label.numberOfLines = 2 label.textAlignment = .center label.textColor = .gray800 + label.font = .systemFont(ofSize: 20, weight: .medium) } let loadingContainerView = UIView() let loadingBar = UIView() diff --git a/PLUV/Component/SaveMoveView.swift b/PLUV/Component/SaveMoveView.swift index 101247a..f48d796 100644 --- a/PLUV/Component/SaveMoveView.swift +++ b/PLUV/Component/SaveMoveView.swift @@ -80,6 +80,7 @@ final class SaveMoveView: UIView { if isOriginalColor { saveButton.setImage(UIImage(named: "savebutton_icon2"), for: .normal) feedDelegate?.setFeedSaveAPI() + saveDelegate?.setFeedSaveAPI() } else { saveButton.setImage(UIImage(named: "savebutton_icon"), for: .normal) feedDelegate?.deleteFeedSaveAPI() @@ -98,8 +99,4 @@ final class SaveMoveView: UIView { saveButton.setImage(UIImage(named: imageName), for: .normal) isOriginalColor = isSaved } - - func updateSaveButton() { - saveButton.isEnabled = false - } } diff --git a/PLUV/Component/ValidationView.swift b/PLUV/Component/ValidationView.swift index 63302ef..72e4694 100644 --- a/PLUV/Component/ValidationView.swift +++ b/PLUV/Component/ValidationView.swift @@ -16,10 +16,12 @@ final class ValidationView: UIView { var noSongImageView = UIImageView().then { $0.contentMode = .scaleAspectFill } - var titleLabel = UILabel().then { - $0.textColor = .gray800 - $0.font = .systemFont(ofSize: 20, weight: .medium) - } + var titleLabel = UILabel().then { + $0.numberOfLines = 2 + $0.textAlignment = .center + $0.textColor = .gray800 + $0.font = .systemFont(ofSize: 20, weight: .medium) + } init(title: String, image: String) { super.init(frame: .zero) diff --git a/PLUV/Feed/FeedDetailTableViewCell.swift b/PLUV/Feed/FeedDetailTableViewCell.swift index 0c1205c..beb1ef9 100644 --- a/PLUV/Feed/FeedDetailTableViewCell.swift +++ b/PLUV/Feed/FeedDetailTableViewCell.swift @@ -18,7 +18,6 @@ final class FeedDetailTableViewCell: UITableViewCell { $0.font = .systemFont(ofSize: 15) } private let thumbnailImageView = UIImageView().then { - $0.contentMode = .scaleAspectFit $0.layer.cornerRadius = 5 $0.layer.borderColor = UIColor.gray300.cgColor $0.layer.borderWidth = 0.5 @@ -29,7 +28,7 @@ final class FeedDetailTableViewCell: UITableViewCell { $0.font = .systemFont(ofSize: 15) /// g, y, p 같은 문자 이슈로 1point 줄임 } private let singerLabel = UILabel().then { - $0.textColor = .gray500 + $0.textColor = .gray600 $0.font = .systemFont(ofSize: 13) /// g, y, p 같은 문자 이슈로 1point 줄임 } @@ -50,7 +49,7 @@ final class FeedDetailTableViewCell: UITableViewCell { numberLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(20) make.centerY.equalToSuperview() - make.width.equalTo(16) + make.width.equalTo(20) make.height.equalTo(14) } @@ -65,6 +64,7 @@ final class FeedDetailTableViewCell: UITableViewCell { self.contentView.addSubview(songTitleLabel) songTitleLabel.snp.makeConstraints { make in make.top.equalToSuperview().offset(15) + make.trailing.equalToSuperview().inset(24) make.leading.equalTo(thumbnailImageView.snp.trailing).offset(12) make.height.equalTo(16) } diff --git a/PLUV/Feed/FeedDetailViewController.swift b/PLUV/Feed/FeedDetailViewController.swift index 98bf345..9d82d23 100644 --- a/PLUV/Feed/FeedDetailViewController.swift +++ b/PLUV/Feed/FeedDetailViewController.swift @@ -77,10 +77,10 @@ class FeedDetailViewController: UIViewController, SaveMoveViewFeedDelegate { setSaveAPI() } - // override func viewDidLayoutSubviews() { - // super.viewDidLayoutSubviews() - // setTableViewHeight() /// 레이아웃이 갱신될 때마다 테이블 뷰 높이 갱신 - // } + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + setTableViewHeight() + } private func setUI() { self.view.backgroundColor = .white @@ -173,11 +173,13 @@ class FeedDetailViewController: UIViewController, SaveMoveViewFeedDelegate { } private func setTableViewHeight() { + feedDetailTableView.layoutIfNeeded() + let contentHeight = feedDetailTableView.contentSize.height - feedDetailTableViewHeightConstraint?.update(offset: contentHeight + 300) + feedDetailTableViewHeightConstraint?.update(offset: contentHeight + 100) /// 이미지 높이 + 테이블 뷰 높이를 합산하여 스크롤뷰의 contentSize 설정 - let totalHeight = feedDetailImageView.frame.height + feedDetailTitleView.frame.height + 10 + contentHeight + 101 + let totalHeight = navigationbarView.frame.height + feedDetailImageView.frame.height + feedDetailTitleView.frame.height + contentHeight scrollView.contentSize = CGSize(width: view.frame.width, height: totalHeight) scrollView.layoutIfNeeded() } @@ -206,6 +208,7 @@ class FeedDetailViewController: UIViewController, SaveMoveViewFeedDelegate { case 200: self.viewModel.selectFeedMusicItem.accept(response.data) self.setData() + self.view.layoutIfNeeded() default: AlertController(message: response.msg).show() } @@ -223,13 +226,6 @@ class FeedDetailViewController: UIViewController, SaveMoveViewFeedDelegate { cell.prepare(music: music, index: index) } .disposed(by: disposeBag) - - /// 데이터 로드 후 레이아웃 강제 업데이트 - DispatchQueue.main.async { - self.feedDetailTableView.reloadData() - self.feedDetailTableView.layoutIfNeeded() - self.setTableViewHeight() - } } func setFeedSaveAPI() { diff --git a/PLUV/Move/MovePlaylistViewController.swift b/PLUV/Move/MovePlaylistViewController.swift index 0c6a8dd..f9cb77c 100644 --- a/PLUV/Move/MovePlaylistViewController.swift +++ b/PLUV/Move/MovePlaylistViewController.swift @@ -62,7 +62,7 @@ class MovePlaylistViewController: UIViewController { $0.font = .systemFont(ofSize: 18) } private var platformLabel = UILabel().then { - $0.textColor = .subBlue + $0.textColor = .gray800 $0.font = .systemFont(ofSize: 14) $0.textAlignment = .center } @@ -90,7 +90,9 @@ class MovePlaylistViewController: UIViewController { setUI() setPlaylistData() - searchAPI() + Task { + await self.addSpotifyToApple(musicIdsArr: completeArr) + } circleLoadingIndicator.isAnimating = true @@ -238,125 +240,6 @@ class MovePlaylistViewController: UIViewController { } } - private func searchAPI() { - if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic && destinationPlatform == .Spotify { - /// 권한이 부여된 경우에만 넘겨야함!!! - Task { - await self.searchAppleToSpotifyAPI(musics: self.viewModel.musicItems) - } - } else if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .Spotify && destinationPlatform == .AppleMusic { - MPMediaLibrary.requestAuthorization { status in - switch status { - case .authorized: - /// 권한이 부여된 경우 - print("Apple Music authorization granted") - Task { - await self.searchSpotifyToAppleAPI(musics: self.viewModel.musicItems) - } - default: - DispatchQueue.main.async { - AlertController(message: "미디어 권한을 허용해야 사용할 수 있어요") { - UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) - }.show() - } - } - } - } - } - - /// 애플에 있는 것 스포티파이에서 검색 - private func searchAppleToSpotifyAPI(musics: [Music]) async { - do { - let jsonData = try JSONEncoder().encode(musics) - let jsonString = String(data: jsonData, encoding: .utf8) ?? "" - let musicParams = jsonString.replacingOccurrences(of: "\\", with: "").replacingOccurrences(of: "artistNames", with: "artistName") - - if let parameterJsonData = musicParams.data(using: .utf8) { - do { - if let parameterJsonArray = try JSONSerialization.jsonObject(with: parameterJsonData, options: []) as? [[String: Any]] { - - let url = EndPoint.musicSpotifySearch.path - let params = ["destinationAccessToken" : TokenManager.shared.spotifyAccessToken, - "musics" : parameterJsonArray] as [String : Any] - - APIService().post(of: APIResponse<[Search]>.self, url: url, parameters: params) { response in - switch response.code { - case 200: - var idArr: [String] = [] - let searchArr: [Search] = response.data - for search in searchArr { - if search.isEqual == true { - idArr.append(search.destinationMusics.first!.id!) - } else { - idArr.append(search.destinationMusics.first!.id!) - } - } - print(response.data, "애플에 있는 것 스포티파이에서 검색") - self.addAppleToSpotify(musicIdsArr: idArr) - default: - AlertController(message: response.msg).show() - } - } - } - } catch { - print("JSON 변환 실패: \(error.localizedDescription)") - } - } - } catch { - print(error) - } - } - - /// 스포티파이에 있는 것 애플에서 검색 - private func searchSpotifyToAppleAPI(musics: [Music]) async { - do { - let developerToken = try await DefaultMusicTokenProvider.init().developerToken(options: .ignoreCache) - let userToken = try await MusicUserTokenProvider.init().userToken(for: developerToken, options: .ignoreCache) - - let jsonData = try JSONEncoder().encode(musics) - let jsonString = String(data: jsonData, encoding: .utf8) ?? "" - let musicParams = jsonString.replacingOccurrences(of: "\\", with: "").replacingOccurrences(of: "artistNames", with: "artistName") - - if let parameterJsonData = musicParams.data(using: .utf8) { - do { - if let parameterJsonArray = try JSONSerialization.jsonObject(with: parameterJsonData, options: []) as? [[String: Any]] { - - let url = EndPoint.musicAppleSearch.path - let params = ["destinationAccessToken" : userToken, - "musics" : parameterJsonArray] as [String : Any] - print(params, "파람 확인") - APIService().post(of: APIResponse<[Search]>.self, url: url, parameters: params) { response in - switch response.code { - case 200: - var idArr: [String] = [] - let searchArr: [Search] = response.data - for search in searchArr { - if search.isEqual == true { - idArr.append(search.destinationMusics.first!.id!) - } else { - if let id = search.destinationMusics.first?.id { - idArr.append(id) - } - } - } - print(response.data, "스포티파이에 있는 것 애플에서 검색") - Task { - await self.addSpotifyToApple(musicIdsArr: idArr) - } - default: - AlertController(message: response.msg).show() - } - } - } - } catch { - print("JSON 변환 실패: \(error.localizedDescription)") - } - } - } catch { - print(error) - } - } - /// 애플에 있는 것 스포티파이에 등록 private func addAppleToSpotify(musicIdsArr: [String]) { let loginToken = UserDefaults.standard.string(forKey: APIService.shared.loginAccessTokenKey)! @@ -399,12 +282,12 @@ class MovePlaylistViewController: UIViewController { let url = EndPoint.musicAppleAdd.path let params = [ - "playListName": self.viewModel.playlistItem?.name ?? "", + "playListName": self.saveViewModel.saveItem?.title ?? "", "destinationAccessToken": musicUserToken, "musicIds": musicIdsArr, "transferFailMusics": [ ], - "thumbNailUrl": self.viewModel.playlistItem?.thumbnailURL ?? "", + "thumbNailUrl": self.saveViewModel.saveItem?.thumbNailURL ?? "", "source": "spotify" ] as [String : Any] diff --git a/PLUV/Save/Cell/SaveSongsTableViewCell.swift b/PLUV/Save/Cell/SaveSongsTableViewCell.swift index 7588412..ac53bcf 100644 --- a/PLUV/Save/Cell/SaveSongsTableViewCell.swift +++ b/PLUV/Save/Cell/SaveSongsTableViewCell.swift @@ -47,7 +47,7 @@ class SaveSongsTableViewCell: UITableViewCell { indexLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(20) make.centerY.equalToSuperview() - make.width.equalTo(16) + make.width.equalTo(20) make.height.equalTo(14) } @@ -62,6 +62,7 @@ class SaveSongsTableViewCell: UITableViewCell { self.contentView.addSubview(nameLabel) nameLabel.snp.makeConstraints { make in make.top.equalToSuperview().offset(15) + make.trailing.equalToSuperview().inset(24) make.leading.equalTo(thumbnailImageView.snp.trailing).offset(12) make.height.equalTo(16) } diff --git a/PLUV/Save/SaveDetailViewController.swift b/PLUV/Save/SaveDetailViewController.swift index 47da75d..4f9e6a8 100644 --- a/PLUV/Save/SaveDetailViewController.swift +++ b/PLUV/Save/SaveDetailViewController.swift @@ -12,6 +12,7 @@ import RxCocoa import Alamofire protocol SaveMoveViewSaveDelegate: AnyObject { + func setFeedSaveAPI() func deleteFeedSaveAPI() func transferFeedSave() } @@ -221,6 +222,21 @@ class SaveDetailViewController: UIViewController, SaveMoveViewSaveDelegate { } } + func setFeedSaveAPI() { + guard let id = self.viewModel.selectSaveItem?.id else { return } + let loginToken = UserDefaults.standard.string(forKey: APIService.shared.loginAccessTokenKey)! + let url = EndPoint.feedIdSave(String(id)).path + + APIService().postWithAccessToken(of: APIResponse.self, url: url, parameters: nil, AccessToken: loginToken) { response in + switch response.code { + case 200: + print("피드 저장이 정상적으로 처리되었습니다.") + default: + AlertController(message: response.msg).show() + } + } + } + func deleteFeedSaveAPI() { guard let id = self.viewModel.selectSaveItem?.id else { return } let loginToken = UserDefaults.standard.string(forKey: APIService.shared.loginAccessTokenKey)! @@ -230,7 +246,6 @@ class SaveDetailViewController: UIViewController, SaveMoveViewSaveDelegate { switch response.code { case 200: print("피드 삭제가 정상적으로 처리되었습니다.") - self.saveMoveView.updateSaveButton() default: AlertController(message: response.msg).show() } diff --git a/PLUV/Select/SelectMeViewModel.swift b/PLUV/Select/SelectMeViewModel.swift index da7013b..c7d7441 100644 --- a/PLUV/Select/SelectMeViewModel.swift +++ b/PLUV/Select/SelectMeViewModel.swift @@ -9,30 +9,9 @@ import Foundation import RxSwift import RxCocoa -class SelectMeViewModel { +class SelectMeViewModel: SelectMusicViewModelProtocol { var meItem: Me? var musicItem = BehaviorRelay<[Music]>(value: []) let selectedMusic = BehaviorRelay<[Music]>(value: []) let disposeBag = DisposeBag() - - func musicItemCount(completion: @escaping (Int) -> Void) { - musicItem - .map { $0.count } - .subscribe(onNext: { count in - completion(count) - }) - .disposed(by: disposeBag) - } - - func musicSelect(music: Music) { - var currentSelect = selectedMusic.value - - if let index = currentSelect.firstIndex(where: { $0.title == music.title && $0.artistNames == music.artistNames }) { - currentSelect.remove(at: index) - } else { - currentSelect.append(music) - } - - selectedMusic.accept(currentSelect) - } } diff --git a/PLUV/Select/SelectMusicProtocol.swift b/PLUV/Select/SelectMusicProtocol.swift new file mode 100644 index 0000000..122636f --- /dev/null +++ b/PLUV/Select/SelectMusicProtocol.swift @@ -0,0 +1,42 @@ +// +// SelectMusicProtocol.swift +// PLUV +// +// Created by jaegu park on 11/24/24. +// + +import Foundation +import RxSwift +import RxCocoa + +protocol SelectMusicViewModelProtocol { + var musicItem: BehaviorRelay<[Music]> { get } + var selectedMusic: BehaviorRelay<[Music]> { get } + var disposeBag: DisposeBag { get } + + func musicItemCount(completion: @escaping (Int) -> Void) + func musicSelect(music: Music) +} + +extension SelectMusicViewModelProtocol { + func musicItemCount(completion: @escaping (Int) -> Void) { + musicItem + .map { $0.count } + .subscribe(onNext: { count in + completion(count) + }) + .disposed(by: disposeBag) + } + + func musicSelect(music: Music) { + var currentSelect = selectedMusic.value + + if let index = currentSelect.firstIndex(where: { $0.title == music.title && $0.artistNames == music.artistNames }) { + currentSelect.remove(at: index) + } else { + currentSelect.append(music) + } + + selectedMusic.accept(currentSelect) + } +} diff --git a/PLUV/Select/SelectMusicViewController.swift b/PLUV/Select/SelectMusicViewController.swift index 73c9c15..7ffffaa 100644 --- a/PLUV/Select/SelectMusicViewController.swift +++ b/PLUV/Select/SelectMusicViewController.swift @@ -9,339 +9,400 @@ import UIKit import SnapKit import RxSwift import RxCocoa +import MediaPlayer import MusicKit class SelectMusicViewController: UIViewController { - - var completeArr: [String] = [] - var successArr: [SearchMusic] = [] - var successSimilarArr: [SearchMusic] = [] - var failArr: [SearchMusic] = [] - var searchArr: [Search] = [] - - var viewModel = SelectMusicViewModel() - var meViewModel = SelectMeViewModel() - var saveViewModel = SelectSaveViewModel() - - let loadingView = LoadingView(loadingState: .LoadMusic) - let searchLoadingView = LoadingView(loadingState: .SearchMusic) - - var sourcePlatform: PlatformRepresentable? - var destinationPlatform: MusicPlatform = .Spotify - - private let scrollView = UIScrollView() - private let contentView = UIView() - - private let selectMusicTitleView = UIView() - private let sourceToDestinationLabel = UILabel().then { - $0.font = .systemFont(ofSize: 14, weight: .medium) - $0.textColor = .gray800 - } - private let backButton = UIButton().then { - $0.setImage(UIImage(named: "xbutton_icon"), for: .normal) - } - private let progressView = CustomProgressView() - private let playlistTitleLabel = UILabel().then { - $0.text = "플레이리스트의 음악이\n일치하는지 확인해주세요" - $0.numberOfLines = 2 - $0.font = .systemFont(ofSize: 24, weight: .semibold) - $0.textColor = .gray800 - } - private let playlistView = UIView() - private let playlistThumnailImageView = UIImageView().then { - $0.layer.borderColor = UIColor(white: 0, alpha: 0.1).cgColor - $0.layer.cornerRadius = 8 - $0.layer.borderWidth = 0.5 - $0.clipsToBounds = true - } - private let sourcePlatformLabel = UILabel().then { - $0.textColor = .gray600 - $0.font = .systemFont(ofSize: 14) - } - private let playlistMenuImageView = UIImageView().then { - $0.image = UIImage(named: "menu_image") - } - private let playlistNameLabel = UILabel().then { - $0.textColor = .gray800 - $0.font = .systemFont(ofSize: 18, weight: .medium) - } - private let playlistSongCountLabel = UILabel().then { - $0.textColor = .gray500 - $0.font = .systemFont(ofSize: 14) - } - - private let selectSongView = UIView() - private let songCountLabel = UILabel().then { - $0.textColor = .gray700 - $0.font = .systemFont(ofSize: 14) - } - private let selectAllLabel = UILabel().then { - $0.textColor = .mainPurple //.gray800 - $0.font = .systemFont(ofSize: 14) - $0.text = "전체선택" - } - private let checkImageView = UIImageView().then { - $0.image = UIImage(named: "check_image") - } - - private let selectMusicTableView = UITableView().then { - $0.separatorStyle = .none - $0.register(SelectMusicTableViewCell.self, forCellReuseIdentifier: SelectMusicTableViewCell.identifier) - } - - private var moveView = MoveView(view: UIViewController()) - private let disposeBag = DisposeBag() - - override func viewDidLoad() { - super.viewDidLoad() - - setUI() - setMusicListAPI() - } + + var completeArr: [String] = [] + var successArr: [SearchMusic] = [] + var successSimilarArr: [SearchMusic] = [] + var failArr: [SearchMusic] = [] + var searchArr: [Search] = [] + + var viewModel = SelectMusicViewModel() + var meViewModel = SelectMeViewModel() + var saveViewModel = SelectSaveViewModel() + + let loadingView = LoadingView(loadingState: .LoadMusic) + let searchLoadingView = LoadingView(loadingState: .SearchMusic) + + var sourcePlatform: PlatformRepresentable? + var destinationPlatform: MusicPlatform = .Spotify + + private let scrollView = UIScrollView() + private let contentView = UIView() + + private let selectMusicTitleView = UIView() + private let sourceToDestinationLabel = UILabel().then { + $0.font = .systemFont(ofSize: 14, weight: .medium) + $0.textColor = .gray800 + } + private let backButton = UIButton().then { + $0.setImage(UIImage(named: "xbutton_icon"), for: .normal) + } + private let progressView = CustomProgressView() + private let playlistTitleLabel = UILabel().then { + $0.text = "플레이리스트의 음악이\n일치하는지 확인해주세요" + $0.numberOfLines = 2 + $0.font = .systemFont(ofSize: 24, weight: .semibold) + $0.textColor = .gray800 + } + private let playlistView = UIView() + private let playlistThumnailImageView = UIImageView().then { + $0.layer.borderColor = UIColor(white: 0, alpha: 0.1).cgColor + $0.layer.cornerRadius = 8 + $0.layer.borderWidth = 0.5 + $0.clipsToBounds = true + } + private let sourcePlatformLabel = UILabel().then { + $0.textColor = .gray600 + $0.font = .systemFont(ofSize: 14) + } + private let playlistMenuImageView = UIImageView().then { + $0.image = UIImage(named: "menu_image") + } + private let playlistNameLabel = UILabel().then { + $0.textColor = .gray800 + $0.font = .systemFont(ofSize: 18, weight: .medium) + } + private let playlistSongCountLabel = UILabel().then { + $0.textColor = .gray500 + $0.font = .systemFont(ofSize: 14) + } + + private let selectSongView = UIView() + private let songCountLabel = UILabel().then { + $0.textColor = .gray700 + $0.font = .systemFont(ofSize: 14) + } + private let selectAllLabel = UILabel().then { + $0.textColor = .mainPurple //.gray800 + $0.font = .systemFont(ofSize: 14) + $0.text = "전체선택" + } + private let checkImageView = UIImageView().then { + $0.image = UIImage(named: "check_image") + } + + private let selectMusicTableView = UITableView().then { + $0.separatorStyle = .none + $0.register(SelectMusicTableViewCell.self, forCellReuseIdentifier: SelectMusicTableViewCell.identifier) + } + + private var moveView = MoveView(view: UIViewController()) + private let disposeBag = DisposeBag() + + override func viewDidLoad() { + super.viewDidLoad() + + setUI() + setMusicListAPI() + } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationController?.setNavigationBarHidden(true, animated: false) } - - private func setUI() { - self.view.backgroundColor = .white - self.navigationItem.setHidesBackButton(true, animated: false) - - self.view.addSubview(selectMusicTitleView) - selectMusicTitleView.snp.makeConstraints { make in - make.top.leading.trailing.equalToSuperview() - make.height.equalTo(221) - } - - self.selectMusicTitleView.addSubview(sourceToDestinationLabel) - sourceToDestinationLabel.snp.makeConstraints { make in - make.top.equalToSuperview().inset(47) - make.leading.trailing.equalToSuperview().inset(24) - make.height.equalTo(46) - } - - self.selectMusicTitleView.addSubview(backButton) - backButton.snp.makeConstraints { make in - make.top.equalToSuperview().inset(53) - make.trailing.equalToSuperview().inset(20) - make.height.equalTo(34) - make.width.equalTo(34) - } - backButton.addTarget(self, action: #selector(clickXButton), for: .touchUpInside) - - self.selectMusicTitleView.addSubview(progressView) - progressView.snp.makeConstraints { make in - make.top.equalTo(backButton.snp.bottom).offset(6) - make.trailing.leading.equalToSuperview() - make.height.equalTo(4) - } - progressView.updateProgress(to: 0.625) - - self.selectMusicTitleView.addSubview(playlistTitleLabel) - playlistTitleLabel.snp.makeConstraints { make in - make.top.equalTo(progressView.snp.bottom).offset(24) - make.leading.trailing.equalToSuperview().inset(24) - make.height.equalTo(68) - } - - self.view.addSubview(playlistView) - playlistView.snp.makeConstraints { make in - make.leading.trailing.equalToSuperview() - make.top.equalTo(selectMusicTitleView.snp.bottom) - make.height.equalTo(110) - } - - self.playlistView.addSubview(playlistThumnailImageView) - playlistThumnailImageView.snp.makeConstraints { make in - make.width.height.equalTo(86) - make.centerY.equalToSuperview() - make.leading.equalToSuperview().offset(24) - } - - self.playlistView.addSubview(playlistMenuImageView) - playlistMenuImageView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(14) - make.leading.equalTo(playlistThumnailImageView.snp.trailing).offset(12) - make.width.height.equalTo(20) - } - - self.playlistView.addSubview(playlistNameLabel) - playlistNameLabel.snp.makeConstraints { make in - make.leading.equalTo(playlistMenuImageView.snp.trailing).offset(4) - make.centerY.equalTo(playlistMenuImageView) - make.trailing.equalToSuperview().inset(24) - } - - self.playlistView.addSubview(sourcePlatformLabel) - sourcePlatformLabel.snp.makeConstraints { make in - make.top.equalTo(playlistNameLabel.snp.bottom).offset(12) - make.leading.equalTo(playlistThumnailImageView.snp.trailing).offset(12) - make.height.equalTo(14) - } - - self.playlistView.addSubview(playlistSongCountLabel) - playlistSongCountLabel.snp.makeConstraints { make in - make.top.equalTo(playlistNameLabel.snp.bottom).offset(12) - make.leading.equalTo(sourcePlatformLabel.snp.trailing).offset(4) - make.height.equalTo(14) - } - - self.view.addSubview(selectSongView) - selectSongView.snp.makeConstraints { make in - make.top.equalTo(playlistView.snp.bottom) - make.leading.trailing.equalToSuperview() - make.height.equalTo(38) - } - - self.selectSongView.addSubview(songCountLabel) - songCountLabel.snp.makeConstraints { make in - make.leading.equalToSuperview().offset(24) - make.centerY.equalToSuperview() - make.height.equalTo(14) - } - - self.selectSongView.addSubview(selectAllLabel) - selectAllLabel.snp.makeConstraints { make in - make.trailing.equalToSuperview().inset(24) - make.centerY.equalToSuperview() - make.height.equalTo(14) - } - - self.selectSongView.addSubview(checkImageView) - checkImageView.snp.makeConstraints { make in - make.trailing.equalTo(selectAllLabel.snp.leading).offset(-6) - make.centerY.equalToSuperview() - make.width.height.equalTo(16) - } - - self.view.addSubview(selectMusicTableView) - selectMusicTableView.snp.makeConstraints { make in - make.top.equalTo(selectSongView.snp.bottom) - make.leading.trailing.equalToSuperview() - make.height.equalTo(660) - make.bottom.equalTo(view).inset(101) - } - //selectMusicTableView.isScrollEnabled = false - - moveView = MoveView(view: self) - self.view.addSubview(moveView) - moveView.snp.makeConstraints { make in - make.leading.trailing.bottom.equalToSuperview() - make.height.equalTo(101) - } - - self.view.addSubview(loadingView) - loadingView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - setButtons() - } - - private func setButtons() { - setSelectAllButton() - bindtrasferButton() - } - - @objc private func clickXButton() { - let moveStopView = MoveStopView(title: "지금 중단하면 진행 사항이 사라져요.", target: self, num: 6) - - self.view.addSubview(moveStopView) - moveStopView.alpha = 0 - moveStopView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - UIView.animate(withDuration: 0.3) { - moveStopView.alpha = 1 - } - } - - private func setSelectAllButton() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickSelectAllButton)) - selectAllLabel.addGestureRecognizer(tapGesture) - selectAllLabel.isUserInteractionEnabled = true - } - - @objc private func clickSelectAllButton() { - if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic || musicPlatform == .Spotify { - /// 모든 셀을 선택할지 해제할지 결정 - let allSelected = viewModel.selectedMusic.value.count == viewModel.musicItem.value.count - - if allSelected { - /// 모두 선택 해제 - viewModel.selectedMusic.accept([]) - } else { - /// 모두 선택 - viewModel.selectedMusic.accept(viewModel.musicItem.value) - } - self.selectMusicTableView.reloadData() - } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { - let allSelected = meViewModel.selectedMusic.value.count == meViewModel.musicItem.value.count - - if allSelected { - /// 모두 선택 해제 - meViewModel.selectedMusic.accept([]) - } else { - /// 모두 선택 - meViewModel.selectedMusic.accept(meViewModel.musicItem.value) - } - self.selectMusicTableView.reloadData() - } else { - let allSelected = saveViewModel.selectedMusic.value.count == saveViewModel.musicItem.value.count - - if allSelected { - /// 모두 선택 해제 - saveViewModel.selectedMusic.accept([]) - } else { - /// 모두 선택 - saveViewModel.selectedMusic.accept(saveViewModel.musicItem.value) - } - self.selectMusicTableView.reloadData() - } - } - - private func bindtrasferButton() { - moveView.trasferButton.addTarget(self, action: #selector(clickTransferButton), for: .touchUpInside) - - if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic || musicPlatform == .Spotify { - /// selectedMusic의 변화를 관찰하여 trasferButton의 활성화 상태를 업데이트 - self.viewModel.selectedMusic - .map { !$0.isEmpty } /// 선택된 음악이 있으면 true, 없으면 false - .bind(to: moveView.trasferButton.rx.isEnabled) - .disposed(by: disposeBag) - } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { - self.meViewModel.selectedMusic - .map { !$0.isEmpty } /// 선택된 음악이 있으면 true, 없으면 false - .bind(to: moveView.trasferButton.rx.isEnabled) - .disposed(by: disposeBag) - } else { - self.saveViewModel.selectedMusic - .map { !$0.isEmpty } /// 선택된 음악이 있으면 true, 없으면 false - .bind(to: moveView.trasferButton.rx.isEnabled) - .disposed(by: disposeBag) - } - } - - private func goNextStep() { - if self.searchArr.count == self.completeArr.count { - self.setValidationView(title: "플레이리스트의 모든 음악을 찾았어요!", image: "ok_image") - let movePlaylistVC = MovePlaylistViewController(musicArr: completeArr, source: self.sourcePlatform!, destination: self.destinationPlatform) - movePlaylistVC.viewModel.playlistItem = viewModel.playlistItem - movePlaylistVC.meViewModel.meItem = meViewModel.meItem - movePlaylistVC.saveViewModel.saveItem = saveViewModel.saveItem - self.navigationController?.pushViewController(movePlaylistVC, animated: true) - } else { - self.setValidationView(title: "앗, 찾을 수 없는 곡이 몇 개 있네요!", image: "alert_image") - let validationSimilarVC = ValidationSimilarViewController(completeArr: completeArr, successArr: successArr, successSimilarArr: successSimilarArr, failArr: failArr) - validationSimilarVC.viewModel.playlistItem = viewModel.playlistItem - validationSimilarVC.meViewModel.meItem = meViewModel.meItem - validationSimilarVC.saveViewModel.saveItem = saveViewModel.saveItem - self.navigationController?.pushViewController(validationSimilarVC, animated: true) - } - } - + + private func setUI() { + self.view.backgroundColor = .white + self.navigationItem.setHidesBackButton(true, animated: false) + + self.view.addSubview(selectMusicTitleView) + selectMusicTitleView.snp.makeConstraints { make in + make.top.leading.trailing.equalToSuperview() + make.height.equalTo(221) + } + + self.selectMusicTitleView.addSubview(sourceToDestinationLabel) + sourceToDestinationLabel.snp.makeConstraints { make in + make.top.equalToSuperview().inset(47) + make.leading.trailing.equalToSuperview().inset(24) + make.height.equalTo(46) + } + + self.selectMusicTitleView.addSubview(backButton) + backButton.snp.makeConstraints { make in + make.top.equalToSuperview().inset(53) + make.trailing.equalToSuperview().inset(20) + make.height.equalTo(34) + make.width.equalTo(34) + } + backButton.addTarget(self, action: #selector(clickXButton), for: .touchUpInside) + + self.selectMusicTitleView.addSubview(progressView) + progressView.snp.makeConstraints { make in + make.top.equalTo(backButton.snp.bottom).offset(6) + make.trailing.leading.equalToSuperview() + make.height.equalTo(4) + } + progressView.updateProgress(to: 0.625) + + self.selectMusicTitleView.addSubview(playlistTitleLabel) + playlistTitleLabel.snp.makeConstraints { make in + make.top.equalTo(progressView.snp.bottom).offset(24) + make.leading.trailing.equalToSuperview().inset(24) + make.height.equalTo(68) + } + + self.view.addSubview(playlistView) + playlistView.snp.makeConstraints { make in + make.leading.trailing.equalToSuperview() + make.top.equalTo(selectMusicTitleView.snp.bottom) + make.height.equalTo(110) + } + + self.playlistView.addSubview(playlistThumnailImageView) + playlistThumnailImageView.snp.makeConstraints { make in + make.width.height.equalTo(86) + make.centerY.equalToSuperview() + make.leading.equalToSuperview().offset(24) + } + + self.playlistView.addSubview(playlistMenuImageView) + playlistMenuImageView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(14) + make.leading.equalTo(playlistThumnailImageView.snp.trailing).offset(12) + make.width.height.equalTo(20) + } + + self.playlistView.addSubview(playlistNameLabel) + playlistNameLabel.snp.makeConstraints { make in + make.leading.equalTo(playlistMenuImageView.snp.trailing).offset(4) + make.centerY.equalTo(playlistMenuImageView) + make.trailing.equalToSuperview().inset(24) + } + + self.playlistView.addSubview(sourcePlatformLabel) + sourcePlatformLabel.snp.makeConstraints { make in + make.top.equalTo(playlistNameLabel.snp.bottom).offset(12) + make.leading.equalTo(playlistThumnailImageView.snp.trailing).offset(12) + make.height.equalTo(14) + } + + self.playlistView.addSubview(playlistSongCountLabel) + playlistSongCountLabel.snp.makeConstraints { make in + make.top.equalTo(playlistNameLabel.snp.bottom).offset(12) + make.leading.equalTo(sourcePlatformLabel.snp.trailing).offset(4) + make.height.equalTo(14) + } + + self.view.addSubview(selectSongView) + selectSongView.snp.makeConstraints { make in + make.top.equalTo(playlistView.snp.bottom) + make.leading.trailing.equalToSuperview() + make.height.equalTo(38) + } + + self.selectSongView.addSubview(songCountLabel) + songCountLabel.snp.makeConstraints { make in + make.leading.equalToSuperview().offset(24) + make.centerY.equalToSuperview() + make.height.equalTo(14) + } + + self.selectSongView.addSubview(selectAllLabel) + selectAllLabel.snp.makeConstraints { make in + make.trailing.equalToSuperview().inset(24) + make.centerY.equalToSuperview() + make.height.equalTo(14) + } + + self.selectSongView.addSubview(checkImageView) + checkImageView.snp.makeConstraints { make in + make.trailing.equalTo(selectAllLabel.snp.leading).offset(-6) + make.centerY.equalToSuperview() + make.width.height.equalTo(16) + } + + self.view.addSubview(selectMusicTableView) + selectMusicTableView.snp.makeConstraints { make in + make.top.equalTo(selectSongView.snp.bottom) + make.leading.trailing.equalToSuperview() + make.height.equalTo(660) + make.bottom.equalTo(view).inset(101) + } + + moveView = MoveView(view: self) + self.view.addSubview(moveView) + moveView.snp.makeConstraints { make in + make.leading.trailing.bottom.equalToSuperview() + make.height.equalTo(101) + } + + self.view.addSubview(loadingView) + loadingView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + setButtons() + } + + private func setButtons() { + setSelectAllButton() + bindtrasferButton() + } + + private func setSearchView() { + let searchLoadingView = LoadingView(loadingState: .SearchMusic) + + self.view.addSubview(searchLoadingView) + searchLoadingView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + private func setValidationView(title: String, image: String) { + let validationView = ValidationView(title: title, image: image) + + self.view.addSubview(validationView) + validationView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + validationView.removeFromSuperview() + } + } + + private func setPlaylistData() { + if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic || musicPlatform == .Spotify { + let thumbnailURL = URL(string: self.viewModel.playlistItem?.thumbnailURL ?? "") + playlistThumnailImageView.kf.setImage(with: thumbnailURL) + sourceToDestinationLabel.text = sourcePlatform!.name + " > " + destinationPlatform.name + sourcePlatformLabel.text = sourcePlatform!.name + playlistNameLabel.text = self.viewModel.playlistItem?.name + self.viewModel.musicItemCount { count in + self.playlistSongCountLabel.text = "총 \(count)곡" + self.songCountLabel.text = "\(count)곡" + } + } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { + let thumbnailURL = URL(string: self.meViewModel.meItem?.imageURL ?? "") + playlistThumnailImageView.kf.setImage(with: thumbnailURL) + sourceToDestinationLabel.text = sourcePlatform!.name + " > " + destinationPlatform.name + sourcePlatformLabel.text = sourcePlatform!.name + playlistNameLabel.text = self.meViewModel.meItem?.title + self.meViewModel.musicItemCount { count in + self.playlistSongCountLabel.text = "총 \(count)곡" + self.songCountLabel.text = "\(count)곡" + } + } else { + let thumbnailURL = URL(string: self.saveViewModel.saveItem?.thumbNailURL ?? "") + playlistThumnailImageView.kf.setImage(with: thumbnailURL) + sourceToDestinationLabel.text = sourcePlatform!.name + " > " + destinationPlatform.name + sourcePlatformLabel.text = sourcePlatform!.name + playlistNameLabel.text = self.saveViewModel.saveItem?.title + self.saveViewModel.musicItemCount { count in + self.playlistSongCountLabel.text = "총 \(count)곡" + self.songCountLabel.text = "\(count)곡" + } + } + } + + @objc private func clickXButton() { + let moveStopView = MoveStopView(title: "지금 중단하면 진행 사항이 사라져요.", target: self, num: 6) + + self.view.addSubview(moveStopView) + moveStopView.alpha = 0 + moveStopView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + UIView.animate(withDuration: 0.3) { + moveStopView.alpha = 1 + } + } + + private func setSelectAllButton() { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(clickSelectAllButton)) + selectAllLabel.addGestureRecognizer(tapGesture) + selectAllLabel.isUserInteractionEnabled = true + } + + @objc private func clickSelectAllButton() { + if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic || musicPlatform == .Spotify { + /// 모든 셀을 선택할지 해제할지 결정 + let allSelected = viewModel.selectedMusic.value.count == viewModel.musicItem.value.count + + if allSelected { + /// 모두 선택 해제 + viewModel.selectedMusic.accept([]) + } else { + /// 모두 선택 + viewModel.selectedMusic.accept(viewModel.musicItem.value) + } + self.selectMusicTableView.reloadData() + } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { + let allSelected = meViewModel.selectedMusic.value.count == meViewModel.musicItem.value.count + + if allSelected { + /// 모두 선택 해제 + meViewModel.selectedMusic.accept([]) + } else { + /// 모두 선택 + meViewModel.selectedMusic.accept(meViewModel.musicItem.value) + } + self.selectMusicTableView.reloadData() + } else { + let allSelected = saveViewModel.selectedMusic.value.count == saveViewModel.musicItem.value.count + + if allSelected { + /// 모두 선택 해제 + saveViewModel.selectedMusic.accept([]) + } else { + /// 모두 선택 + saveViewModel.selectedMusic.accept(saveViewModel.musicItem.value) + } + self.selectMusicTableView.reloadData() + } + } + + private func bindtrasferButton() { + moveView.trasferButton.addTarget(self, action: #selector(clickTransferButton), for: .touchUpInside) + + if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic || musicPlatform == .Spotify { + /// selectedMusic의 변화를 관찰하여 trasferButton의 활성화 상태를 업데이트 + self.viewModel.selectedMusic + .map { !$0.isEmpty } /// 선택된 음악이 있으면 true, 없으면 false + .bind(to: moveView.trasferButton.rx.isEnabled) + .disposed(by: disposeBag) + } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { + self.meViewModel.selectedMusic + .map { !$0.isEmpty } /// 선택된 음악이 있으면 true, 없으면 false + .bind(to: moveView.trasferButton.rx.isEnabled) + .disposed(by: disposeBag) + } else { + self.saveViewModel.selectedMusic + .map { !$0.isEmpty } /// 선택된 음악이 있으면 true, 없으면 false + .bind(to: moveView.trasferButton.rx.isEnabled) + .disposed(by: disposeBag) + } + } + + private func goNextStep() { + if self.searchArr.count == self.completeArr.count { + self.setValidationView(title: "플레이리스트의 모든 음악을 찾았어요!", image: "ok_image") + /// 유효성 검사뷰 2초뒤 사라진 후 화면 넘어감 + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + let movePlaylistVC = MovePlaylistViewController(musicArr: self.completeArr, source: self.sourcePlatform!, destination: self.destinationPlatform) + movePlaylistVC.viewModel.playlistItem = self.viewModel.playlistItem + movePlaylistVC.meViewModel.meItem = self.meViewModel.meItem + movePlaylistVC.saveViewModel.saveItem = self.saveViewModel.saveItem + self.navigationController?.pushViewController(movePlaylistVC, animated: true) + } + } else { + self.setValidationView(title: "앗, 찾을 수 없는 곡이 몇 개 있네요!", image: "alert_image") + DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { + let validationSimilarVC = ValidationSimilarViewController(completeArr: self.completeArr, successArr: self.successArr, successSimilarArr: self.successSimilarArr, failArr: self.failArr) + validationSimilarVC.viewModel.playlistItem = self.viewModel.playlistItem + validationSimilarVC.meViewModel.meItem = self.meViewModel.meItem + validationSimilarVC.saveViewModel.saveItem = self.saveViewModel.saveItem + self.navigationController?.pushViewController(validationSimilarVC, animated: true) + } + } + } + @objc private func clickTransferButton() { guard let sourcePlatform = sourcePlatform else { return } @@ -371,396 +432,291 @@ class SelectMusicViewController: UIViewController { return } - selectedMusicObservable? - .map { musicArray in - Task { - await transferAPI(musicArray) - self.goNextStep() + if destinationPlatform == .AppleMusic { + selectedMusicObservable? + .map { musicArray in + MPMediaLibrary.requestAuthorization { status in + switch status { + case .authorized: + /// 권한이 부여된 경우 + print("Apple Music authorization granted") + Task { + await transferAPI(musicArray) + self.goNextStep() + } + default: + DispatchQueue.main.async { + AlertController(message: "미디어 권한을 허용해야 사용할 수 있어요") { + UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) + }.show() + } + } + } } + .subscribe { musicArray in + print(musicArray) + } + .disposed(by: disposeBag) + } else { + selectedMusicObservable? + .map { musicArray in + Task { + await transferAPI(musicArray) + self.goNextStep() + } + } + .subscribe { musicArray in + print(musicArray) + } + .disposed(by: disposeBag) + } + } + + private func configureTableView(for viewModel: SelectMusicViewModelProtocol) { + self.selectMusicTableView.rx.setDelegate(self) + .disposed(by: disposeBag) + + self.selectMusicTableView.rx.itemSelected + .subscribe(onNext: { [weak self] indexPath in + self?.selectMusicTableView.deselectRow(at: indexPath, animated: true) + }) + .disposed(by: disposeBag) + + viewModel.selectedMusic.accept(viewModel.musicItem.value) + + viewModel.musicItem + .observe(on: MainScheduler.instance) + .bind(to: self.selectMusicTableView.rx.items(cellIdentifier: SelectMusicTableViewCell.identifier, cellType: SelectMusicTableViewCell.self)) { row, music, cell in + cell.prepare(music: music) + + let isSelected = viewModel.selectedMusic.value.contains { + $0.title == music.title && $0.artistNames == music.artistNames + } + cell.updateSelectionUI(isSelected: isSelected) } - .subscribe { musicArray in - print(musicArray) - } + .disposed(by: disposeBag) + + self.selectMusicTableView.rx.modelSelected(Music.self) + .subscribe(onNext: { music in + viewModel.musicSelect(music: music) + self.selectMusicTableView.reloadData() + }) + .disposed(by: disposeBag) + + viewModel.selectedMusic + .subscribe(onNext: { [weak self] _ in + self?.selectMusicTableView.reloadData() + }) .disposed(by: disposeBag) } - - private func setPlaylistData() { - if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic || musicPlatform == .Spotify { - let thumbnailURL = URL(string: self.viewModel.playlistItem?.thumbnailURL ?? "") - playlistThumnailImageView.kf.setImage(with: thumbnailURL) - sourceToDestinationLabel.text = sourcePlatform!.name + " > " + destinationPlatform.name - sourcePlatformLabel.text = sourcePlatform!.name - playlistNameLabel.text = self.viewModel.playlistItem?.name - self.viewModel.musicItemCount { count in - self.playlistSongCountLabel.text = "총 \(count)곡" - self.songCountLabel.text = "\(count)곡" - } - } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { - let thumbnailURL = URL(string: self.meViewModel.meItem?.imageURL ?? "") - playlistThumnailImageView.kf.setImage(with: thumbnailURL) - sourceToDestinationLabel.text = sourcePlatform!.name + " > " + destinationPlatform.name - sourcePlatformLabel.text = sourcePlatform!.name - playlistNameLabel.text = self.meViewModel.meItem?.title - self.meViewModel.musicItemCount { count in - self.playlistSongCountLabel.text = "총 \(count)곡" - self.songCountLabel.text = "\(count)곡" - } - } else { - let thumbnailURL = URL(string: self.saveViewModel.saveItem?.thumbNailURL ?? "") - playlistThumnailImageView.kf.setImage(with: thumbnailURL) - sourceToDestinationLabel.text = sourcePlatform!.name + " > " + destinationPlatform.name - sourcePlatformLabel.text = sourcePlatform!.name - playlistNameLabel.text = self.saveViewModel.saveItem?.title - self.saveViewModel.musicItemCount { count in - self.playlistSongCountLabel.text = "총 \(count)곡" - self.songCountLabel.text = "\(count)곡" - } - } - } - - private func setData() { - self.selectMusicTableView.rx.setDelegate(self) - .disposed(by: disposeBag) - - /// 아이템 선택 시 스타일 제거 - self.selectMusicTableView.rx.itemSelected - .subscribe(onNext: { [weak self] indexPath in - self?.selectMusicTableView.deselectRow(at: indexPath, animated: true) - }) - .disposed(by: disposeBag) - - /// 모든 cell 선택된 상태로 세팅 - self.viewModel.selectedMusic.accept(viewModel.musicItem.value) - - /// TableView에 들어갈 Cell에 정보 제공 - self.viewModel.musicItem - .observe(on: MainScheduler.instance) - .bind(to: self.selectMusicTableView.rx.items(cellIdentifier: SelectMusicTableViewCell.identifier, cellType: SelectMusicTableViewCell.self)) { row, music, cell in - cell.prepare(music: music) - - /// 현재 음악이 선택된 상태인지 확인하고 UI 업데이트 - let isSelected = self.viewModel.selectedMusic.value.contains(where: { $0.title == music.title && $0.artistNames == music.artistNames }) - cell.updateSelectionUI(isSelected: isSelected) - } - .disposed(by: disposeBag) - - /// 셀 선택 처리 - self.selectMusicTableView.rx.modelSelected(Music.self) - .subscribe(onNext: { [weak self] music in - self?.viewModel.musicSelect(music: music) - self?.selectMusicTableView.reloadData() - }) - .disposed(by: disposeBag) - - /// 선택한 음악의 변화를 관찰하고 이에 따라 UI를 업데이트 - self.viewModel.selectedMusic - .subscribe(onNext: { [weak self] selectedMusic in - self?.selectMusicTableView.reloadData() - }) - .disposed(by: disposeBag) - } - - private func setMeData() { - self.selectMusicTableView.rx.setDelegate(self) - .disposed(by: disposeBag) - - /// 아이템 선택 시 스타일 제거 - self.selectMusicTableView.rx.itemSelected - .subscribe(onNext: { [weak self] indexPath in - self?.selectMusicTableView.deselectRow(at: indexPath, animated: true) - }) - .disposed(by: disposeBag) - - /// 모든 cell 선택된 상태로 세팅 - self.meViewModel.selectedMusic.accept(meViewModel.musicItem.value) - - /// TableView에 들어갈 Cell에 정보 제공 - self.meViewModel.musicItem - .observe(on: MainScheduler.instance) - .bind(to: self.selectMusicTableView.rx.items(cellIdentifier: SelectMusicTableViewCell.identifier, cellType: SelectMusicTableViewCell.self)) { row, music, cell in - cell.prepare(music: music) + + private func setData() { + configureTableView(for: viewModel) + } + + private func setMeData() { + configureTableView(for: meViewModel) + } + + private func setSaveData() { + configureTableView(for: saveViewModel) + } + + private func setMusicListAPI() { + if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic { + Task { + await self.setAppleMusicListAPI() + } + } else if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .Spotify { + setSpotifyMusicListAPI() + } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { + setMeMusicListAPI() + } else { + setSaveMusicListAPI() + } + } + + private func setAppleMusicListAPI() async { + do { + let developerToken = try await DefaultMusicTokenProvider.init().developerToken(options: .ignoreCache) + let userToken = try await MusicUserTokenProvider.init().userToken(for: developerToken, options: .ignoreCache) - /// 현재 음악이 선택된 상태인지 확인하고 UI 업데이트 - let isSelected = self.meViewModel.selectedMusic.value.contains(where: { $0.title == music.title && $0.artistNames == music.artistNames }) - cell.updateSelectionUI(isSelected: isSelected) - } - .disposed(by: disposeBag) - - /// 셀 선택 처리 - self.selectMusicTableView.rx.modelSelected(Music.self) - .subscribe(onNext: { [weak self] music in - self?.meViewModel.musicSelect(music: music) - self?.selectMusicTableView.reloadData() - }) - .disposed(by: disposeBag) - - /// 선택한 음악의 변화를 관찰하고 이에 따라 UI를 업데이트 - self.meViewModel.selectedMusic - .subscribe(onNext: { [weak self] selectedMusic in - self?.selectMusicTableView.reloadData() - }) - .disposed(by: disposeBag) - } - - private func setSaveData() { - self.selectMusicTableView.rx.setDelegate(self) - .disposed(by: disposeBag) - - /// 아이템 선택 시 스타일 제거 - self.selectMusicTableView.rx.itemSelected - .subscribe(onNext: { [weak self] indexPath in - self?.selectMusicTableView.deselectRow(at: indexPath, animated: true) - }) - .disposed(by: disposeBag) - - /// 모든 cell 선택된 상태로 세팅 - self.saveViewModel.selectedMusic.accept(saveViewModel.musicItem.value) - - /// TableView에 들어갈 Cell에 정보 제공 - self.saveViewModel.musicItem - .observe(on: MainScheduler.instance) - .bind(to: self.selectMusicTableView.rx.items(cellIdentifier: SelectMusicTableViewCell.identifier, cellType: SelectMusicTableViewCell.self)) { row, music, cell in - cell.prepare(music: music) + let url = EndPoint.playlistAppleMusicRead(self.viewModel.playlistItem?.id ?? "").path + let params = ["musicUserToken" : userToken] - /// 현재 음악이 선택된 상태인지 확인하고 UI 업데이트 - let isSelected = self.saveViewModel.selectedMusic.value.contains(where: { $0.title == music.title && $0.artistNames == music.artistNames }) - cell.updateSelectionUI(isSelected: isSelected) - } - .disposed(by: disposeBag) - - /// 셀 선택 처리 - self.selectMusicTableView.rx.modelSelected(Music.self) - .subscribe(onNext: { [weak self] music in - self?.saveViewModel.musicSelect(music: music) - self?.selectMusicTableView.reloadData() - }) - .disposed(by: disposeBag) - - /// 선택한 음악의 변화를 관찰하고 이에 따라 UI를 업데이트 - self.saveViewModel.selectedMusic - .subscribe(onNext: { [weak self] selectedMusic in - self?.selectMusicTableView.reloadData() - }) - .disposed(by: disposeBag) - } - - private func setMusicListAPI() { - if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .AppleMusic { - Task { - await self.setAppleMusicListAPI() - } - } else if let musicPlatform = sourcePlatform as? MusicPlatform, musicPlatform == .Spotify { - setSpotifyMusicListAPI() - } else if let musicPlatform = sourcePlatform as? LoadPluv, musicPlatform == .FromRecent { - setMeMusicListAPI() - } else { - setSaveMusicListAPI() - } - } - - private func setAppleMusicListAPI() async { - do { - let developerToken = try await DefaultMusicTokenProvider.init().developerToken(options: .ignoreCache) - let userToken = try await MusicUserTokenProvider.init().userToken(for: developerToken, options: .ignoreCache) - - let url = EndPoint.playlistAppleMusicRead(self.viewModel.playlistItem?.id ?? "").path - let params = ["musicUserToken" : userToken] - - APIService().post(of: APIResponse<[Music]>.self, url: url, parameters: params) { response in + APIService().post(of: APIResponse<[Music]>.self, url: url, parameters: params) { response in + switch response.code { + case 200: + self.viewModel.musicItem.accept(response.data) + self.setData() + self.setPlaylistData() + self.loadingView.removeFromSuperview() + self.view.layoutIfNeeded() + default: + AlertController(message: response.msg).show() + } + } + } catch { + print("ERROR : setAppleMusicListAPI") + } + } + + private func setSpotifyMusicListAPI() { + let url = EndPoint.playlistMusicSpotifyRead(self.viewModel.playlistItem?.id ?? "").path + let params = ["accessToken" : TokenManager.shared.spotifyAccessToken] + + APIService().post(of: APIResponse<[Music]>.self, url: url, parameters: params) { response in + switch response.code { + case 200: + self.viewModel.musicItem.accept(response.data) + self.setData() + self.setPlaylistData() + self.loadingView.removeFromSuperview() + self.view.layoutIfNeeded() + default: + AlertController(message: response.msg).show() + } + } + } + + private func setMeMusicListAPI() { + let recentId = meViewModel.meItem?.id ?? 0 + let loginToken = UserDefaults.standard.string(forKey: APIService.shared.loginAccessTokenKey)! + let url = EndPoint.historySuccess("\(recentId)").path + + APIService().getWithAccessToken(of: APIResponse<[Music]>.self, url: url, AccessToken: loginToken) { response in + switch response.code { + case 200: + self.meViewModel.musicItem.accept(response.data) + self.setMeData() + self.setPlaylistData() + self.loadingView.removeFromSuperview() + self.view.layoutIfNeeded() + default: + AlertController(message: response.msg).show() + } + } + } + + private func setSaveMusicListAPI() { + let saveId = saveViewModel.saveItem?.id ?? 0 + let url = EndPoint.feedIdMusic("\(saveId)").path + + APIService().get(of: APIResponse<[Music]>.self, url: url) { response in switch response.code { case 200: - self.viewModel.musicItem.accept(response.data) - self.setData() - self.setPlaylistData() - self.loadingView.removeFromSuperview() - self.view.layoutIfNeeded() + self.saveViewModel.musicItem.accept(response.data) + self.setSaveData() + self.setPlaylistData() + self.loadingView.removeFromSuperview() + self.view.layoutIfNeeded() default: - AlertController(message: response.msg).show() + AlertController(message: response.msg).show() } - } - } catch { - print("ERROR : setAppleMusicListAPI") - } - } - - private func setSpotifyMusicListAPI() { - let url = EndPoint.playlistMusicSpotifyRead(self.viewModel.playlistItem?.id ?? "").path - let params = ["accessToken" : TokenManager.shared.spotifyAccessToken] - - APIService().post(of: APIResponse<[Music]>.self, url: url, parameters: params) { response in - switch response.code { - case 200: - self.viewModel.musicItem.accept(response.data) - self.setData() - self.setPlaylistData() - self.loadingView.removeFromSuperview() - self.view.layoutIfNeeded() - default: - AlertController(message: response.msg).show() - } - } - } - - private func setMeMusicListAPI() { - let recentId = meViewModel.meItem?.id ?? 0 - let loginToken = UserDefaults.standard.string(forKey: APIService.shared.loginAccessTokenKey)! - let url = EndPoint.historySuccess("\(recentId)").path - - APIService().getWithAccessToken(of: APIResponse<[Music]>.self, url: url, AccessToken: loginToken) { response in - switch response.code { - case 200: - self.meViewModel.musicItem.accept(response.data) - self.setMeData() - self.setPlaylistData() - self.loadingView.removeFromSuperview() - self.view.layoutIfNeeded() - default: - AlertController(message: response.msg).show() - } - } - } - - private func setSaveMusicListAPI() { - let saveId = saveViewModel.saveItem?.id ?? 0 - let url = EndPoint.feedIdMusic("\(saveId)").path - - APIService().get(of: APIResponse<[Music]>.self, url: url) { response in - switch response.code { - case 200: - self.saveViewModel.musicItem.accept(response.data) - self.setSaveData() - self.setPlaylistData() - self.loadingView.removeFromSuperview() - self.view.layoutIfNeeded() - default: - AlertController(message: response.msg).show() - } - } - } - - private func setSearchView() { - let searchLoadingView = LoadingView(loadingState: .SearchMusic) - - self.view.addSubview(searchLoadingView) - loadingView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - } - - private func setValidationView(title: String, image: String) { - let validationView = ValidationView(title: title, image: image) - - self.view.addSubview(validationView) - loadingView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { - validationView.removeFromSuperview() - } - } - - private func searchToSpotifyAPI(musics: [Music]) async { - self.setSearchView() - do { - let jsonData = try JSONEncoder().encode(musics) - let jsonString = String(data: jsonData, encoding: .utf8) ?? "" - let musicParams = jsonString.replacingOccurrences(of: "\\", with: "").replacingOccurrences(of: "artistNames", with: "artistName") - - if let parameterJsonData = musicParams.data(using: .utf8) { - do { - if let parameterJsonArray = try JSONSerialization.jsonObject(with: parameterJsonData, options: []) as? [[String: Any]] { - - let url = EndPoint.musicSpotifySearch.path - let params = ["destinationAccessToken" : TokenManager.shared.spotifyAccessToken, - "musics" : parameterJsonArray] as [String : Any] - - APIService().post(of: APIResponse<[Search]>.self, url: url, parameters: params) { response in - switch response.code { - case 200: - self.searchArr = response.data - for i in 0...self, url: url, parameters: params) { response in + switch response.code { + case 200: + self.searchArr = response.data + for i in 0...self, url: url, parameters: params) { response in - switch response.code { - case 200: - self.searchArr = response.data - for i in 0...self, url: url, parameters: params) { response in + switch response.code { + case 200: + self.searchArr = response.data + for i in 0.. CGFloat { - return 66 - } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 66 + } } diff --git a/PLUV/Select/SelectMusicViewModel.swift b/PLUV/Select/SelectMusicViewModel.swift index 55af1d2..cc49dec 100644 --- a/PLUV/Select/SelectMusicViewModel.swift +++ b/PLUV/Select/SelectMusicViewModel.swift @@ -9,30 +9,9 @@ import Foundation import RxSwift import RxCocoa -class SelectMusicViewModel { +class SelectMusicViewModel: SelectMusicViewModelProtocol { var playlistItem: Playlist? var musicItem = BehaviorRelay<[Music]>(value: []) let selectedMusic = BehaviorRelay<[Music]>(value: []) let disposeBag = DisposeBag() - - func musicItemCount(completion: @escaping (Int) -> Void) { - musicItem - .map { $0.count } - .subscribe(onNext: { count in - completion(count) - }) - .disposed(by: disposeBag) - } - - func musicSelect(music: Music) { - var currentSelect = selectedMusic.value - - if let index = currentSelect.firstIndex(where: { $0.title == music.title && $0.artistNames == music.artistNames }) { - currentSelect.remove(at: index) - } else { - currentSelect.append(music) - } - - selectedMusic.accept(currentSelect) - } } diff --git a/PLUV/Select/SelectSaveViewModel.swift b/PLUV/Select/SelectSaveViewModel.swift index dbacec9..9df02af 100644 --- a/PLUV/Select/SelectSaveViewModel.swift +++ b/PLUV/Select/SelectSaveViewModel.swift @@ -9,30 +9,9 @@ import Foundation import RxSwift import RxCocoa -class SelectSaveViewModel { +class SelectSaveViewModel: SelectMusicViewModelProtocol { var saveItem: Feed? var musicItem = BehaviorRelay<[Music]>(value: []) let selectedMusic = BehaviorRelay<[Music]>(value: []) let disposeBag = DisposeBag() - - func musicItemCount(completion: @escaping (Int) -> Void) { - musicItem - .map { $0.count } - .subscribe(onNext: { count in - completion(count) - }) - .disposed(by: disposeBag) - } - - func musicSelect(music: Music) { - var currentSelect = selectedMusic.value - - if let index = currentSelect.firstIndex(where: { $0.title == music.title && $0.artistNames == music.artistNames }) { - currentSelect.remove(at: index) - } else { - currentSelect.append(music) - } - - selectedMusic.accept(currentSelect) - } }