// // MPPositive_OfflineSongsViewController.swift // MusicPlayer // // Created by Mr.Zhou on 2024/5/28. // import UIKit class MPPositive_OfflineSongsViewController: MPPositive_BaseViewController { ///表头View private lazy var headView:UIView = { let headView:UIView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: (227*width)+navAndstatusBarHeight)) headView.backgroundColor = .init(hex: "#151718") headView.layer.masksToBounds = true return headView }() ///顶部大封面 private lazy var backImageView:UIImageView = { let image:UIImageView = .init(image: placeholderImage) image.contentMode = .scaleAspectFill image.layer.masksToBounds = true return image }() ///顶部模糊遮罩 private lazy var backBlurView:UIVisualEffectView = { // 创建一个模糊效果 let blurEffect = UIBlurEffect(style: .dark) // 创建一个可交互的毛玻璃视图 let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.alpha = 0.6 blurEffectView.isUserInteractionEnabled = false return blurEffectView }() ///阴影渐变图(遮挡封面图) private lazy var maskImageView:UIImageView = { let imageView:UIImageView = .init(image: .init(named: "List_Cover'mask")) imageView.contentMode = .scaleAspectFill return imageView }() ///顶部小封面 private lazy var coverImageView:UIImageView = { let image:UIImageView = .init(image: placeholderImage) image.contentMode = .scaleAspectFill image.layer.masksToBounds = true image.layer.cornerRadius = 8*width return image }() ///歌单标题 private lazy var titleLabel:UILabel = createLabel("Name", font: .systemFont(ofSize: 18*width, weight: .regular), textColor: .white, textAlignment: .center, lines: 2) ///section展示View private lazy var sectionShowView:UIView = { let sectionView:UIView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 175*width)) sectionView.backgroundColor = .init(hex: "#151718") return sectionView }() ///播放全部 private lazy var playAllBtn:UIButton = { let btn = UIButton() btn.setImage(UIImage(named: "Center_PlayAll'logo"), for: .normal) btn.setTitle(" Play all", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 14*width, weight: .regular) btn.addTarget(self, action: #selector(playAllClick(_ :)), for: .touchUpInside) return btn }() ///随机播放按钮 private lazy var shuffleBtn:UIButton = { let btn = UIButton() btn.setImage(UIImage(named: "List_ShufflePlay'logo"), for: .normal) btn.setTitle(" Shuffle", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 14*width, weight: .regular) btn.addTarget(self, action: #selector(shuffleClick(_ :)), for: .touchUpInside) return btn }() ///批量删除按钮 private lazy var bulkDeleteBtn:UIButton = { let btn = UIButton() btn.setImage(UIImage(named: "Center_BulkDelete'logo"), for: .normal) btn.setTitle(" Delete", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 14*width, weight: .regular) btn.addTarget(self, action: #selector(batchDeleteClick(_ :)), for: .touchUpInside) return btn }() ///搜索按钮 private lazy var searchBtn:UIButton = { let btn = UIButton() btn.setBackgroundImage(UIImage(named: "Center_Search'logo"), for: .normal) btn.addTarget(self, action: #selector(searchActionShowClick(_ :)), for: .touchUpInside) return btn }() //搜索View private lazy var searchShowView:MPPositive_CenterListSearchView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 30*width)) ///广告View fileprivate lazy var adContainerView:UIView = { let adContainerView:UIView = .init(frame: .init(x: 0, y: 0, width: screen_Width, height: 150)) adContainerView.backgroundColor = .clear return adContainerView }() //原生广告是否展示 private var isShowAdNatived:Bool = false //添加右上角排序按钮 private lazy var rightBtn:UIButton = { let btn:UIButton = .init(frame: .init(x: 0, y: 0, width: 24*width, height: 24*width)) btn.setBackgroundImage(UIImage(named: "Change Sort'logo"), for: .normal) btn.addTarget(self, action: #selector(sortTypeClick(_ :)), for: .touchUpInside) return btn }() ///tableView private lazy var tableView:UITableView = { let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain) if #available(iOS 15.0, *) { tableView.sectionHeaderTopPadding = 0 } tableView.tableHeaderView = headView tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.contentInsetAdjustmentBehavior = .never tableView.estimatedRowHeight = 200 tableView.rowHeight = UITableView.automaticDimension tableView.bounces = false tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_SearchResultShowTableViewCell.self, forCellReuseIdentifier: MPPositive_SearchResultShowTableViewCellID) tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) return tableView }() private let MPPositive_SearchResultShowTableViewCellID = "MPPositive_SearchResultShowTableViewCell" //是否搜索模式 private var isSearchStyle:Bool = false //全部数据 private var offlines:[MPPositive_DownloadViewModel] = [] //搜索模式展示的数据组 private var searchSongs:[MPPositive_DownloadViewModel] = [] //常态模式展示的数据组 private var showSongs:[MPPositive_DownloadViewModel] = [] //当前状态 private var sortType:Int{ get{ if let type = UserDefaults.standard.object(forKey: "Offline_Songs_SortType") as? Int { return type }else { return 0 } } } override func viewDidLoad() { super.viewDidLoad() setTitle("") setPopBtn() configure() MP_AdMobManager.shared.layoutLibraryNativeAd(in: adContainerView, index: 1){ [weak self] in self?.reloadNatived() } MP_AdMobManager.shared.onLibraryNativeAdBlock = { [weak adContainerView] in guard let adContainerView = adContainerView else {return} MP_AdMobManager.shared.layoutLibraryNativeAd(in: adContainerView, index: 1){ self.reloadNatived() } } searchShowView.cancelBlock = { [weak self] in guard let self = self else {return} cancelSearchAction() } searchShowView.textBlock = { [weak self] (text) in guard let self = self else {return} isSearchStyle = true //获得文本回调,根据文本筛选数据 reloadSearch(text) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) reload() } //刷新列表 private func reload() { MPPositive_LoadCoreModel.shared.reloadLoadSongViewModel { [weak self] in guard let self = self else {return} offlines = MPPositive_LoadCoreModel.shared.loadViewModels let coverURL = URL(string: offlines.first?.loadItem.coverImage ?? "") backImageView.kf.setImage(with: coverURL, placeholder: placeholderImage) coverImageView.kf.setImage(with: coverURL, placeholder: placeholderImage) titleLabel.text = "Offline Songs".localizableString() playAllBtn.setTitle(" Play (\(offlines.count))", for: .normal) isSearchStyle ? reloadSearch(self.searchShowView.textField.text ?? ""):reloadShow() } } //刷新常态展示组 private func reloadShow() { // tableView.showMessage(offlines.count, title: "No Songs") switch sortType { case 0://从新到旧 showSongs = offlines.sorted(by: { item1, item2 in if let date1 = item1.loadItem.addTime, let date2 = item2.loadItem.addTime { return date1 > date2 }else { return false } }) default://从旧到新 showSongs = offlines.sorted(by: { item1, item2 in if let date1 = item1.loadItem.addTime, let date2 = item2.loadItem.addTime { return date1 < date2 }else { return true } }) } tableView.reloadData() } //刷新搜索组 private func reloadSearch(_ text:String) { if text.isEmpty { //当前输入文本为空,展示所有数据 searchSongs = offlines }else { //当前输入文本不为空,筛选文本 searchSongs = offlines.filter({($0.title ?? "").contains(text) || ($0.subtitle ?? "").contains(text)}) } tableView.reloadSections(.init(integer: 0), with: .automatic) } //刷新原生广告 @objc private func reloadNatived() { isShowAdNatived = true tableView.reloadData() } private func configure() { navView.addSubview(rightBtn) rightBtn.snp.makeConstraints { make in make.width.height.equalTo(24*width) make.centerY.equalToSuperview() make.right.equalToSuperview().offset(-16*width) } //添加顶部背景 headView.addSubview(backImageView) backImageView.snp.makeConstraints { make in make.left.top.right.equalToSuperview() make.height.equalTo((227*width)+navAndstatusBarHeight) } //添加顶部模糊层 headView.addSubview(backBlurView) backBlurView.snp.makeConstraints { make in make.left.top.right.equalToSuperview() make.height.equalTo(backImageView) } //添加顶部阴影 headView.addSubview(maskImageView) maskImageView.snp.makeConstraints { make in make.left.right.equalToSuperview() make.height.equalTo(120*width) make.bottom.equalTo(backImageView) } //小封面 headView.addSubview(coverImageView) coverImageView.snp.makeConstraints { make in make.width.height.equalTo(120*width) make.center.equalTo(backImageView) } //标题 headView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.width.lessThanOrEqualToSuperview().multipliedBy(0.7) make.centerX.equalToSuperview() make.top.equalTo(coverImageView.snp.bottom).offset(30*width) } //播放全部按钮 sectionShowView.addSubview(playAllBtn) playAllBtn.snp.makeConstraints { make in make.left.equalToSuperview().offset(18*width) make.top.equalToSuperview().offset(24*width) } //随机播放按钮 sectionShowView.addSubview(shuffleBtn) shuffleBtn.snp.makeConstraints { make in make.left.equalTo(playAllBtn.snp.right).offset(18*width) make.centerY.equalTo(playAllBtn) } sectionShowView.addSubview(bulkDeleteBtn) bulkDeleteBtn.snp.makeConstraints { make in make.left.equalTo(shuffleBtn.snp.right).offset(18*width) make.centerY.equalTo(playAllBtn) } //搜索 sectionShowView.addSubview(searchBtn) searchBtn.snp.makeConstraints { make in make.right.equalToSuperview().offset(-18*width) make.width.height.equalTo(24*width) make.centerY.equalTo(playAllBtn) } sectionShowView.addSubview(searchShowView) searchShowView.snp.makeConstraints { make in make.height.equalTo(45*width) make.left.right.equalToSuperview() make.centerY.equalTo(searchBtn) } searchShowView.alpha = 0 searchShowView.isHidden = true searchShowView.isUserInteractionEnabled = false //添加广告展示View sectionShowView.addSubview(adContainerView) adContainerView.snp.makeConstraints { make in make.top.equalTo(playAllBtn.snp.bottom).offset(20*width) make.left.right.equalToSuperview() make.height.equalTo(130*width) } //添加tableView view.addSubview(tableView) tableView.snp.makeConstraints { make in make.top.left.right.bottom.equalToSuperview() } } //弹出排序框 @objc private func sortTypeClick(_ sender:UIButton) { view.endEditing(true) //关闭搜索模式 cancelSearchAction() MPPositive_ModalType = .SortType let sortVC = MPPositive_SortTypeViewController(sortType) sortVC.chooseBlock = { [weak self] (type) in guard let self = self else {return} UserDefaults.standard.set(type, forKey: "Offline_Songs_SortType") reloadShow() } sortVC.transitioningDelegate = self sortVC.modalPresentationStyle = .custom present(sortVC, animated: true) } //搜索 @objc private func searchActionShowClick(_ sender:UIButton) { //点击搜索时,展示搜索框 UIView.animate(withDuration: 0.2) { [weak self] in guard let self = self else {return} searchShowView.isHidden = false //调整searchView的透明度 searchShowView.alpha = 1 searchBtn.isUserInteractionEnabled = false } completion: { [weak self] statu in guard let self = self else {return} searchShowView.isUserInteractionEnabled = true } } //取消搜索事件 private func cancelSearchAction() { UIView.animate(withDuration: 0.2) { [weak self] in guard let self = self else {return} searchShowView.isUserInteractionEnabled = false searchShowView.alpha = 0 isSearchStyle = false } completion: { [weak self] statu in guard let self = self else {return} searchShowView.isHidden = true searchBtn.isUserInteractionEnabled = true reloadShow() } } //播放全部 @objc private func playAllClick(_ sender:UIButton) { guard offlines.count != 0 else {return} MPPositive_Debouncer.shared.call { [weak self] in guard let self = self else {return} // guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { // MP_HUD.text("Bad connection~".localizableString(), delay: 2.0, completion: nil) // return // } MP_AnalyticsManager.shared.song_clickAction("Offline Song") //优先清除数据 MP_PlayerManager.shared.loadPlayer = nil //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() //将当前下载音乐放入列表中 var array:[MPPositive_SongItemModel] = [] for (index, song) in offlines.enumerated() { let item = MPPositive_SongItemModel() item.index = index item.coverUrls = [song.loadItem.coverImage ?? ""] item.reviewUrls = [song.loadItem.reviewImage ?? ""] item.title = song.loadItem.title item.longBylineText = song.loadItem.longBylineText item.lengthText = song.loadItem.lengthText item.shortBylineText = song.loadItem.shortBylineText item.lyricsID = song.loadItem.lyricsID item.lyrics = song.loadItem.lyrics item.videoId = song.loadItem.videoId item.relatedID = song.loadItem.relatedID array.append(item) } guard let currentVideo = offlines.first else {return} let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: currentVideo.loadItem.videoId ?? "") lodaViewModel.improveData(currentVideo.loadItem.videoId ?? "") //更改播放器播放类型 MP_PlayerManager.shared.setPlayType(.normal) MP_PlayerManager.shared.loadPlayer = lodaViewModel MP_AnalyticsManager.shared.player_b_listAction() } } //随机播放 @objc private func shuffleClick(_ sender:UIButton) { view.endEditing(true) guard offlines.count != 0 else {return} MPPositive_Debouncer.shared.call { [weak self] in guard let self = self else {return} // guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { // MP_HUD.text("Bad connection~".localizableString(), delay: 2.0, completion: nil) // return // } MP_AnalyticsManager.shared.song_clickAction("Offline Song") //优先清除数据 MP_PlayerManager.shared.loadPlayer = nil //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() //将当前下载音乐放入列表中 var array:[MPPositive_SongItemModel] = [] for (index, song) in offlines.enumerated() { let item = MPPositive_SongItemModel() item.index = index item.coverUrls = [song.loadItem.coverImage ?? ""] item.reviewUrls = [song.loadItem.reviewImage ?? ""] item.title = song.loadItem.title item.longBylineText = song.loadItem.longBylineText item.lengthText = song.loadItem.lengthText item.shortBylineText = song.loadItem.shortBylineText item.lyricsID = song.loadItem.lyricsID item.lyrics = song.loadItem.lyrics item.videoId = song.loadItem.videoId item.relatedID = song.loadItem.relatedID array.append(item) } guard let currentVideo = offlines.randomElement() else {return} let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: currentVideo.loadItem.videoId ?? "") lodaViewModel.improveData(currentVideo.loadItem.videoId ?? "") //更改播放器播放类型 MP_PlayerManager.shared.setPlayType(.random) MP_PlayerManager.shared.loadPlayer = lodaViewModel MP_AnalyticsManager.shared.player_b_listAction() } } //批量删除事件 @objc private func batchDeleteClick(_ sender:UIButton) { view.endEditing(true) print("点击了批量删除") //同时执行弹出事件 let showVC:MPPositive_BatchDeletionViewController = .init(withOfflines: offlines) navigationController?.pushViewController(showVC, animated: true) } } //MARK: - tableView extension MPPositive_OfflineSongsViewController: UITableViewDataSource, UITableViewDelegate, UIViewControllerTransitioningDelegate{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return isSearchStyle ? searchSongs.count:showSongs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_SearchResultShowTableViewCellID, for: indexPath) as! MPPositive_SearchResultShowTableViewCell if isSearchStyle { cell.loadViewModel = searchSongs[indexPath.row] }else { cell.loadViewModel = showSongs[indexPath.row] } cell.moreBlock = { [weak self] in guard let self = self else {return} MPPositive_Debouncer.shared.call { let item = self.isSearchStyle ? self.searchSongs[indexPath.row]:self.showSongs[indexPath.row] let song = MPPositive_SongItemModel() song.coverUrls = [item.loadItem.coverImage ?? ""] song.reviewUrls = [item.loadItem.reviewImage ?? ""] song.title = item.loadItem.title song.longBylineText = item.loadItem.longBylineText song.lengthText = item.loadItem.lengthText song.shortBylineText = item.loadItem.shortBylineText song.lyrics = item.loadItem.lyrics song.lyricsID = item.loadItem.lyricsID song.videoId = item.loadItem.videoId song.relatedID = item.loadItem.relatedID MPPositive_ModalType = .MoreOperations let moreVC = MPPositive_MoreSongOperationsViewController(song) moreVC.disMissBlock = { self.reload() } moreVC.transitioningDelegate = self moreVC.modalPresentationStyle = .custom self.present(moreVC, animated: true) } } cell.deleteBlock = { [weak self] in guard let self = self else {return} //点击删除下载 let alertController = UIAlertController(title: "Delete This Song".localizableString(), message: "Are you sure you want to delete the offline resources of this song?".localizableString(), preferredStyle: .alert) let cancel = UIAlertAction(title: "Cancel".localizableString(), style: .cancel) alertController.addAction(cancel) let sure = UIAlertAction(title: "Confirm".localizableString(), style: .destructive) {(action) in guard let videoId = self.isSearchStyle ? self.searchSongs[indexPath.row].loadItem.videoId:self.showSongs[indexPath.row].loadItem.videoId else {return} //确定删除 MP_DownloadManager.shared.deleteFileDocuments(videoId) { videoId in MP_HUD.progress("Loading...".localizableString(), delay: 0.5) { MP_HUD.text("Removed".localizableString(), delay: 1.0, completion: nil) self.reload() } } } alertController.addAction(sure) present(alertController, animated: true) } cell.cancelBlock = { [weak self] in guard let self = self else {return} //点击取消下载 let alertController = UIAlertController(title: "Cancel Song Download Task".localizableString(), message: "Are you sure you want to cancel the download task of this song?".localizableString(), preferredStyle: .alert) let cancel = UIAlertAction(title: "Cancel".localizableString(), style: .cancel) alertController.addAction(cancel) let sure = UIAlertAction(title: "Confirm".localizableString(), style: .destructive) {(action) in guard let videoId = self.isSearchStyle ? self.searchSongs[indexPath.row].loadItem.videoId:self.showSongs[indexPath.row].loadItem.videoId else {return} //确定取消 MP_DownloadManager.shared.cancelDownloadTask(videoId) { videoId in MP_HUD.text("Cancel".localizableString(), delay: 1.0, completion: nil) self.reload() } } alertController.addAction(sure) present(alertController, animated: true) } return cell } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return sectionShowView } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return isShowAdNatived ? 185*width:75*width } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { MPPositive_Debouncer.shared.call { [weak self] in guard let self = self else {return} MP_AnalyticsManager.shared.song_clickAction("Offline Song") //优先清除数据 MP_PlayerManager.shared.loadPlayer = nil //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() //将当前下载音乐放入列表中 var array:[MPPositive_SongItemModel] = [] for (index, song) in offlines.enumerated() { let item = MPPositive_SongItemModel() item.index = index item.coverUrls = [song.loadItem.coverImage ?? ""] item.reviewUrls = [song.loadItem.reviewImage ?? ""] item.title = song.loadItem.title item.longBylineText = song.loadItem.longBylineText item.lengthText = song.loadItem.lengthText item.shortBylineText = song.loadItem.shortBylineText item.lyricsID = song.loadItem.lyricsID item.lyrics = song.loadItem.lyrics item.videoId = song.loadItem.videoId item.relatedID = song.loadItem.relatedID array.append(item) } let currentVideo = isSearchStyle ? searchSongs[indexPath.row]:showSongs[indexPath.row] let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: currentVideo.loadItem.videoId ?? "") lodaViewModel.improveData(currentVideo.loadItem.videoId ?? "") //更改播放器播放类型 MP_PlayerManager.shared.setPlayType(.normal) MP_PlayerManager.shared.loadPlayer = lodaViewModel MP_AnalyticsManager.shared.player_b_listAction() // NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) } } func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting) } func scrollViewDidScroll(_ scrollView: UIScrollView) { let yOffset = scrollView.contentOffset.y //当滚动值等于大于当前封面区域时,设置一个toppading值 if yOffset >= ((153*width)+navAndstatusBarHeight) { if tableView.contentInset.top == 0 { tableView.contentInset = .init(top: navAndstatusBarHeight, left: 0, bottom: 70*width, right: 0) topSafeAndNavView.backgroundColor = .init(hex: "#151718") } }else { if tableView.contentInset.top == navAndstatusBarHeight { tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) topSafeAndNavView.backgroundColor = .clear } } } }