From 97743866641ab24b2dcf60f30f5ced97da91af34 Mon Sep 17 00:00:00 2001 From: Joseph Cha Date: Wed, 14 Aug 2024 13:51:03 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8#297:=20=EC=9C=A0=ED=8A=9C=EB=B8=8C=20?= =?UTF-8?q?=EB=AE=A4=EC=A7=81=EC=95=B1=EC=97=90=EC=84=9C=20=EA=B3=B5?= =?UTF-8?q?=EC=9C=A0=ED=95=98=EA=B8=B0=EB=A1=9C=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=ED=95=9C=20=EB=85=B8=EB=9E=98=EC=9D=B4=EB=A6=84,=20?= =?UTF-8?q?=EC=95=84=ED=8B=B0=EC=8A=A4=ED=8A=B8=EC=9D=B4=EB=A6=84=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=8C=EC=95=85=EA=B2=80=EC=83=89=20=EB=B9=84?= =?UTF-8?q?=EC=A6=88=EB=8B=88=EC=8A=A4=EB=A1=9C=EC=A7=81=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EB=B0=8F=20Music=20Array=20=EC=B2=AB=EB=B2=88?= =?UTF-8?q?=EC=A7=B8=20=EC=9D=B8=EB=8D=B1=EC=8A=A4=EA=B0=92=20=EC=95=A8?= =?UTF-8?q?=EB=B2=94=EC=BB=A4=EB=B2=84,=20=EB=85=B8=EB=9E=98=EC=9D=B4?= =?UTF-8?q?=EB=A6=84,=20=EC=95=84=ED=8B=B0=EC=8A=A4=ED=8A=B8=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20UI=EC=97=90=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/ShareViewController.swift | 70 ++++++++++++++++++- .../ViewModel/ShareViewModel.swift | 21 +++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/StreetDrop/ShareExtension/View/ShareViewController.swift b/StreetDrop/ShareExtension/View/ShareViewController.swift index 659a61d..1c6d23f 100644 --- a/StreetDrop/ShareExtension/View/ShareViewController.swift +++ b/StreetDrop/ShareExtension/View/ShareViewController.swift @@ -17,6 +17,7 @@ final class ShareViewController: SLComposeServiceViewController { final class ShareViewController: UIViewController { private let viewModel: ShareViewModel = .init() private let disposeBag: DisposeBag = .init() + private let sharedMusicKeyWordEvent: PublishRelay = .init() override func isContentValid() -> Bool { return true @@ -121,12 +122,20 @@ final class ShareViewController: UIViewController { super.viewDidLoad() bindViewModel() configureUI() + + // 공유된 데이터 가져오기 + guard let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem else { return } + handleExtensionItem(extensionItem) + } } } private extension ShareViewController { func bindViewModel() { - let input: ShareViewModel.Input = .init(viewDidLoadEvent: .just(Void())) + let input: ShareViewModel.Input = .init( + viewDidLoadEvent: .just(Void()), + sharedMusicKeyWordEvent: sharedMusicKeyWordEvent.asObservable() + ) let output: ShareViewModel.Output = viewModel.convert( input: input, disposedBag: disposeBag @@ -137,6 +146,14 @@ private extension ShareViewController { owner.villageNameLabel.text = villageName } .disposed(by: disposeBag) + + output.showSearchedMusic + .bind(with: self) { owner, music in + owner.albumCoverImageView.kf.setImage(with: URL(string: music.albumImage)) + owner.songNameLabel.text = music.songName + owner.artistNameLabel.text = music.artistName + } + .disposed(by: disposeBag) } func configureUI() { @@ -198,5 +215,56 @@ private extension ShareViewController { } } } + +// MARK: - Handle Shared Data +private extension ShareViewController { + func handleExtensionItem(_ extensionItem: NSExtensionItem) { + // ExtensionItem에서 ContentText 추출 + guard let sharedTextItem = extensionItem.attributedContentText, + let videoID = extractVideoID(from: sharedTextItem.string) else { return } + + fetchVideoDetails(videoID: videoID) { [weak self] songName, artistName in + self?.sharedMusicKeyWordEvent.accept("\(songName ?? "")-\(artistName ?? "")") + } + } + func fetchVideoDetails(videoID: String, completion: @escaping (String?, String?) -> Void) { + let apiKey = "AIzaSyDHsdIcuAK98-EW9UueCeF6g4iYiap7bmA" // YouTube Data API 키를 여기에 입력하세요 + let urlString = "https://www.googleapis.com/youtube/v3/videos?id=\(videoID)&key=\(apiKey)&part=snippet" + + guard let url = URL(string: urlString) else { + completion(nil, nil) + return + } + + let task = URLSession.shared.dataTask(with: url) { data, response, error in + guard let data = data, error == nil else { + completion(nil, nil) + return + } + + do { + if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let items = json["items"] as? [[String: Any]], + let snippet = items.first?["snippet"] as? [String: Any] { + let title = snippet["title"] as? String + let channelTitle = snippet["channelTitle"] as? String + completion(title, channelTitle) + } else { + completion(nil, nil) + } + } catch { + completion(nil, nil) + } + } + task.resume() + } + + func extractVideoID(from url: String) -> String? { + guard let urlComponents = URLComponents(string: url), + let queryItems = urlComponents.queryItems else { + return nil + } + return queryItems.first(where: { $0.name == "v" })?.value + } } diff --git a/StreetDrop/ShareExtension/ViewModel/ShareViewModel.swift b/StreetDrop/ShareExtension/ViewModel/ShareViewModel.swift index a622381..b089775 100644 --- a/StreetDrop/ShareExtension/ViewModel/ShareViewModel.swift +++ b/StreetDrop/ShareExtension/ViewModel/ShareViewModel.swift @@ -30,14 +30,18 @@ final class ShareViewModel: NSObject, ShareViewModelType { struct Input { let viewDidLoadEvent: Observable + let sharedMusicKeyWordEvent: Observable } struct Output { - fileprivate let showVillageNameRelay: PublishRelay = .init() var showVillageName: Observable { showVillageNameRelay.asObservable() } + fileprivate let showSearchedMusicRelay: PublishRelay = .init() + var showSearchedMusic: Observable { + showSearchedMusicRelay.asObservable() + } } func convert(input: Input, disposedBag: DisposeBag) -> Output { @@ -49,6 +53,21 @@ final class ShareViewModel: NSObject, ShareViewModelType { } .disposed(by: disposedBag) + input.sharedMusicKeyWordEvent + .bind(with: self) { owner, sharedMusicKeyWord in + owner.searchMusicUsecase.searchMusic(keyword: sharedMusicKeyWord) + .subscribe(with: self) { owner, musicList in + guard let firstMusic = musicList.first else { + // TODO: 요셉, 검색된 음악 없다는 이벤트 view에 전달 + return + } + owner.output.showSearchedMusicRelay.accept(firstMusic) + } onFailure: { owner, error in + + } + .disposed(by: disposedBag) + } + .disposed(by: disposedBag) return output }