Music_Player3/relax.offline.mp3.music/MP/MPPositive/ViewControllers/Center(个人曲库页)/MPPositive_OfflineSongsViewController.swift
2024-12-25 10:02:00 +08:00

603 lines
27 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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)
///sectionView
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
}
}
}
}