// // TSLiveWallpaperBrowseVC.swift // TSLiveWallpaper // // Created by 100Years on 2024/12/24. // import Photos import PhotosUI private let topLineH = k_Height_statusBar() class TSLiveWallpaperBrowseItemModel { var style:ImageDataStyple = .homeLiveList var imageUrl:String = "" var videoUrl:String = "" var vip:Bool = false var livePhoto:PHLivePhoto? = nil var livePhotoResources:(pairedImage: URL, pairedVideo: URL)? var imageCacheUrl:URL? var videoCacheUrl:URL? } class TSLiveWallpaperBrowseVC: TSBaseVC { private var isPanningDown: Bool? lazy var isPreview = false { didSet { self.previewView.isHidden = !isPreview self.btnsAllView.isHidden = isPreview } } private var dataModelArray = [TSLiveWallpaperBrowseItemModel]() var currentIndex:Int { didSet{ reloadUI() } } init(itemModels: [TSImageDataItemModel],currentIndex:Int) { for itemModel in itemModels { let model = TSLiveWallpaperBrowseItemModel() model.style = itemModel.style model.imageUrl = itemModel.imageUrl model.videoUrl = itemModel.videoUrl model.vip = itemModel.vip dataModelArray.append(model) } self.currentIndex = currentIndex super.init() } var isCanDelete:Bool = false { didSet{ deleteBtn.isHidden = !isCanDelete copyrightBtn.isHidden = isCanDelete } } var deleteCompletion:((_ item:Int)->Void)? @MainActor required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } lazy var backBtn: UIButton = { return UIButton.createButton(image: UIImage(named: "navi_back_white"),backgroundColor: UIColor.fromHex("#111111", alpha: 0.2),corner: 16.0) { [weak self] in self?.pop() } }() //MARK: btnsAllView lazy var saveBtn: UIButton = { let saveBtn = TSViewTool.createNormalSubmitBtn(title: "Save".localized) { [weak self] in guard let self = self else { return } let cell = collectionView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? TSLiveWallpaperBrowseCell //判断 vip if let itemModel = self.dataModelArray.safeObj(At: currentIndex), itemModel.vip == true, PurchaseManager.default.isVip == false { cell?.stopPlayLive() TSPurchaseVC.show(target: self) {[weak self] in guard let self = self else { return } cell?.stratPlayLive() } return } //保存图片 if let cell = collectionView.cellForItem(at: IndexPath(item: currentIndex, section: 0)) as? TSLiveWallpaperBrowseCell { cell.saveLivePhoto { [weak self] success in guard let self = self else { return } if success { kSaveSuccesswShared.show(atView: self.view) }else{ TSToastShared.showToast(text: "Save Fail".localized) } } } } saveBtn.cornerRadius = 24 return saveBtn }() lazy var deleteBtn: UIButton = {//删除按钮 let deleteBtn = UIButton.createButton(image: UIImage(named: "delete_white"),backgroundColor: UIColor.fromHex("#111111", alpha: 0.2),corner: 16.0) { [weak self] in guard let self = self else { return } showCustomAlert( message: "Are you sure to delete".localized, deleteHandler: { [weak self] in guard let self = self else { return } if let itemModel = self.dataModelArray.safeObj(At: currentIndex) { self.dataModelArray.remove(at: currentIndex) } deleteCompletion?(currentIndex) if self.dataModelArray.count == 0 { self.pop() }else{ self.collectionView.reloadData() } }, cancelHandler: { } ) } deleteBtn.isHidden = true return deleteBtn }() lazy var copyrightBtn: UIButton = { //版权信息按钮 let copyrightBtn = UIButton.createButton(image: UIImage(named: "info_white"),backgroundColor: UIColor.fromHex("#111111", alpha: 0.2),corner: 16.0) { [weak self] in guard let self = self else { return } navigationController?.pushViewController(TSLiveWallpaperCopyrightVC(), animated: true) } return copyrightBtn }() lazy var btnsAllView: UIView = { let btnsAllView = UIView() btnsAllView.addSubview(copyrightBtn) copyrightBtn.snp.makeConstraints { make in make.width.height.equalTo(44) make.trailing.equalTo(-16) make.top.equalTo(topLineH) } btnsAllView.addSubview(deleteBtn) deleteBtn.snp.makeConstraints { make in make.width.height.equalTo(44) make.trailing.equalTo(-16) make.top.equalTo(topLineH) } //预览按钮 let previewBtn = UIButton.createButton(image: UIImage(named: "random_preview"),backgroundColor: UIColor.fromHex("#000000", alpha: 0.5),corner: 24) { [weak self] in guard let self = self else { return } self.isPreview = !self.isPreview } btnsAllView.addSubview(previewBtn) previewBtn.snp.makeConstraints { make in make.trailing.equalTo(-42) make.bottom.equalTo(-16-k_Height_safeAreaInsetsBottom()) make.width.height.equalTo(48) } btnsAllView.addSubview(saveBtn) saveBtn.snp.makeConstraints { make in make.trailing.equalTo(previewBtn.snp.leading).offset(-18) make.leading.equalTo(42) make.bottom.equalTo(-16-k_Height_safeAreaInsetsBottom()) make.height.equalTo(48) } return btnsAllView }() //MARK: previewView lazy var previewView: UIView = { let previewView = UIView() previewView.isHidden = true let imageView = UIImageView.createImageView(imageName:"iPhone_lock_screen_preview") previewView.addSubview(imageView) imageView.snp.makeConstraints { make in make.edges.equalToSuperview() } let tap = UITapGestureRecognizer(target: self, action: #selector(onPreviewButton)) previewView.addGestureRecognizer(tap) return previewView }() lazy var collectionView: UICollectionView = { let collectionView = UICollectionView.createCommon(delegate: self, cellReuseIds: ["TSLiveWallpaperBrowseCell"]) collectionView.backgroundColor = .black if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.minimumInteritemSpacing = 0 flowLayout.minimumLineSpacing = 0 flowLayout.itemSize = UIScreen.main.bounds.size flowLayout.scrollDirection = .horizontal } return collectionView }() override func createView() { setNavBarViewHidden(true) view.backgroundColor = .clear contentView.addSubview(collectionView) contentView.addSubview(btnsAllView) contentView.addSubview(previewView) contentView.addSubview(backBtn) collectionView.snp.makeConstraints { make in make.edges.equalToSuperview() } btnsAllView.snp.makeConstraints { make in make.edges.equalToSuperview() } previewView.snp.makeConstraints { make in make.edges.equalToSuperview() } backBtn.snp.makeConstraints { make in make.leading.equalTo(16) make.top.equalTo(topLineH) make.width.height.equalTo(44) } let pan = UIPanGestureRecognizer(target: self, action: #selector(onPanGesture(_:))) view.addGestureRecognizer(pan) kDelayMainShort { self.collectionView.setContentOffset(CGPoint(x: CGFloat(self.currentIndex) * self.collectionView.frame.size.width, y: 0), animated: false) } self.reloadUI() } func reloadUI() { if let itemModel = self.dataModelArray.safeObj(At: currentIndex) { TSViewTool.setNormalSubmitBtn(btn: saveBtn, showVip: itemModel.vip) } } } //MARK: 点击操作 extension TSLiveWallpaperBrowseVC{ @objc func onPreviewButton() { isPreview = !isPreview } @objc func onPanGesture(_ pan: UIPanGestureRecognizer) { let trans = pan.translation(in: self.view) let velocity = pan.velocity(in: nil) switch pan.state { case .began: if abs(trans.x) > abs(trans.y) { isPanningDown = false } else if trans.y > 0 { isPanningDown = true } case .changed: switch isPanningDown { case .none: if abs(trans.x) > abs(trans.y) { isPanningDown = false } else if trans.y > 0 { isPanningDown = true } case .some(true): var viewTrans = self.view.transform viewTrans = viewTrans.translatedBy(x: 0, y: trans.y) if viewTrans.ty >= 0 { self.view.transform = viewTrans } case .some(false): let newOffsetX = self.collectionView.contentOffset.x - trans.x if newOffsetX >= 0 && newOffsetX <= (self.collectionView.contentSize.width - self.collectionView.bounds.width) { self.collectionView.contentOffset.x -= trans.x } } pan.setTranslation(.zero, in: pan.view) case .ended: switch isPanningDown { case .none: debugPrint("no thing to do ") self.view.transform = .identity case .some(true): if self.view.transform.ty > 80 || velocity.y >= 500 { UIView.animate(withDuration: 0.2) { [weak self] in self?.view.transform.ty = k_ScreenHeight } completion: { [weak self] finished in if finished { self?.dismiss(animated: false) } } } else { self.view.transform = .identity } case .some(false): let velocity = pan.velocity(in: pan.view) let page: CGFloat if velocity.x >= 500 { page = (collectionView.contentOffset.x / collectionView.bounds.width).rounded(.down) } else if velocity.x <= 500 { page = (collectionView.contentOffset.x / collectionView.bounds.width).rounded(.up) } else { page = (collectionView.contentOffset.x / collectionView.bounds.width).rounded() } let newOffsetX = page * collectionView.bounds.width collectionView.setContentOffset(CGPoint(x: newOffsetX, y: 0), animated: true) } isPanningDown = nil case .cancelled, .failed: self.view.transform = CGAffineTransform.identity isPanningDown = nil default: debugPrint(pan.state) debugPrint(pan.translation(in: self.view)) } } } //MARK: UICollectionViewDataSource extension TSLiveWallpaperBrowseVC:UICollectionViewDataSource,UICollectionViewDelegate { func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { resetIndexWithOffset(scrollView) } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { resetIndexWithOffset(scrollView) } private func resetIndexWithOffset(_ scrollView: UIScrollView) { let item = Int((scrollView.contentOffset.x / scrollView.bounds.width).rounded()) currentIndex = item } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return dataModelArray.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TSLiveWallpaperBrowseCell", for: indexPath) as! TSLiveWallpaperBrowseCell debugPrint("collectionView cellForItemAt=\(indexPath)") if let wallpaperModel = dataModelArray.safeObj(At: indexPath.item){ cell.itemModel = wallpaperModel } return cell } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { debugPrint("collectionView didEndDisplaying=\(indexPath)") if let cell = cell as? TSLiveWallpaperBrowseCell { cell.stopPlayLive() } } func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath){ debugPrint("collectionView willDisplay=\(indexPath)") if let cell = cell as? TSLiveWallpaperBrowseCell { cell.stratPlayLive() } } } class TSLiveWallpaperBrowseCell : TSBaseCollectionCell,PHLivePhotoViewDelegate{ private let showImageViewW = k_ScreenWidth - 32 private lazy var livePhotoTool = LivePhoto() private var isCanPlay = true lazy var livePhotoView: PHLivePhotoView = { let liveIv = PHLivePhotoView() liveIv.delegate = self liveIv.contentMode = .scaleAspectFill liveIv.isHidden = true return liveIv }() lazy var loading: UIActivityIndicatorView = { let loading = UIActivityIndicatorView(style: .large) loading.hidesWhenStopped = true loading.color = .white return loading }() var itemModel:TSLiveWallpaperBrowseItemModel? { didSet { self.livePhotoView.livePhoto = nil self.livePhotoView.isHidden = true if let livePhoto = itemModel?.livePhoto { self.livePhotoView.livePhoto = livePhoto self.livePhotoView.isHidden = false self.livePhotoView.startPlayback(with: .full) return } if let wallpaperModel = itemModel, wallpaperModel.imageUrl.count > 0 { loading.startAnimating() var imageCachePath = "" var videoCachePath = "" let group = DispatchGroup() group.enter() TSCommonTool.downloadAndCacheFile(from: wallpaperModel.imageUrl,fileEx: "jpeg") { path, error in if let path = path { imageCachePath = path } group.leave() } group.enter() TSCommonTool.downloadAndCacheFile(from: wallpaperModel.videoUrl,fileEx: "mov") { path, error in if let path = path { videoCachePath = path } group.leave() } group.notify(queue: .main) { [self] in if imageCachePath.count == 0 || videoCachePath.count == 0 { return } let imageCacheUrl = URL(fileURLWithPath: imageCachePath) let videoCacheUrl = URL(fileURLWithPath: videoCachePath) if !TSFileManagerTool.fileExists(at: imageCacheUrl) || !TSFileManagerTool.fileExists(at: videoCacheUrl){ return } self.itemModel?.imageCacheUrl = imageCacheUrl self.itemModel?.videoCacheUrl = videoCacheUrl // if videoCacheUrl.path.contains("/saveVideo/") { // self.loading.stopAnimating() // // LivePhotoConverter.livePhotoRequest(videoURL: videoCacheUrl, imageURL: imageCacheUrl) { livePhoto in // self.itemModel?.livePhoto = livePhoto // self.itemModel?.livePhotoResources = (imageCacheUrl,videoCacheUrl) // // if let livePhoto = livePhoto { // self.livePhotoView.livePhoto = livePhoto // self.livePhotoView.isHidden = false // self.livePhotoView.startPlayback(with: .full) // }else{ // debugPrint("livePhoto.generate fail") // } // } // return // } // // LivePhotoConverter.convertVideo(videoCacheUrl, imageURL: imageCacheUrl) { success, photoURL, videoURL, errorMsg in // DispatchQueue.main.async { // self.loading.stopAnimating() // if success { // LivePhotoConverter.livePhotoRequest(videoURL: videoURL!, imageURL: photoURL!) { livePhoto in // self.itemModel?.livePhoto = livePhoto // self.itemModel?.livePhotoResources = (photoURL!,videoURL!) // // if let livePhoto = livePhoto { // self.livePhotoView.livePhoto = livePhoto // self.livePhotoView.isHidden = false // self.livePhotoView.startPlayback(with: .full) // }else{ // debugPrint("livePhoto.generate fail") // } // } // }else{ // debugPrint(errorMsg) // } // } // } livePhotoTool.generate(from: imageCacheUrl, videoURL: videoCacheUrl, progress: { (percent) in debugPrint(percent) }) { [weak self] (livePhoto, resources) in guard let self = self else { return } loading.stopAnimating() itemModel?.livePhoto = livePhoto itemModel?.livePhotoResources = resources if let livePhoto = livePhoto { self.livePhotoView.livePhoto = livePhoto self.livePhotoView.isHidden = false self.livePhotoView.startPlayback(with: .full) }else{ debugPrint("livePhoto.generate fail") } } } } } } override func creatUI() { self.backgroundColor = UIColor.clear self.contentView.backgroundColor = UIColor.black contentView.addSubview(livePhotoView) livePhotoView.snp.makeConstraints { make in make.edges.equalToSuperview() } contentView.addSubview(loading) loading.snp.makeConstraints { make in make.center.equalToSuperview() } } func livePhotoView(_ livePhotoView: PHLivePhotoView, didEndPlaybackWith playbackStyle: PHLivePhotoViewPlaybackStyle) { // if !livePhotoView.isHidden { if self.isCanPlay { debugPrint("startPlayback") kDelayOnMainThread(1.0) { livePhotoView.startPlayback(with: .full) } } } func saveLivePhoto(completion: @escaping (Bool) -> Void){ // if let resources = itemModel?.livePhotoResources { // LivePhoto.saveToLibrary(resources, completion: { (success) in // kMainAsync { // if success { // debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.") // completion(true) // }else { // debugPrint("Live Photo Not Saved,The live photo was not saved to Photos.") // completion(false) // } // } // }) // } // if let resources = itemModel?.livePhotoResources { // LivePhotoConverter.saveToLibrary(videoURL: resources.pairedVideo, imageURL: resources.pairedImage) { success in // kMainAsync { // if success { // debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.") // completion(true) // }else { // debugPrint("Live Photo Not Saved,The live photo was not saved to Photos.") // completion(false) // } // } // } // } guard let videoCacheUrl = itemModel?.videoCacheUrl, let imageCacheUrl = itemModel?.imageCacheUrl else{ TSToastShared.showToast(text: "save fail") return } TSToastShared.showLoading() if videoCacheUrl.path.contains("/saveVideo/") { LivePhotoConverter.saveToLibrary(videoURL: videoCacheUrl, imageURL: imageCacheUrl) { success in kMainAsync { TSToastShared.hideLoading() if success { debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.") completion(true) }else { debugPrint("Live Photo Not Saved,The live photo was not saved to Photos.") completion(false) } } } return } LivePhotoConverter.convertVideo(videoCacheUrl, imageURL: imageCacheUrl) { success, photoURL, videoURL, errorMsg in DispatchQueue.main.async { TSToastShared.hideLoading() if success { LivePhotoConverter.saveToLibrary(videoURL: videoURL!, imageURL: photoURL!) { success in kMainAsync { if success { debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.") completion(true) }else { debugPrint("Live Photo Not Saved,The live photo was not saved to Photos.") completion(false) } } } }else{ debugPrint(errorMsg) } } } } func stopPlayLive() { debugPrint("stopPlayLive") self.isCanPlay = false livePhotoView.isHidden = true livePhotoView.stopPlayback() } func stratPlayLive() { debugPrint("stratPlayLive") self.isCanPlay = true self.livePhotoView.isHidden = false self.livePhotoView.startPlayback(with: .full) } }