// // MPPositive_HomeViewController.swift // MusicPlayer // // Created by Mr.Zhou on 2024/4/19. // import UIKit import MJRefresh import Lottie import UserMessagingPlatform class MPPositive_HomeViewController: MPPositive_BaseViewController, UIViewControllerTransitioningDelegate{ //背景图片 private lazy var bgImageView:UIImageView = { let imageView:UIImageView = .init(image: .init(named: "B_Home_BG'bg")) imageView.contentMode = .scaleAspectFill return imageView }() //菜单按钮 private lazy var memuBtn:UIButton = { let btn = UIButton() btn.setBackgroundImage(UIImage(named: "Home_Menu'logo"), for: .normal) btn.addTarget(self, action: #selector(menuRightClick(_ :)), for: .touchUpInside) return btn }() //ViP按钮 private lazy var vipImageView:UIImageView = { let imageView:UIImageView = UIImageView(image: .init(named: "VIP'logo")) imageView.isUserInteractionEnabled = true imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(vipIAPClick(_ :)))) return imageView }() //斜向标题Label private lazy var HiMelodyLabel:UILabel = { let label = UILabel() label.text = "HiMelody" // Create a bold italic font if let boldItalicFontDescriptor = UIFont.systemFont(ofSize: 20*width, weight: .bold).fontDescriptor.withSymbolicTraits([.traitBold, .traitItalic]) { let boldItalicFont = UIFont(descriptor: boldItalicFontDescriptor, size: 20*width) label.font = boldItalicFont } else { label.font = UIFont.boldSystemFont(ofSize: 20*width) } label.textColor = .white label.isUserInteractionEnabled = true label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(scrollTopAction(_ :)))) return label }() //搜索栏 private lazy var searchView = createSearchView() //导航栏内购是否展示 private var isBuyShow:Bool = true //最后一次偏移量 private var lastContentOffset: CGFloat = 0 //是否用户滚动 private var isUserDragging:Bool = false //tableView private lazy var tableView:UITableView = { let tableView = UITableView(frame: .init(x: 0, y: 0, width: screen_Width, height: screen_Height), style: .plain) tableView.backgroundColor = .clear tableView.separatorStyle = .none tableView.estimatedRowHeight = 200 tableView.rowHeight = UITableView.automaticDimension tableView.dataSource = self tableView.delegate = self tableView.register(MPPositive_PersonalisedRecommendationsTableViewCell.self, forCellReuseIdentifier: MPPositive_PersonalisedRecommendationsTableViewCellID) tableView.register(MPPositive_HomeLibraryListstableViewCell.self, forCellReuseIdentifier: MPPositive_HomeLibraryListstableViewCellID) tableView.register(MPPositive_HomeSinglesTableViewCell.self, forCellReuseIdentifier: MPPositive_HomeSinglesTableViewCellID) tableView.register(MPPositive_HomeShowTableViewCell.self, forCellReuseIdentifier: MPPositive_HomeShowTableViewCellID) tableView.contentInset = .init(top: 0, left: 0, bottom: 70*width, right: 0) //添加一个上拉加载 let footer = MJRefreshAutoGifFooter { [weak self] in self?.footerRefreshContinuationData() } footer.setTitle("Pull Up", for: .idle) footer.setTitle("Release Refresh", for: .pulling) footer.setTitle("Loading...", for: .refreshing) footer.setTitle("No Data", for: .noMoreData) tableView.mj_footer = footer return tableView }() private let MPPositive_PersonalisedRecommendationsTableViewCellID = "MPPositive_PersonalisedRecommendationsTableViewCell" private let MPPositive_HomeLibraryListstableViewCellID = "MPPositive_HomeLibraryListstableViewCell" private let MPPositive_HomeSinglesTableViewCellID = "MPPositive_HomeSinglesTableViewCell" private let MPPositive_HomeShowTableViewCellID = "MPPositive_HomeShowTableViewCell" private var isFirstAppearance:Bool = true //个性化内容 private var personalModuleLists:[MPPositive_PersonalListViewModel] = [] //系统返回内容 private var browseModuleLists:[MPPositive_BrowseModuleListViewModel] = [] override func viewDidLoad() { super.viewDidLoad() DispatchQueue.main.asyncAfter(deadline: .now() + 3) { UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: nil) { requestConsentError in if let consentError = requestConsentError { print("[UMP] GoogleUMP error=\(consentError.localizedDescription)") return } print("[UMP] GoogleUMP success") UMPConsentForm.loadAndPresentIfRequired(from: self) { loadAndPresentError in if let consentError = loadAndPresentError { print("[UMP] GoogleUMP error=\(consentError.localizedDescription)") return } print("[UMP] GoogleUMP success") } } } //设置API密钥 MP_NetWorkManager.shared.requestYBMAPIKeyCode() setTitle("") confirgue() if browseModuleLists.count == 0 { MP_HUD.loading() } MP_AdMobManager.shared.configureLibraryNativeAd(rootController: self) MP_AdMobManager.shared.loadLibraryNativeAd() //加载搜索原生广告 MP_AdMobManager.shared.configureSreachNativeAd(rootController: self) MP_AdMobManager.shared.loadSearchNativeAd() errorBlock = { [weak self] in guard let self = self else { return } //先判断是否具备预览数据 guard browseModuleLists.count == 0 else { MP_HUD.hideNow() return } //判断是否具备个性化数据 guard personalModuleLists.count == 0 else { MP_HUD.hideNow() return } //移除所有(除了navView)的子控件,并告知页面处理错误,提示用户重试 view.subviews.forEach { item in if item != self.topSafeAndNavView { //移除 if item.superview != nil { item.removeFromSuperview() } } } //添加报错View setErrorView() MP_HUD.hideNow() } retryBlock = { [weak self] in guard let self = self else {return} MP_HUD.loading() DispatchQueue.main.async { //获取首页 MPPositive_BrowseLoadViewModel.shared.reloadBrowseLists() } } //当home展示后尝试获取最后一次播放歌曲内容 guard let last = UserDefaults.standard.object(forKey: "Last_Play_Songs") as? [String:Any] else {return} guard let currentVideoId = last["currentVideoId"] as? String, let data = last["Songs"] as? Data, let array = jsonforCoreSongs(data) else {return} //数据获取完成,使用这些数据合成一个Load let lodaViewModel = MPPositive_PlayerLoadViewModel(array, currentVideoId: currentVideoId) lodaViewModel.improveData(currentVideoId) //更改播放器播放类型 MP_PlayerManager.shared.setPlayType(.normal) MP_PlayerManager.shared.setLastStatus(bool: true) MP_PlayerManager.shared.loadPlayer = lodaViewModel MP_LuxServerManager.shared.upDateOpenActiveEventTask() } deinit { NotificationCenter.default.removeObserver(self) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) NotificationCenter.notificationKey.add(observer: self, selector: #selector(reloadAction(_ :)), notificationName: .positive_browses_reload) NotificationCenter.notificationKey.add(observer: self, selector: #selector(continuationDataCompletionEvent(_ :)), notificationName: .positive_browses_completion) MPPositive_BrowseLoadViewModel.shared.libraryList.reloadLibrarys{ [weak self] in guard let self = self else {return} MPPositive_BrowseLoadViewModel.shared.getRecentlyData() } if isFirstAppearance == false { // 第二次展示的逻辑,可触发相应的方法 presentGuide() isFirstAppearance = true } MP_AnalyticsManager.shared.home_b_pvAction() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self) } //生成一个顶部搜索框 private func createSearchView() -> UIView{ let searchView:UIView = UIView() searchView.backgroundColor = .init(hex: "#212121") searchView.isUserInteractionEnabled = true searchView.layer.masksToBounds = true searchView.layer.cornerRadius = 16*width //添加一个icon let iconImageView = UIImageView(image: .init(named: "Search_ICON'logo")) searchView.addSubview(iconImageView) iconImageView.snp.makeConstraints { make in make.height.width.equalTo(16*width) make.left.equalToSuperview().offset(16*width) make.centerY.equalToSuperview() } let label = createLabel("Search songs,artists,playlists".localizableString(), font: .systemFont(ofSize: 14*width, weight: .regular), textColor: .init(hex: "#666666"), textAlignment: .left) searchView.addSubview(label) label.snp.makeConstraints { make in make.left.equalTo(iconImageView.snp.right).offset(8*width) make.right.equalToSuperview().offset(-16*width) make.centerY.equalToSuperview() } searchView.isUserInteractionEnabled = true searchView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(searchClick(_:)))) return searchView } //前往搜索结果页 @objc fileprivate func searchClick(_ sender:UITapGestureRecognizer) { isFirstAppearance = false MP_AnalyticsManager.shared.search_from_actionAction("home page") let resultVC = MPPositive_SearchResultShowViewController() navigationController?.pushViewController(resultVC, animated: false) } ///弹出好评引导框 func presentGuide() { guard UserDefaults.standard.bool(forKey: "isGuide") != true else { //已经评论过了 return } //检索活跃天数是否达到2次 guard var actives = UserDefaults.standard.object(forKey: "ActiveDays") as? [Date], actives.count >= 2 else { return } //达到2次,可以弹出好评引导框 MPPositive_ModalType = .Guide let guideVC = MP_GuideViewController() guideVC.disMissBlock = { DispatchQueue.main.async { //移除所有的日期值,填充一个新的日期值 actives.removeAll() let now = Date().timeZone() UserDefaults.standard.setValue([now], forKey: "ActiveDays") } } guideVC.feedBackBlock = { DispatchQueue.main.async { let alert = UIAlertController(title: "Feedback".localizableString(), message: "If you have any comments or suggestions, please contact us at the following e-mail address".localizableString(), preferredStyle: .actionSheet) let email = UIAlertAction(title: "marketing@lux-ad.com", style: .default) { (_) in //将邮箱复制到剪切板中 UIPasteboard.general.string = "marketing@lux-ad.com" MP_HUD.text("Successfully copied the e-mail address to the clipboard".localizableString(), delay: 1.0, completion: nil) } alert.addAction(email) let cancel = UIAlertAction(title: "Cancel".localizableString(), style: .cancel) alert.addAction(cancel) self.present(alert, animated: true) } } guideVC.storeBlock = { DispatchQueue.main.async { //跳转AppStore if let url = URL(string: "https://apps.apple.com/app/6502973957") { UIApplication.shared.open(url, options: [:], completionHandler: nil) } } } guideVC.transitioningDelegate = self guideVC.modalPresentationStyle = .custom self.present(guideVC, animated: true) } func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { return MPPositive_PresentationController(presentedViewController: presented, presenting: presenting) } //上拉加载更多 @objc private func footerRefreshContinuationData() { guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { tableView.mj_footer?.endRefreshing() return } guard (MP_NetWorkManager.shared.continuationAndItct != nil) || (MPPositive_BrowseLoadViewModel.shared.browseModuleLists.isEmpty == true) else { tableView.mj_footer?.endRefreshing() return } MPPositive_BrowseLoadViewModel.shared.loadMoreBrowseDatas() } //数据加载完毕 @objc private func continuationDataCompletionEvent(_ sender:Notification) { DispatchQueue.main.async { [weak self] in guard let self = self else {return} tableView.mj_footer?.endRefreshingWithNoMoreData() } } //MARK: - UI生成与配置 //配置 private func confirgue() { topSafeAndNavView.frame = .init(x: 0, y: 0, width: screen_Width, height: navAndstatusBarHeight + 40*width) navView.snp.updateConstraints { make in make.height.equalTo(90*width) } if memuBtn.superview == nil { navView.addSubview(memuBtn) memuBtn.snp.makeConstraints { make in make.right.equalToSuperview().offset(-16*width) make.top.equalToSuperview().offset(16*width) make.width.height.equalTo(24*width) } } if vipImageView.superview == nil { navView.addSubview(vipImageView) vipImageView.snp.makeConstraints { make in make.centerY.equalTo(memuBtn.snp.centerY) make.right.equalTo(memuBtn.snp.left).offset(-10*width) make.width.equalTo(51*width) make.height.equalTo(32*width) } } if HiMelodyLabel.superview == nil { navView.addSubview(HiMelodyLabel) HiMelodyLabel.snp.makeConstraints { make in make.left.equalToSuperview().offset(16*width) make.centerY.equalTo(memuBtn) } HiMelodyLabel.setGradientTextColor(gradientTextColors) } if searchView.superview == nil { navView.addSubview(searchView) searchView.snp.makeConstraints { make in make.width.equalTo(340*width) make.height.equalTo(32*width) make.centerX.equalToSuperview() make.bottom.equalToSuperview().offset(-9*width) } } if bgImageView.superview == nil { view.addSubview(bgImageView) bgImageView.snp.makeConstraints { make in make.top.right.left.equalToSuperview() make.height.equalTo(981*width) } } if tableView.superview == nil { view.addSubview(tableView) tableView.snp.makeConstraints { make in make.top.equalTo(navView.snp.bottom).offset(15*width) make.left.right.bottom.equalToSuperview() } } } //MARK: - 页面刷新 //页面刷新 @objc private func reloadAction(_ sender:Notification?) { DispatchQueue.main.async { [weak self] in guard let self = self else {return} if tableView.superview == nil { confirgue() } removeErrorView() MP_HUD.hideNow() personalModuleLists = MPPositive_BrowseLoadViewModel.shared.personalModuleLists browseModuleLists = MPPositive_BrowseLoadViewModel.shared.browseModuleLists tableView.showMessage((browseModuleLists.count == 0 && personalModuleLists.count == 0) ? 0:1) // 重新启用下拉刷新 tableView.reloadData() tableView.mj_footer?.endRefreshing() } } //MARK: - 点击 //点击顶部右侧弹出菜单 @objc private func menuRightClick(_ sender:UIButton) { isFirstAppearance = false let setVC = MPSideA_SettingViewController() setVC.cleanSide = true navigationController?.pushViewController(setVC, animated: true) } //弹出VIP页面 @objc private func vipIAPClick(_ sender:UITapGestureRecognizer) { view.endEditing(true) MP_AnalyticsManager.shared.VIP_clickAction() let iapVC = MP_IAPViewController() iapVC.isType = true iapVC.privacyBlock = { [weak self] in guard let self = self else {return} MP_NetWorkManager.shared.requestNetworkPermission(oberve: self) { [weak self] in let privacyVC = MPSideA_PrivacyViewController() self?.navigationController?.pushViewController(privacyVC, animated: true) } } iapVC.termsBlock = { [weak self] in guard let self = self else {return} MP_NetWorkManager.shared.requestNetworkPermission(oberve: self) { [weak self] in let serviceVC = MPSideA_ServiceViewController() self?.navigationController?.pushViewController(serviceVC, animated: true) } } iapVC.modalPresentationStyle = .fullScreen present(iapVC, animated: true) } @objc private func scrollTopAction(_ sender:UITapGestureRecognizer) { let topOffset = CGPoint(x: 0, y: 0) tableView.setContentOffset(topOffset, animated: true) } } //MARK: - tableView extension MPPositive_HomeViewController: UITableViewDataSource, UITableViewDelegate { func numberOfSections(in tableView: UITableView) -> Int { //根据数据调整当前tableView展示组数,一组为用户个性化内容,二组为系统返回数据 if personalModuleLists.isEmpty && MPPositive_BrowseLoadViewModel.shared.libraryList.libraryViewModels.isEmpty { //一组所有数据都为空,只用返回二组数据 return 1 }else { //一组有一部分具备数据或者全部数据,展示内容为两组 return 2 } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if personalModuleLists.isEmpty && MPPositive_BrowseLoadViewModel.shared.libraryList.libraryViewModels.isEmpty { //只有一组,这一组为系统返回数据 return browseModuleLists.count }else { //一组有一部分具备数据或者全部数据,展示内容为两组,视情况而定个性化组展示内容 if MPPositive_BrowseLoadViewModel.shared.libraryList.libraryViewModels.isEmpty { switch section { case 0: //返回纯自定义 return personalModuleLists.count default: return browseModuleLists.count } }else { switch section { case 0: //返回自定义加上Library行(可能只有Library行) return personalModuleLists.count + 1 default: return browseModuleLists.count } } } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if personalModuleLists.isEmpty && MPPositive_BrowseLoadViewModel.shared.libraryList.libraryViewModels.isEmpty { //只有一组,这一组为系统返回数据 let row = indexPath.row return setHomeYoutubeLayoutCell(tableView, row: row, indexPath: indexPath) }else { switch indexPath.section { case 0://个性化行 //一组有一部分具备数据或者全部数据,展示内容为两组,视情况而定个性化组展示内容 if MPPositive_BrowseLoadViewModel.shared.libraryList.libraryViewModels.isEmpty { let row:Int = indexPath.row //返回个性化行 return setHomePersonlLayoutCell(tableView, row: row) }else { //返回自定义加上Library行(可能只有Library行) if personalModuleLists.isEmpty { //只有library return setLibraryCell(tableView, indexPath: indexPath) }else { //两者都有 let row:Int = indexPath.row == 0 ? 0:(indexPath.row-1) if indexPath.row == 1 { //library return setLibraryCell(tableView, indexPath: indexPath) }else { //个性化行 return setHomePersonlLayoutCell(tableView, row: row) } } } default://系统数据行 let row = indexPath.row return setHomeYoutubeLayoutCell(tableView, row: row, indexPath: indexPath) } } } //生成一个个性化内容Cell private func setHomePersonlLayoutCell(_ tableView:UITableView, row:Int) -> UITableViewCell { switch row { case 0: let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_HomeSinglesTableViewCellID) as! MPPositive_HomeSinglesTableViewCell cell.personlViewModel = personalModuleLists[row] return cell default: let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_PersonalisedRecommendationsTableViewCellID) as! MPPositive_PersonalisedRecommendationsTableViewCell cell.personlViewModel = personalModuleLists[row] cell.reloadRecommondBlock = { [weak self] index in switch index { case 1://刷新个性化单曲模块 let array = MPPositive_LoadCoreModel.shared.recents MPPositive_BrowseLoadViewModel.shared.getRecommondFirstNumberData(array) { [weak self] personl in guard let self = self, let item = personl else { DispatchQueue.main.async { self?.tableView.reloadSections(.init(integer: 0), with: .automatic) } return } //更新个性化数据 if MPPositive_BrowseLoadViewModel.shared.personalModuleLists.indices.contains(index) { //正常情况 MPPositive_BrowseLoadViewModel.shared.personalModuleLists[index] = item }else { //数据缺失,已插入的方式添加 MPPositive_BrowseLoadViewModel.shared.personalModuleLists.insert(item, at: 1) } self.personalModuleLists = MPPositive_BrowseLoadViewModel.shared.personalModuleLists DispatchQueue.main.async { self.tableView.reloadSections(.init(integer: 0), with: .automatic) } } default://刷新个性化歌单模块 let array = MPPositive_LoadCoreModel.shared.recents MPPositive_BrowseLoadViewModel.shared.getRecommondSecondNumberData(array) { [weak self] personl in guard let self = self, let item = personl else { DispatchQueue.main.async { self?.tableView.reloadSections(.init(integer: 0), with: .automatic) } return } //更新个性化数据 if MPPositive_BrowseLoadViewModel.shared.personalModuleLists.indices.contains(index) { //正常情况 MPPositive_BrowseLoadViewModel.shared.personalModuleLists[index] = item }else { //数据缺失,已插入的方式添加 MPPositive_BrowseLoadViewModel.shared.personalModuleLists.append(item) } self.personalModuleLists = MPPositive_BrowseLoadViewModel.shared.personalModuleLists DispatchQueue.main.async { self.tableView.reloadSections(.init(integer: 0), with: .automatic) } } } } return cell } } //生成一个系统数据展示Cell(多样性) private func setHomeYoutubeLayoutCell(_ tableView:UITableView, row:Int, indexPath:IndexPath) -> UITableViewCell { if let first = browseModuleLists[row].items.first, first.browseItem.itemType == .single, (first.browseItem.pageType == "MUSIC_VIDEO_TYPE_ATV" || first.browseItem.pageType == "MUSIC_VIDEO_TYPE_UGC") { //是单曲 let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_HomeSinglesTableViewCellID, for: indexPath) as! MPPositive_HomeSinglesTableViewCell cell.browseViewModel = browseModuleLists[row] return cell }else { let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_HomeShowTableViewCellID, for: indexPath) as! MPPositive_HomeShowTableViewCell cell.browseViewModel = browseModuleLists[row] cell.requestNextBlock = { [weak self] (item) in guard let self = self else {return} requestNextEvent(item) } cell.findMoreBlock = { [weak self] in guard let self = self else {return} isFirstAppearance = false let moreVC = MPPositive_MoreContentViewController(browseModuleLists[row]) navigationController?.pushViewController(moreVC, animated: true) } return cell } } //生成一个Library展示Cell private func setLibraryCell(_ tableView:UITableView, indexPath:IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: MPPositive_HomeLibraryListstableViewCellID, for: indexPath) as! MPPositive_HomeLibraryListstableViewCell cell.libraryViewModels = MPPositive_BrowseLoadViewModel.shared.libraryList.libraryViewModels return cell } //请求单曲/列表/专辑 private func requestNextEvent(_ item:MPPositive_BrowseItemViewModel){ MP_AnalyticsManager.shared.home_b_module_clickAction(item.browseItem.pageType ?? "") switch item.browseItem.itemType { case .single: //单曲/视频跳转 MPPositive_Debouncer.shared.call { [weak self] in guard let self = self else {return} guard MP_NetWorkManager.shared.netWorkStatu == .reachable else { playOfflineSongs(item.browseItem.videoId ?? "") return } MP_AnalyticsManager.shared.song_clickAction("Home") //优先清除数据 MP_PlayerManager.shared.loadPlayer = nil //弹出播放器 NotificationCenter.notificationKey.post(notificationName: .pup_player_vc) MP_AnalyticsManager.shared.player_b_impAction() //触发next请求,优先获取列表全部单曲基础数据(不完善) MP_NetWorkManager.shared.requestNextList(item.browseItem.playListId ?? "", videoId: item.browseItem.videoId ?? "", clickTrackingParams: item.browseItem.clickTrackingParams){ [weak self] listSongs in guard let self = self else {return} //回掉的数据并不完善,生成一个playerloadViewModel let lodaViewModel = MPPositive_PlayerLoadViewModel(listSongs, currentVideoId: item.browseItem.videoId ?? "") lodaViewModel.improveData(item.browseItem.videoId ?? "") //更改播放器播放类型 MP_PlayerManager.shared.setPlayType(.normal) MP_PlayerManager.shared.loadPlayer = lodaViewModel MP_AnalyticsManager.shared.player_b_listAction() } } case .list: isFirstAppearance = false //列表专辑 let listVC = MPPositive_ListShowViewController(item.browseItem.browseId ?? "", params: "", title: item.title ?? "", subtitle: item.subtitle ?? "", clickTrackingParams: item.browseItem.clickTrackingParams ?? "") navigationController?.pushViewController(listVC, animated: true) case .artist: isFirstAppearance = false //前往艺术家页面 let artistVC = MPPositive_ArtistShowViewController(item.browseItem.artistId ?? "", clickTrackingParams: item.browseItem.clickTrackingParams ?? "") navigationController?.pushViewController(artistVC, animated: true) default: break } } }